diff --git a/crates/bitcoin/examples/run-proof.rs b/crates/bitcoin/examples/run-proof.rs index f0a6ff043f..b880721a65 100644 --- a/crates/bitcoin/examples/run-proof.rs +++ b/crates/bitcoin/examples/run-proof.rs @@ -1,6 +1,9 @@ extern crate bitcoin; -use bitcoin::merkle::MerkleProof; +use bitcoin::{ + merkle::{MerkleProof, PartialTransactionProof}, + parser::parse_transaction, +}; // Proving that the transaction // 8d30eb0f3e65b8d8a9f26f6f73fc5aafa5c0372f9bb38aa38dd4c9dd1933e090 @@ -13,7 +16,17 @@ const PROOF_HEX: &str = "010000006fd2c5a8fac33dbe89bb2a2947a73eed2afc3b1d4f88694 fn main() { let raw_proof = hex::decode(PROOF_HEX).unwrap(); let proof = MerkleProof::parse(&raw_proof).unwrap(); - let result = proof.verify_proof().unwrap(); + let tx_hex = "010000000168a59c95a89ed5e9af00e90a7823156b02b7811000c63170bb2440d8db6a1869000000008a473044022050c32cf6cd888178268701a636b189dc3f026ee3ebd230fd77018e54044aac77022055aa7fa73c524dd4f0be02694683a21eb03d5d2f2c519d7dc7110b742c417517014104aa5c77986a87b93b03d949013e629601b6dbdbd5fc09f3bef9263b64b3c38d79d443fafa2fbf422a203fe433adf6e071f3172a53747739ce72c640fe7e514981ffffffff0140420f00000000001976a91449cf380abdb86449efc694988bf0f447739f73cd88ac00000000"; + let raw_tx = hex::decode(tx_hex).unwrap(); + let transaction = parse_transaction(&raw_tx).unwrap(); + + let unchecked_proof = PartialTransactionProof { + transaction, + tx_encoded_len: raw_tx.len() as u32, + merkle_proof: proof.clone(), + }; + + let result = unchecked_proof.verify_proof().unwrap(); println!( "proof: transactions count = {}, hash count = {}, tree height = {},\nmerkle root = {:?}, hashes count = {}, flags={:?},\ncomputed merkle root = {}, position = {}", proof.transactions_count, diff --git a/crates/bitcoin/src/error.rs b/crates/bitcoin/src/error.rs index 2fa5fd5edb..4efca6e89a 100644 --- a/crates/bitcoin/src/error.rs +++ b/crates/bitcoin/src/error.rs @@ -20,4 +20,5 @@ pub enum Error { ArithmeticUnderflow, InvalidCompact, BoundExceeded, + InvalidTxid, } diff --git a/crates/bitcoin/src/formatter.rs b/crates/bitcoin/src/formatter.rs index b64a9a15ae..4ac0996444 100644 --- a/crates/bitcoin/src/formatter.rs +++ b/crates/bitcoin/src/formatter.rs @@ -3,8 +3,8 @@ use sp_std::{prelude::*, vec, vec::Vec}; use crate::{merkle::MerkleProof, script::*, types::*, Error, GetCompact}; -const WITNESS_FLAG: u8 = 0x01; -const WITNESS_MARKER: u8 = 0x00; +pub(crate) const WITNESS_FLAG: u8 = 0x01; +pub(crate) const WITNESS_MARKER: u8 = 0x00; pub trait Writer { fn write(&mut self, buf: &[u8]) -> Result<(), Error>; @@ -139,9 +139,16 @@ impl TryFormat for TransactionInput { }; previous_hash.try_format(w)?; previous_index.try_format(w)?; - CompactUint::from_usize(self.script.len()).try_format(w)?; + if let TransactionInputSource::Coinbase(Some(height)) = self.source { - Script::height(height).as_bytes().try_format(w)?; + let height_bytes = Script::height(height); + // account for the height in version 2 blocks + let script_len = self.script.len().saturating_add(height_bytes.len()); + + CompactUint::from_usize(script_len).try_format(w)?; + height_bytes.as_bytes().try_format(w)?; + } else { + CompactUint::from_usize(self.script.len()).try_format(w)?; } w.write(&self.script)?; // we already formatted the length self.sequence.try_format(w)?; diff --git a/crates/bitcoin/src/merkle.rs b/crates/bitcoin/src/merkle.rs index e516a05a0f..9923006389 100644 --- a/crates/bitcoin/src/merkle.rs +++ b/crates/bitcoin/src/merkle.rs @@ -6,7 +6,7 @@ use mocktopus::macros::mockable; use crate::{ parser::BytesParser, - types::{BlockHeader, CompactUint, H256Le}, + types::{BlockHeader, CompactUint, H256Le, Transaction}, utils::hash256_merkle_step, Error, }; @@ -21,6 +21,14 @@ const MIN_TRANSACTION_WEIGHT: u32 = WITNESS_SCALE_FACTOR * 60; // https://github.com/bitcoin/bitcoin/blob/7fcf53f7b4524572d1d0c9a5fdc388e87eb02416/src/merkleblock.cpp#L155 const MAX_TRANSACTIONS_IN_PROOF: u32 = MAX_BLOCK_WEIGHT / MIN_TRANSACTION_WEIGHT; +#[derive(Clone, Encode, Decode, TypeInfo, PartialEq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct PartialTransactionProof { + pub transaction: Transaction, + pub tx_encoded_len: u32, + pub merkle_proof: MerkleProof, +} + /// Stores the content of a merkle tree #[derive(Clone)] #[cfg_attr(feature = "std", derive(Debug))] @@ -43,11 +51,14 @@ struct MerkleProofTraversal { hash_position: Option, } -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct ProofResult { pub extracted_root: H256Le, pub transaction_hash: H256Le, pub transaction_position: u32, + pub transaction: Transaction, + pub tx_count: u32, + pub block_hash: H256Le, } impl MerkleTree { @@ -95,6 +106,82 @@ impl MerkleTree { } #[cfg_attr(test, mockable)] +impl PartialTransactionProof { + /// Computes the merkle root of the proof partial merkle tree + pub fn verify_proof(self) -> Result { + let mut traversal = MerkleProofTraversal { + bits_used: 0, + hashes_used: 0, + merkle_position: None, + hash_position: None, + }; + + // fail if no transactions + if self.merkle_proof.transactions_count == 0 { + return Err(Error::MalformedMerkleProof); + } + + // fail if too many transactions + if self.merkle_proof.transactions_count > MAX_TRANSACTIONS_IN_PROOF { + return Err(Error::MalformedMerkleProof); + } + + // fail if not at least one bit per hash + if self.merkle_proof.flag_bits.len() < self.merkle_proof.hashes.len() { + return Err(Error::MalformedMerkleProof); + } + + let root = self.merkle_proof.traverse_and_extract( + self.merkle_proof.compute_partial_tree_height(), + 0, + &mut traversal, + )?; + let merkle_position = traversal.merkle_position.ok_or(Error::InvalidMerkleProof)?; + let hash_position = traversal.hash_position.ok_or(Error::InvalidMerkleProof)?; + + // fail if all hashes are not used + if traversal.hashes_used != self.merkle_proof.hashes.len() { + return Err(Error::MalformedMerkleProof); + } + + // fail if all bits are not used + if traversal + .bits_used + .checked_add(7) + .ok_or(Error::ArithmeticOverflow)? + .checked_div(8) + .ok_or(Error::ArithmeticUnderflow)? + != self + .merkle_proof + .flag_bits + .len() + .checked_add(7) + .ok_or(Error::ArithmeticOverflow)? + .checked_div(8) + .ok_or(Error::ArithmeticUnderflow)? + { + return Err(Error::MalformedMerkleProof); + } + + let tx_id = self.transaction.tx_id_bounded(self.tx_encoded_len)?; + + // fail if the transaction hash is invalid + if self.merkle_proof.hashes[hash_position] != tx_id { + return Err(Error::InvalidTxid); + } + // ensure!(self.merkle_proof.hashes[hash_position] == tx_id, Error::InvalidTxid); + + Ok(ProofResult { + extracted_root: root, + transaction_hash: self.merkle_proof.hashes[hash_position], + transaction_position: merkle_position, + transaction: self.transaction, + tx_count: self.merkle_proof.transactions_count, + block_hash: self.merkle_proof.block_header.hash, + }) + } +} + impl MerkleProof { /// Returns the width of the partial merkle tree pub fn compute_partial_tree_width(&self, height: u32) -> u32 { @@ -153,62 +240,37 @@ impl MerkleProof { Ok(hashed_bytes) } - /// Computes the merkle root of the proof partial merkle tree - pub fn verify_proof(&self) -> Result { - let mut traversal = MerkleProofTraversal { - bits_used: 0, - hashes_used: 0, - merkle_position: None, - hash_position: None, - }; - - // fail if no transactions - if self.transactions_count == 0 { - return Err(Error::MalformedMerkleProof); - } - - // fail if too many transactions - if self.transactions_count > MAX_TRANSACTIONS_IN_PROOF { - return Err(Error::MalformedMerkleProof); - } - - // fail if not at least one bit per hash - if self.flag_bits.len() < self.hashes.len() { - return Err(Error::MalformedMerkleProof); + pub(crate) fn traverse_and_build( + &mut self, + height: u32, + pos: u32, + tx_ids: &[H256Le], + matches: &[bool], + ) -> Result<(), Error> { + let mut parent_of_match = false; + let mut p = pos << height; + while p < (pos + 1) << height && p < self.transactions_count { + parent_of_match |= matches[p as usize]; + p += 1; } - let root = self.traverse_and_extract(self.compute_partial_tree_height(), 0, &mut traversal)?; - let merkle_position = traversal.merkle_position.ok_or(Error::InvalidMerkleProof)?; - let hash_position = traversal.hash_position.ok_or(Error::InvalidMerkleProof)?; + self.flag_bits.push(parent_of_match); - // fail if all hashes are not used - if traversal.hashes_used != self.hashes.len() { - return Err(Error::MalformedMerkleProof); - } + if height == 0 || !parent_of_match { + let hash = self.compute_merkle_root(pos, height, tx_ids)?; + self.hashes.push(hash); + } else { + let next_height = height.checked_sub(1).ok_or(Error::ArithmeticUnderflow)?; + let left_index = pos.checked_mul(2).ok_or(Error::ArithmeticOverflow)?; + let right_index = left_index.checked_add(1).ok_or(Error::ArithmeticOverflow)?; - // fail if all bits are not used - if traversal - .bits_used - .checked_add(7) - .ok_or(Error::ArithmeticOverflow)? - .checked_div(8) - .ok_or(Error::ArithmeticUnderflow)? - != self - .flag_bits - .len() - .checked_add(7) - .ok_or(Error::ArithmeticOverflow)? - .checked_div(8) - .ok_or(Error::ArithmeticUnderflow)? - { - return Err(Error::MalformedMerkleProof); + self.traverse_and_build(next_height, left_index, tx_ids, matches)?; + if right_index < self.compute_partial_tree_width(next_height) { + self.traverse_and_build(next_height, right_index, tx_ids, matches)?; + } } - Ok(ProofResult { - extracted_root: root, - transaction_hash: self.hashes[hash_position], - transaction_position: merkle_position, - }) + Ok(()) } /// Parses a merkle proof as produced by the bitcoin client gettxoutproof @@ -249,46 +311,14 @@ impl MerkleProof { hashes, }) } - - pub(crate) fn traverse_and_build( - &mut self, - height: u32, - pos: u32, - tx_ids: &[H256Le], - matches: &[bool], - ) -> Result<(), Error> { - let mut parent_of_match = false; - let mut p = pos << height; - while p < (pos + 1) << height && p < self.transactions_count { - parent_of_match |= matches[p as usize]; - p += 1; - } - - self.flag_bits.push(parent_of_match); - - if height == 0 || !parent_of_match { - let hash = self.compute_merkle_root(pos, height, tx_ids)?; - self.hashes.push(hash); - } else { - let next_height = height.checked_sub(1).ok_or(Error::ArithmeticUnderflow)?; - let left_index = pos.checked_mul(2).ok_or(Error::ArithmeticOverflow)?; - let right_index = left_index.checked_add(1).ok_or(Error::ArithmeticOverflow)?; - - self.traverse_and_build(next_height, left_index, tx_ids, matches)?; - if right_index < self.compute_partial_tree_width(next_height) { - self.traverse_and_build(next_height, right_index, tx_ids, matches)?; - } - } - - Ok(()) - } } #[cfg(test)] mod tests { + use crate::parser::parse_transaction; + use super::*; - use mocktopus::mocking::*; use sp_core::H256; use sp_std::str::FromStr; @@ -302,31 +332,7 @@ mod tests { // block: https://www.blockchain.com/btc/block/0000000000000000007962066dcd6675830883516bcf40047d42740a85eb2919 const PROOF_HEX: &str = "00000020ecf348128755dbeea5deb8eddf64566d9d4e59bc65d485000000000000000000901f0d92a66ee7dcefd02fa282ca63ce85288bab628253da31ef259b24abe8a0470a385a45960018e8d672f8a90a00000d0bdabada1fb6e3cef7f5c6e234621e3230a2f54efc1cba0b16375d9980ecbc023cbef3ba8d8632ea220927ec8f95190b30769eb35d87618f210382c9445f192504074f56951b772efa43b89320d9c430b0d156b93b7a1ff316471e715151a0619a39392657f25289eb713168818bd5b37476f1bc59b166deaa736d8a58756f9d7ce2aef46d8004c5fe3293d883838f87b5f1da03839878895b71530e9ff89338bb6d4578b3c3135ff3e8671f9a64d43b22e14c2893e8271cecd420f11d2359307403bb1f3128885b3912336045269ef909d64576b93e816fa522c8c027fe408700dd4bdee0254c069ccb728d3516fe1e27578b31d70695e3e35483da448f3a951273e018de7f2a8f657064b013c6ede75c74bbd7f98fdae1c2ac6789ee7b21a791aa29d60e89fff2d1d2b1ada50aa9f59f403823c8c58bb092dc58dc09b28158ca15447da9c3bedb0b160f3fe1668d5a27716e27661bcb75ddbf3468f5c76b7bed1004c6b4df4da2ce80b831a7c260b515e6355e1c306373d2233e8de6fda3674ed95d17a01a1f64b27ba88c3676024fbf8d5dd962ffc4d5e9f3b1700763ab88047f7d0000"; - - fn sample_valid_proof_result() -> ProofResult { - let tx_id = H256Le::from_bytes_le( - &hex::decode("c8589f304d3b9df1d4d8b3d15eb6edaaa2af9d796e9d9ace12b31f293705c5e9".to_owned()).unwrap(), - ); - let merkle_root = H256Le::from_bytes_le( - &hex::decode("90d079ef103a8b7d3d9315126468f78b456690ba6628d1dcd5a16c9990fbe11e".to_owned()).unwrap(), - ); - ProofResult { - extracted_root: merkle_root, - transaction_hash: tx_id, - transaction_position: 0, - } - } - - #[test] - fn test_mock_verify_proof() { - let mock_proof_result = sample_valid_proof_result(); - - let proof = MerkleProof::parse(&hex::decode(PROOF_HEX).unwrap()).unwrap(); - MerkleProof::verify_proof.mock_safe(move |_| MockResult::Return(Ok(mock_proof_result))); - - let res = MerkleProof::verify_proof(&proof).unwrap(); - assert_eq!(res, mock_proof_result); - } + const TX_HEX: &str = "02000000013f123860735a487635587ec2e40f8c979ff487baed0af3af0011c14e19a5c368be0700008a47304402202b0f871fba25ae9908f5a4a3075237bd311265309ffa4e58f57e146cdd01916702204a1230f836d039713bbe7063dd9ebefb54e49c1cf30aec1b9bd7df820cc1ff3301410433e05b29670f19cbc499f207f11abe1c69f77f00d5cbb9dbec5b5fe7527e2f41fa1e90f10a05e9c0a34d255988082e190c9ee7ea05f62297d4f76d9b61d7561bffffffff01d69b0100000000001976a914cd55050b6536a764c00d061afa7500d5a552558e88ac00000000"; #[test] fn test_parse_proof() { @@ -370,8 +376,14 @@ mod tests { #[test] fn test_extract_hash() { let proof = MerkleProof::parse(&hex::decode(PROOF_HEX).unwrap()).unwrap(); + let tx = parse_transaction(&hex::decode(TX_HEX).unwrap()).unwrap(); + let partial_proof = PartialTransactionProof { + merkle_proof: proof.clone(), + transaction: tx, + tx_encoded_len: TX_HEX.len() as u32 / 2, + }; let merkle_root = H256Le::from_bytes_le(&proof.block_header.merkle_root.to_bytes_le()); - let result = proof.verify_proof().unwrap(); + let result = partial_proof.verify_proof().unwrap(); assert_eq!(result.extracted_root, merkle_root); assert_eq!(result.transaction_position, 48); let expected_tx_hash = H256Le::from_hex_be("61a05151711e4716f31f7a3bb956d1b030c4d92093b843fa2e771b95564f0704"); diff --git a/crates/bitcoin/src/parser.rs b/crates/bitcoin/src/parser.rs index e491efc1d4..4e21b3af19 100644 --- a/crates/bitcoin/src/parser.rs +++ b/crates/bitcoin/src/parser.rs @@ -60,8 +60,8 @@ make_parsable_int!(i64, 8); impl Parsable for CompactUint { fn parse(raw_bytes: &[u8], position: usize) -> Result<(CompactUint, usize), Error> { - let last_byte = sp_std::cmp::min(position + 3, raw_bytes.len()); - let (value, bytes_consumed) = parse_compact_uint(raw_bytes.get(position..last_byte).ok_or(Error::EndOfFile)?)?; + // note: passing entire remaining buffer since we don't know how big the bytes the uint takes up + let (value, bytes_consumed) = parse_compact_uint(raw_bytes.get(position..).ok_or(Error::EndOfFile)?)?; Ok((CompactUint { value }, bytes_consumed)) } } @@ -502,7 +502,10 @@ pub(crate) mod tests { use std::str::FromStr; use super::*; - use crate::{Address, PublicKey, Script}; + use crate::{ + formatter::{BoundedWriter, TryFormat}, + Address, PublicKey, Script, + }; use frame_support::{assert_err, assert_ok}; // examples from https://bitcoin.org/en/developer-reference#block-headers @@ -674,6 +677,13 @@ pub(crate) mod tests { assert_eq!(transaction.lock_at, LockTime::BlockHeight(0)); } + #[test] + fn test_parse_long_transaction() { + // txid 1949081b76eeca4ba50ede914a2b7a13ca1457a53a2868b171c4a9c722c3e8b1 + let tx_bytes = hex::decode(&"01000000000101b744d102b2320c192b8861366ffb187950ae0762261bc3e0c9b2b50ecffaf28a0000000000fdffffff0110270000000000002251203a3e4a11d89ce38ea846a6c35f72474b9627ca39dd6a682be415845875e7abe70340400ac147e734a3d6ee8c99d8af576190ac528282b89423cba2e9c806e3c085c720355284f57eff96bab4613d58eebde2251c8798b632f595a5e7b24426cb835cfe335e0100204b295f7286658781d63ff8444369ad9fc4aa20018637f9e5d433dd729f721f82ac0063036f7264010109696d6167652f676966004d080252494646f25b010057454250565038580a000000120000002f06002f0600414e494d06000000000082ff0000414e4d46182000000000000000002f06002f0600e80300025650382000200000b0c8029d012a300630063e9d4ea34d3fa4a32222bbd81bf01389676efc7c99c0e376237ffff6ccac0da3509c7f20ff71fef3fdabfd47efff172f51fce3f23ff2cb94f7b9ff873d8446f3a56fc37f5efca4f9a5fd83f9efb66f300fee9fc83f53bfbc7c72f4c5e603f443f57fdeaffb97e8f7f00f81be801fdcffb2fabe7f98ffe1eeabfca3d403f807f5effffeb13fb39f081fbafe8effff3b383a73fa2bfe77da8e3fe733d61c88ffe97ba032bff4ff87ff947fe7356bf2a4f59c0428cbce23ddd5eb5d42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba844d0802c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd41d3f3ae97db984e8dc1f81fb076b835af5655956559561e6408cf7c32036b835af5648b7a32f388f7757ad75098dba84c6dd42636ea131b75098dba84c6dd42636ea13139ff31b7509d36579010212121b49121b491fe14928f0a49478524a3c29251e14928f0a49478524a3c29251e14928f0a46b409979c47bbabd2d0116353892bfa7931b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba83b667b5eb608938f87d063778c874b3fe636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42626414271ea855eacab245bc0857b44b83b5c18e8ab67a9253a23671f2220638977b5c1970ff77ba83b5c1ad7916f465e711eeeaf5aea131b75098dba84c6dd42636ea131b75098d6716f40f16f054c938f91fe14928f0a499aa36748dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd4257f53978d4c9081fea131b75098dba84c6d9c6e70aabd6ba84c6dd42636ea131b75098dba84c6dd42636ea131b75098d67190e967fcafe9e4c6dd42636ea131b74f01090ff0a49478524a3c29251e14924d08028f0a49478524a3c29251e14928f0a46b418e7b6cedd77ed5eacab1015f943f7757ad75098dba84e9b12861bc469a7e07ec19b48735ffd43f03f604fd3268cbce23ddd5eb5d42636ea131b75098dba84c6dd426359c643ad75098dba84c6dd42636ea131b75076ccf520ff3d8ce8aabd6ba84c6dd42636ea131b75098dba84c6dd42636ce3738555eb5d42636ea131b75098dba84c6b38b782a64840ff5098dba84c6dd42636ea131b75098dba84c6dd42636e7b1abe51e14cb79c47bbabd6ba84c6dd426359c5bc15324207fa84c6dd42636ea131b75098dba84c6dd42636cc75c88b2acab2acab2acab15f96913beb3c6925e1febff0fc9ff87faffc3d6bdaf5aea131b75098dba775f6b440b5c1ad7403a47e2dc1f81e7ec4b12abd5956168531c2636ea131b75098dba84c6dd42636ea1313a02319e412de914040de56f07f51e0bea131b75098dba84c6dd4c151fb4097a5a424f22de8cbce23ddd5eb5d42636ea131b75098db38dce1557ad74f1d6bdaf59b4cda32f388f7757ad75098dba83b667a907f9ec674555eb5d42636ea131b75098dba84c6dd3c04243fc29251c9d6bdaf59b4cda32f388f7757ad75098dba83b667a907f9ec674555eb5d42636ea131b75098dba84c6dd3b8593e5657af6b8312ec1ebeeeaeacb5ed7acd86f0b35bf5ff87faf461531b75098dba84c6dd425759a032ae15b83f03cfda6097f19647ddd5eb5d4264d080236ea131b75098dba84c6dd43315b47977757a630bea131b8a404f309979c47bbabd6ba84c6dd4c15168978ce8aabd6ba84c6dd42636ea131b75098dba84c6b38c875ae9e3ad7b5eb5d41e566d1979c47bbabd6ba84c6dd42636ce36923fc29251e14928f0a49478524a3c29251e145501173e47b305f5098dba78f309979c47bbabd6ba84c6dd42636e7b19d1557ad75098dba84c6dd42636ea131b74f0ac8b83b5c19594cf281d5bce238d5af5056e30fd83b52656287a1006672bc307738ee8abf436163621d1fc07aa78b761f2bed23100594928f0a4687cf7edcc2722b3fc4323fc29251e14928f0a49478524a3c29251c71005b7ce5128f0a2cc2f9e7e51583c38b78442f302afe577fefcf27cde0fb620c48f3e47f85235fb0480de3e47f8524a3c29251e14928f0a494785235a0c74555eb5cfb85f3d8ce7c2c8f16bf96228ce408487f8478a4da51eeeaf4b3fe636ea131b75098dba84c6dd42636ea131b75076cdccf0a49477d40be7b19cf8591e2d822c842f1db3733c28b017d28f7757acce2de8cbce23ddd5eb5d42636ea131b75098dba84afea72f9f23fc23e5af520ff3d8ce7ce50884d7f2bfa9cbe7c426c7491fe149220da48ff0a49478524a3c29251e14928f0a4942adc1242cab2ac328ca0bb2f60ed5b465c9523098d6d0b4622168c2262f1f05acb09d5509761f2bed0a87a9f490fa0c74554e4f62933a3f80f508132f34d08027d11d5956558acf4938dba84c6dd42636ea131b75098dba7807f9ec6af6b418e8aabd6b9f3e9285fee0fe55ef2744e808b9f23fc28b016be7c8ff08de9e4c6dd42636ea131b75098dba84c6dcf6339f0b3a374045cf91fe14580b5f3e47f8478a33902121fe14928e4a8779c47bba9accf6bd6ba84c6dd42636ea131b75098db38da43e831bbc643ad75098d6b608b9f23fc23c519c81090ff0a49472543bce23ddd4d667b5eb5d42636ea131b75098dba84c6d980c71220fc0f38a2b3156559561ad224bb7f85234fa402b69f01ea9e18ce15b51979c4716bf95fd4e5f3e47f8468ec9cffcb0fd83b53a344f15bae9c9cc2746d62f3d7da3ddd5eb5d42636ea131b75098dba84c6b38b7d4dafbbabaa28d03acfcf91fe11e28ce408487f8524a392a1dda07f9ec6750461557ad75098dba84c6dd42636ea131b75076cd813cf1b750788066ce3e47f852369af167190eb5d42635ad7f2bfa78ed99ed7ad75098dba84c6dd42636ea131b75098db9ec69af167a32f30202c95eb5d42636cf8df3e16748dd42636cf8df3e164787190eb5d42636ea131b75098dba84c6dd42635890551b5c1ad753a801d95f563b2a55cf320467bea20f5e2dd87f11e21b9c2aaf5aea131306f5163738eece7e90ff9a2f7f4d9aeceb2facbb1bfc291b4d78b38b78055d10107e07ec0bb086375098dba84c6dd42636ea131b75095fd3c76cdcc5bd25c84579387e54d0802babd6ba84c6dd4257f4f3008b7a32f3020198ef3bce238716f465e711eeeaf5aea131b75098dba83b667a90848486e6fc9f40d1979c47bbabd6ba84c6b38c869f4f2636e7cfa487d0265e6f9e9e4c6dd42636ea131b75098dba84c6dcf6339f0b3a3740458f4e60498dba84c6dd42636ea0ed9b98b79de711eb793a273fe636e7b19d1557ad75098dba84c6dd42636e79179f115eacab124e1986b48c14757e613a36c539a7f4e3d4457b1d155752a4e14a74cd1dd334774cd1dd334774cd1dd334774cd13cd4154784609b007cafb5b97b0b7f44b11240bf87faffc3fd7fe1febff076478716f465da07fa84c6dd42636ea131b75098dba7807fa84c52c23ddc96c4c5d184c50d9b465e60acf0c6ea131ada6786f539b2b2733d483fd426273fe636ea131b75098dba84c6dd426273fe636e8022aaf4bb939b6636e7dd9b465e6066a38d7491fe148dc33c31ba83b667a907fa84c4e7fcc6dd42636ea131b75098dba84c4e7fcc6dd004555e9772736cc6dcfbb368cbcc159e18dd42635b4cf0c6ea0ed99ea41fea13139ff31b75098dba84c6dd42636ea13131402224c5bc4e1f81fb076b835ae57a31b8f9128662a891fdf0f69cd513bb3a3f79829f2f95f6b736b636a90591eeeaf4a04427b53b6afc07a55af01bb0f95f6b733a78ed984d23be54c938f91fe14928f0a49478524a3c29251e11bd3c99162079273f77574fa78ed99f1425a894d0802c7c8fef95388eed03d2f63fcf633a2aae9f4f2636ea131b75098dba84c6dd42636ea1313a022e80130457b1d15574fa78ed99ea6a1de711eeea6b37316f497a907f9ec674555d3e9e4c6dd42636ea131b75098dba84c6dd426274045d002608af63a2aae9f4f1db37318bcc555eb5d3c04242437384bc673e1647ddd4d667b5eb5d42636ea131b75098dba84c6dd4257f539b9a1ad390a3b47bb90bef3effdf7df01e96b879bd3b3adce3ba7fb4ee9265e711ee981462d3f27e57d9310c6d2579e0747f01ea1025e964781a72c15eacab13d0a5d24cbce23ddd5eb5d42636ea131b75098dba7808487f84751f0b9388f5c94c4bd5c54d95dafbbabd6b3f92de4a0b2b7d60a5e28ce407f9ec674555eb5d42636ea131b75098dba84c6dd426359c643ad73e22bd8e8aabab4435b4cda32f388f77575592dde6c6e7b238b5fcafe9e3b667b5eb5d42636ea131b75098dba84c6dd42636e7b1abe51df3a73024c6dcfbb3682b309979c47bbabd30ee7d33164b828884d78b38b782a649c7c8ff0a49478524a3c29251e14928f08c401b707e07b3277583b800b2acab2acab2ac385a1c924a3be750585a9c49f7af5ff872b1f03e49478524a3c23d93696b09f5ffde55b822946fb14671afbff2fd6b835ae6f6065d165595612393e7c8ff0a49478524a3c29251e149285bcf68a5b6e85fee085e40555eb62904b4d478524a3c29266ca29bfcf1f27444d0802e7fcafe9f80b36923fc29251e14928f0a49478524a3c237a78ed99ed7acd6c1173e47f8524a3c29251e14928f0a49472543bb40ff3d8ce7c2c8fbbabd6ba84c6dd42636ea131b75095fd3c76ccf6bd66b608b9f23fc29251e14928f0a49478524a392a1dda07f9ec673e1647ddd5eb5d42636ea131b75098dba84ae14ec2cb5c1ad754fd0ab2614783b52638b2ec4526d28f7757ad75098d6933a815e6cc21e584a515d461ede182aadcf735ddb75096228ce407f9e1893d970fd83b556a664b6eeaf5aea131b75098dba84c6dd4263714825a6cd52dd6b91f538555eb5d42636ea0eafb542d115dd064cd24ce505928f08f1467203fd4c15223671f23fc29251e14928f0a49478524a3c29251c810909638be51e14928f0a4947203fd426274045cf91ec5e625e33a2aaf5aea131b75098dba84c6dd42636ea131b75098db38dcdf9ca1132f388f7757ad74f00ff50989d01173e47b1798978ce8aabd6ba84c6dd42636ea131b75098dba84c6dd42636ce3737e72844cbce23ddd5eb5d3c03fd426274045cf91ec5e625e33a2aaf5aea131b75098dba84c6dd42636ea131b75098db38dcdf9ca1132f388f7757ad74f04eeeece8fe03d53c5bb0f93a95979c4010bc76ccf6bd6ba84c6dd42636ea131b75098dba84c6dd42636e9e021212c717ca3c29251e14928f0a49478524a3c28b016bc6a649c7c8ff0a49478524a3c29251e14928f0a49474d08028524a39021212c717ca3c29251e14928f0a49478524a3c28b016bc6a649c7c8ff0a49478524a3c29251e14928f0a49478524a39021212c717ca3c29251e14928f0a49478524a3c28b016bc6a649c7c8ff0a49478524a3c29251e14928f0a49478524a39021212c717ca3c29251e14928f0a49478524a3c28b016bc6a649c7c8ff0a49478524a3c29251e14928f0a49478524a39021212c717ca3c29251e14928f0a49478524a3c28b016bc6a649c7c8ff0a49478524a3c29251e14928f0a49478524a39021212c717ca3c29251e14928f0a49478524a3c28b016bc6a649c7c8ff0a49478524a3c29251e14928f0a49478524a39021212c717ca3c29251e14928f0a49478524a3c28b016bc6a649c7c8ff0a49478524a3c29251e14928f0a49478524a39021212c717ca3c29251e1451c6b85cfc0fd83b5c1ad7ab2acab2acab0f320467c47bb9350eed03fd42636ea131b75098dba84c6dd42636ea131b75098db9ec6af6d363a48ff0a4947846f4f2636ea12bfa9cbe7c89637cf8591f7757ad75098dba84c6dd42636ea131b75098dba84c6b38c86a8a4da51eeeaf5ae9e01fea131b74f01090ff0a2c05af1a99271f23fc29251e14928f0a49478524a3c29251e14928e408484b1c5f28f0a494785153fe636ea1313a022e7c8f62f312f19d1557ad75098dba84c6dd42636ea131b75098dba84c6d9c6e6fce5089979c47bbabd287e3b88e7e04d08027ec0c7d62d2cab2acab2acab2ac519b6593ec931b67c6f9f0b23eeeaf5aea131b75098dba84c6dd42636ea131b75098d67190d5149b4a3ddd5eb5d3c65a68c4443369d48a21a1049477d10bc76ccf6bd6ba84c6dd42636ea131b75098dba84c6dd42636e9e021212c717ca3c29251e1459b3682b309979c47bbabd66b5fcafe9e4c6dd42636ea131b75098dba84c6dd42636ea131b75076cdcc62f4551e14928f0a46e19b41598c74555eb5d4262802d78d4c938f91fe14928f0a49478524a3c29251e14928f0a494720424258e2f9478524a3c28acf2dc9e65f0ff5ff857ee016a142636ea0ef43871a2d2dc1f81f3803f60ed515782cc1c7c8ff0a49478524a3c29251e14928f0a49478524a3be54e23c40592bd6ba84c6dd4331630aabd6ba83bee82aca98ce8aabd6ba84c6dd42636ea131b75098dba84c6dd42636ea12bfa9ca75125ed7ad75098dba84c6dd42636e9e1b13711e1c5bd1979c47bbabd6ba84c6dd42636ea131b75098dba84c6dd3c0424258e2f9478524a3c29251e14928f0a49121990b4afe9e4c6dd42636ea131b75098dba84c6dd42636ea131b75098db38dcdf9ca1132f388f74cb7c43e56a72ebf390a1c0a8aeacf5395a9cbafce428702a2bab3d4e56a72ebf390a1c0a8aeacf5395a9cbafce428702a2bfe6367fcdcaea608596aa0cf91aaac8fbbabd6ba84c6dd42636ea131b75098dba84c6dd42636ea0ed9b98c4d08025e8aa3c29251c8100a412dee0feb9c825bdc1fd44ac015223671f23fc29251e14928f0a49478524a3c29251e14928f0a49106e6fce5089979c47ba78ce8aabd6ba84c6dd3c03fd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd41db37318bd15478524a3901fea131b75098dba83b667b5eb5d42636ea131b75098dba84c6dd42636ea131b75098dba84afea729d4497b5eb5d41db33d4135a86b5eacab2acab2acab2acab2acab2acab2ac57ea3dbfb0284c6dd42636ea131b75098dba84c6dd42636ea131b75098dba83b66e6317a2a8f0a4947203fcf633e375539b2bc825bdc1fcacbce23ddd5eb5d42636ea131b75098dba84c6dd42636ea131b74f010909638be51e149285bceed03fd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131b750989d01164219cbe7c8ff08de9e3b667b5eb5d42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea131ace321aa2936947bbabd2cff95fd3c98dba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ce3737e72844cbce23dd3c673e1647ddd5eb5d42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd42636ea0ed9b98c5e8aa3c29251c80ff3d8ce8aabd6ba84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c6dd4257f5394ea24bdaf5aea0ed99ea41fea131b75098db4d0802a84c6dd42636ea131b75098dba84c6dd42636ea131b75098dba84c4e808b210ce5f3e47f846f4f1db33daf5aea131b75098dba84c6dd42636ea131b75098dba83a0000feff3f3f4c4656287fbbe95472f809130d01dfd000030da9739000000000000000000000000000000000000000476ec536f2724508099e76ef2ef50e9b48517c30d65869187679e5c41fa21293c918a242f52b2a484d5ac675081b9794935f9378006954051f8bd0f8142dd0cc80001f58f9e8004cd74c4013fbf8c73663953ae4526bbdeca0f1cf904371189bda4eac78fa7e7b5a0fef3e2fdb025916bf1bc7dbc7b4e3ca9003aed9ffc903d97b30d4e33e605730c164ef5cf79e451b495e80ea48de353bc502fe0667fe068642343ff87e2d661a3deab0ab0e2e65517328dbd6f4005fc0b8eca3e600e7000005300004100101ea475a849dc2e3051402ddd895d9af6c09a6ec695fcfd138f26c803df6e38cf532b045bc4cd36500d27d028644e000139000085000556fe31cd9e60df3e782b73c44fcfe39cc893bdf6f71d7b99d0c5aafac335a1fb525be8f26e6fe4df427f37d2045b803e5f97a917ed750f9c70bab2f1e612157ec42277a91ff434708aa2449b87b49a608938686e909ffff158b08e3281def1f2c95849228edc4cceede4a56b52877fa97129b597828f99d169ec5efd2fb98b1191dc147d8a0a4dd9ce0071e0012803cb816913fbc2acd03add3defc4d0802443d18c2bade4e48a1012f26aabb1e4c110f9263e31ef1e1b16bce43d75f52f180af65b44d32135df36cafe2f8823d4445a8c4feb59e3f22aac5ae2233e3be80ed804dd854762aa08a8e0d8aa89fdfc639b3cc8a7c5fb604b22d426d2f9fe4d9d2a674a899b09afe4da8ebc5e799986ec1db090b5356886448a6b82e79f1873fc92de42dae470c76a8846c11cf137485d727e944086ffd918b455574bef11fe03a5a0b723aa57df2f7bd636ce70fa8b9e19b35793354064b252d393305d2dc61fa0405ea3af179e666096d04bfe7ae2f716f224623a58ea292614ccac1d722313aaea75e20bfa5a7c6e08f0103aca5751544cd84d395429fb604dd4485e524dd24eaef0a3e7089f7fa6f33f3db4f905c3919faac9c201fed61f2602aed135ebc4573ec45bb03e0e2ff65087243f27184f0a4d9325275c80d1d26a2cea6385e1e4003398215c5253e881d58073810c0084e0758020569c020f7619f3a25cf452aaa76b4fd8fb9efff00d3ec8e5e3bc21a91bc4d321ec47633c07150e6a32e6a14c07aed3508424bc2ab776bfedc21623dd82135384d947e337d00ec5f17d17879006980000510000420aa22af0366f27ad41e00f346d018fdb8a3f59cfdb2bf1ca233798e422e34827c3764b8cbb8e7f6036de18d6f9dee579d32b53e60933143c3eaa28eeed0c5caf09d597c4239eb3e30a0d7d68980a514a23ec854b7689805c817289563b08cf74d08029f5917057f06bd659ce6fd7835eb2ce76c84f6fce049f4027873d2d8a7164832db6a80c75f61e73bd4056439f0f9a20bae6ff8423da18fb5831dfe49d1af1052fbcf04e3a47a572a187f483bec7da75fd78991551e3e9f9d5307cf31a1250fad58981a801eb26bc4237cc61abc3c809e0b36e515e1e40494000004400002ab4ce3e1e4e5deb411861ac490b56601df103f18231e4597e31d3ac7a89f1532dede4e4898637ca3b64216e69eccba334fa9d2f9f2e0fb284c13fb5c0deb8e946db00b85ef0f22bf743b795918ef42e2f480ef8aef8225af1055930611582e03bb788b43b64bb7fb910935141f9c3b3cf2e20fd09f29bc2aa8e9ec3422a6af0f44f61dc4937a02679fdbcbb97957606e52bc5af5f79de27a4919f3ae17da709b28fc66fa0626bc4ca51f30fe1eb4af0f2eb1a98080850007e017f059567bf8c7367975100eb9149aef7b36f2f7965056470075bb5cbde2811063d610189683cc52bea12ef5ec4201f61003a0b12362f13820feed0d7274c852c201dcce418d28132bc017b3d26e1b22598a359bbb76b2254392b0554cccf8750dfaea38d57c2440ce7150d6298b831b6d5570cbe4e6a41ada91d6c3fcd45255360af508a24fd5b2005fc7db1947ce34b6271d7a5a4901bb475507cf011ba80002780e2140eb2ab148b4c97907d6c554dff6e38caf15f5bb18d4a560d69f5a500e7aa1bb38701230d6d6533dbaf480d4c34d0802ad85293c711bd9d1955af586971568ec33c1f122a07a3e1de0783af874d355a179e2e7b89545dff51f7081c385323947fbd4e5f5e1e5ed40655e1e5bd9cc32ce00c8c8732428547175158f05c62216997210202078d1e7f64981734f17bedd173add43bcf14317d654ce6ac286f98ca909f6b9586d4845d7af47f2e11f0aec81a3b5b2093cf1ede192b638634b2990d3c219a58dfa01cf3b15cfa898e374c3d44bf57e93ad1adf71f9c05a4e3c977987b266979ab89c68849a9f0cb8a2548261ccbcdc86a209820617b6140bc16dde78271d23d2b950c3fa42af3a2a5c18fa9153bcc6012a0302d6f30c6f83f5179ecf924d2e3a00de3e0d7e369a778e3c846434d8b71bdf01e1b298e8c8d413aee0015b3461e1b5663e80a7d35a70f272ee0a3087bd3c05f997bd56c928ecf3e865b42a0f825de96d086cb3b9abb56521f65f4f554874787300dc1a5685dc4d461e2ab2081e0d045311b5e647de156681d6e9efabf097e0bc21d6ed1827fffa3f744b563c3c9a39b82b2ce52c8370d63a18b677bf215252aad923d9a769d8a5bacef4f46834ceba362130a8134a463af4a8a8a3e4c2830c271aa2f063b800031e400000673800458dd610bfdbdd525c0cf1eb872ea3765a43fcfb5d629ce16c12ee809c31ccee5ad0a42dd909886ebf73068cfc236a4d819308c5abf7e162639859d99182ec7e28dfb0778d2b5e90f409722afd509b95abb1d28e4d0802dfbda142316c158f549be2d9e63c0225daf05f6ef0dcadc38fcd95ad48a3e9302e134ca913002b24c000017f6a000512b8001228a00040548000e0bb0002f2640009f9a000233b00007a5e0001a8ae00059336c3442da12e8c6d7a9f4577f5f273ae1a8794aceae5910a0368e94e91e4b394405bc5129e24fb6c6c1f26969222f347bd2714000b5c0b18a94000142ae00025004000850a02eb6c83b72e345330eeadc437dc0c6057c4af869282b7624245e259103db5712a9bb7f4057c023141384eabc4b60f6dd953f595354e7371e4002609308a4b921c37e655c1787f6ce00d3bcadf40000b6bc00013f3903ff085c304328a0d267882d7a1de3b00c25d3fda7677fffc154be3e903a721fe80514627b5d4f1180bfb50354a3e6e828f980197bc0000b9f90001a37d2decbcb8c6d537857ed4cc5e4d2d879a0ee2a2fc07ec0b5dfdd610040db6195fd42d395e2f2516b86d70c9e15a8d0cea9d1b105813ad86880006593c1804d73bb504cce018d3ad0581458c1e2bf22d13c044d604a40173f29700705f8bacfe1206390efe27ef1a83eaba78228a841667360555afe35290c7c878b8b35c029799c00295ed3465c0d9a4eb62012003a45c000184e4000537b8001228a00040548000e0bb0002f264000000414e4d461c110000000000420000e5040017030064000000414c5048ae000000010f30ff1111c271db469254996dfe51f9d3aeea4d0802eeb9af5544ff2740bbf6775dc57af788e9f91fffbb96e49d7c49dec98f937f8b49f23ffef7d5d3b15fc77103e5f91ffffb5659e317a43affe37f7705752cc9d7645f26a23d43b49710ddf91fffbbbad678412abfa54c3822dafc8fff7da4c4f4899857be345096fff1bf3fef3695fff1bffb892cffe37fdf2ef24e75fec7ff2840c744acb8cafc8fff7dd5f8d71abf0dd1e67ffcefe2d9b5d715565038204e100000304b019d012ae60418033e9d4ea14cbfb0a3a224dc981bf01389676efc7c998bd55fce7b28601ffd57d90efa01f801f85781fedfc0cb7e5ff90bfbffb747ce7fa5ff0ffed7fd63f68b9143bf78653fc1fe7de867f8af4807f9bfe33fa87efff9c07e83fea8fbe07f0ffd33ec00fec3fc5bffffb4dff55ffffee01e801fc03fcefffff590fd86f821fed3fefbd22bffff67574eff433fabfd9dfd6ce66dafbd477f8a54fff4fedabff3daa8d46a83ef94c0580b01602c0580b01602c0580b01601d7260f7e6becb831b9fc0b01602bee46f59726327fd55bc2a33e03b2f53be78f95a03aaa77cf1f2b3e254e6d6b86980b01602c0580b01602c0580b01602aec20727c5a721a602c0580b01602c055d841054e6d6b86980b01602c0580b01602c0580b0157610393e2d390d301602c0580b01602aec2082a736b5c34c0580b01602c0580b01602c039206480dda2109c44de5602c038ec1906044cfec0b9f43cc405ccf08be414d08025383c05ccf08671ae5007554ef3fac0850aa9182754ef9e1204d8c4260a9cdad70d301601e34ea82a545ae1a561e4a093fe7b7aa5c2cad01d553be78f95a03aaa77cf1f2b4074a246980abb081bc8914dce047bfc49d7169c86980b00f1a754152a2d70d3015f7ba368a602c0580b01602c057dee7023ddcf8d3a62038f9054e6d6b86980abb0820a9c3e6f2980b00f1ac05bca602c0580b01602c03c69d31006e9a62369eb9b845f20a9cdad70a6e7054e675794c0580abb0b0be5301602c0580b01602aec206c65b6ed23850491e187e9bc1a7ab86980b01602c057dee7054e675794c0580ab02832cbf7abee6985e967f618b82e1612a218dd54e6cf7b8be5301601e34ea82a736aff057b8b4e434c0580b00f1a754152a2d70d301602c0580afbdce047bba602c058078d3aa0a9cdabfc15ee2d390d301602c03c69d5054a8b5c34c0580b01602bef73811eee980b01601e34ea82a736aff057b8b4e434c0580b00f1a754152a2d70d301602c0580afbdce047bba56e3c9ac93d4ef9e3e567378f8957bc192acf6e6fcf1bf67f8dfb3fc6fd9fe27ee7580b01602c0580afbdce0a9cceaf2980b01602c0580ab3ebf80fe1e9b510b214f77aaf088b93f4fe8c6e40fd72bd8cf08be41462ce580afbdd1b45301602c0580b00f1a754152a2d70d301602c0580afbf72a9df3c6e0b29fe37ecee4de117c82a5219cb015f7ba368a602c0580b01604d08021e34ea82a545ae1a602c0580b01602c0580b01602c0580afc7339602bef746d14c0580b01602c03c69d5054a8b5c34c0580b01602c0580b015f73df1f2b3845a5f20a31672c057dee8da2980b01602c058078b6cb1c3018c2a07b9b5addf81602bf22e3fc95050dd1f8078a646f780433e415290ce580afbdd1b45301602c0580b00f1b07e4609592a0a9cdad70d301602c0580ac701602aec2082a70ccc4b5c33e3580b794c0580b01602aec207106d70d301602c0580b01602c0580b01602bef7382a73374c4b5c33e3580b794c0580b01602aec207106d70d301602c0580b01602c0580b0156e46d6ae6fec31624bc37c6f08b8a9895e64275fd862ca152c863bdc5a721a602c058078d7f63ef0750ccad01c75f614f1054e6d5e642f937845f20a1486bd78f95a04437981f4973716672aec21ff91c95054e6d6b86980afbdd52bf633526bb175d734de117c811eff1275c5a71d34c4be41539b5ae157339576103791234c0580b01602c055d841054e6d6b86980afbdd1b45301602aec2082a736b5c34bf1cce55d840de448d301602c0580b015761617ca602c0580b015761617ca602c03c69d4dc0fface4ffd18dd553be78f95a03aaa7ad680eaa9eb5a03aa9f2a7a37dccc46e86ed1084ffd18dd553be78f95a038d2eadda2108cf7b46ca30cf08be40765a2ccc90dd1ba3746e8dd1b3de6be42d9532f9e3e4f774c0558afc31b8c50494d0802ffa46826eb26eb26eb2673f53572a9df3c6e09029b9d034fa34fa34fa1f816dc3b876bfbf21a602bedcd1deb901f601af269f731793f27e4fc9f93f27e3c8ae406bad70d301602c0580b015760ac6c6b5c34c0580b01602b1c03996ad0b882a736b5c34c056060cdc22f9054e6d6b86980b015f7ba0f2506f08be41539b5ae1a041b097d1dd1a602c0580b01601f59cb01602c0580b01602c0580afb02eee598dbc0b01602c0580b015f98e54f411d5c34c0580b01602bed40db7d432b40755d6c0580b01602c0580b0156b8e8f4c5c18dd553be78f95a03aaa77cf1f2b407554ef9e3e5680eaa9df3c7cad01d54e8941a8d18d4ef9e3e5680eaa9df3c7cad01d553be78f95a03aa9dbf4d989d35fd68a602c0580b01602c0580b01602c0580b01602c0580b01602c0580b01602c0580b01602c0580b01602c0580b01602c0580b01602c0580b01602c0580b01602c0580b01602c0580b01602c0580b01602c0580b01602c0580b01602c0580b01602c0580b01602c0580b01565be55b5ae1a602c0580b01602c0580b01602c0580b01602c0580b01602c0580b01602c0580ab7f330eaa9e6b539b5ae1a602c0580b01602c0580b01602c0580b01602c0580b01602c0580b015f7b9c1539b5ae1a602c0580b01602c0580b01602c0580b01602c0580b01602c0580b01602bef7382a736b5c34c0580ab24e59a03aa9b23b2b38e3b064750cace3074d0802abe4a9facc6eaa9df3c7cad01d553be78f95a03aaa77cf1f2b407554ef9e3e5680eaa9df3c7cad01d553be78f95a03aaa77cf1f2b4074a246980b01602c03a60e6bb49ffa2f8b8e3f61b1f7585ffd17be81e57b293d7e9e0fb00fb00fb00fb00fb00fb00fb00fb00fb00fb00fb00fb00fb00facf79e2d390d301602aec2082a736b5c34b03088a768f7ae2cad1ef5c595a3deb8b2b47bd716568f7ae2cad159cb01602c0580afbdce0a9cdad70d2fc50d08595a3deb8b2b47bd716568f7ae2cad1ef5c595a3deb8b2b13a46980b01602c03c69d5054e6d6b867eb3cd316568f7ae2cad1ef5c595a3deb8b2b47bd716568f7ae2c9bcf169c86980b01562c73c005049ff2ee4a41a7c7cace2fe5ed5225c1d5e4b4ded6dc3b8770ee1dc3b8770ee1dc3b8770ee1dc3b8770ee1dc3b8770ee1dc3b8770ee1dc3b8770ee1dc3b8770ee1dc3b8770ee1dc3b87641ca6058e1dc3b802a82a736b5c34c0580b015761617ca5f98e67845f20a9cdad70d301602c0580b01602aec2082a736b5c34c0580b01602bef746d14c0580b01602c0580b01602c0580b01602c0580afbdce0a9cdad70d301602c0580b00f1ac05bca602c0580b01602c0580b01602c0580b01602c03c69d5054e6d6b86980b01602c05583b5dda980d96c353e29edae3dc7b8f71ee3dc7b8f71ee3dc7b8f71ee3dc7b8f71ee3dc7b8f71ee3dc7b8f71ee3dc7b8f71ee3dc7b8f71ee3d4d0802c7b8f71ee3dc7b8f714ef0e69cdcaea77cf1f8ae2d390d301602c055e2a36f959e08681cd871c9cae4d46bfadec2dec2dec2dec2dec2dec2dec2dec2dec2dec2dec26033dc401c34c0580b01602c0580b015f7b9c09a5681b64a82a736b5c34c0580b01602c0580b015f7b9c08f774c0580b01602c0580b01602aec2070380f56a82a736b5c34c0580b01602c0580b01602aec206f2246980b01602c0580b015f6d20b2bb76883490fa95a06d92a04e299ed47bc6fd9fe2bcbf119b5e9c86980b0157e65132509ef67f8d76faae12e6257d694127fe202d91de2be0000fedee5dfef93cc1553d8993fcda1075de0348fa2b8ff0280ae60ab94ef92e63f8c9b0e012b6589f4156021dd48084190789598ec1535d95adb83fabbb5634c39ea26cf7435cf8e47e3774d7a951f129457ed6ae9c5b6751e355868356afcea405572b4af73a0000a780000009e800299419ec98da0500aecd83ca48169c3ae50783c4d84455a691cff9aa9fdd1559dbec6a86e1bc6b856f27f03c3d947f1ce76875ef133f851b50b7137a1fdb84932f4123b3c7ef1892c389607f3c04cbe4b061a102eaac058032223d001979f51d9e3f78c69095666ca33d1c3da35ab97db4fd787fd3f28b5a6e260d19209e1ff936f55430bd9b4559c54ddcad34582e3b9d434822490d3b91b61afa3f494ac22394a9458d22527828afbb1e41aac82780bb8005fe20c381533084822bc4d08022f7de005d95c4dbedaaedf0164ac5dc84881174785efbc00ba8de5a99d9398f58ab75c3b2d0c91b9192ac19a53bca8697db345461c2f221adc8bcc07992b4256155683300820444024a13b48fbb87d0ad7fb23aa7af359c0c849d2a56b62fffad934965d9e19bb07203f5309af85d757e37f6b1b764f449af87dc94834c29c810045dacc6b559c54dfe78307524d1e79cd3f2a1db8f304ad42fe742c69666a6b2a56f872526fd21ff6bd98330fd05888eceec7feeae9ca060a3f6520b15aff64754f5e569317560a0e5b1e7d606441ca448804b422c016057eae9bdf675202ab3357f82a9c7a70a264399794d3fd8f058519a503d59f37bb516ccf81aa85feca536e692b83d02bb36082494605bbfca42c842843938118bc64f600017e3ec003af84c393db006444761ce35516dd0e20734bbf9130fcb3273fab5e340ab3b33255d0b82df2e09c5e9ef9741c0ff589e1898a239f2e56ba7ae82fed8fa65391d4dadf68fcbf8bf0376317f9db2a9fcefb58e5a7fed92f33962b96cdb0d94c16ee1be9f075cf7aa2269e7d761600c888f67d2cc71b27ae1de7ac50e2a2c85946d3d61d15c60ef1005a15db66aa65a0c3c16e59a9fb4d5187f4f55566e58728b033c2a3cac5980f72e6a3a822c76d54dea9421efd9087be86051d003147db982bdf14653da8af400ae80134a02b0062fcfd6110684ac6720609b2b2c3c743d4602e5cf00167cbcbc59a4d08023cfca819f83cf4c62dd0f9dc98a44af47c8cf372db69e9b91cbb1a08eaedcca3c9e403de802d9a27db0ed4d8f7eeb86a90a8931ace75d904368717de1e0ce2d0840abf5c7c876736b7c96f02921f46817e5df6a85016328e6bb68f9540fd23ac19fe80af9c866019530118500e5d8d0475778ad9de49788666e8b00aa4086a73046215f345745eb945a16de2b6aa2fdbd9b9a362c2aa9d48110515e800012f78000001c9500009c5d55d9f06e307734b8592792973f441ee1f988e2767abb2f484a60327500a009f782cd9cd55b450acd2436b78e6b4cbe2f9e8fdaddba80e7058eb778a757b5bb7400000000000000010f7840791e8134d84454f80001d9af6b03a86fb29928af40000061a8af40000017784b32d412d32a87a8db0bb39b5be4b77efd6dc41b81402bb360efb1b0a1c7a38d3a8edbf6570cdfc1a3b9014d78f95671caea1c66c0004612ac86ad1f7f7c7e47d3dce6dc05f166e572936176930e52597c5d4817d14162779d349ffb73e339f3a545f3db5aa7e7ac1a5b5a2670d05e252ea01b4506a75a41a9d6906a75a41a9d65337dc633c000000047697a00000008ed2f40000001c7b5773f0aedbfc4b4f6a4202cfe76f8822de31d68778fc7f572f9c607c744d8d81f888c3839bf4feda8311aeee4efb0399f0be5a98b2eb6c20000000007b3f667bbb4d2ad090815a0626d6c17f55174b3ff08e768979d8106de27283e1fcb04d08020001f63803eeee0000000000001a7c889f7a75967bdbf15b1616e4551bf834771f858991e4d78f95671e36b97dff90584a969e889b1796cc001f899680fde4fc2a99147be92cf5e0ff992ef8be3686a65a79e1360fa20000003840000cb80019ebd07cf91035848a5d450dd90fb79795e8b23da33399457b7a9216edf2d203ac4c29f2a516d9c66888fb9be44009914a4ecb2e0b7bf515fe76e569e84160d6be73fbc7dcd3de2136012b6589e0000000414e4d4608100000210000000000a304009b030064000000414c5048b7000000010f30ff11118261db368eb2c18fe4d5bdd9dd177fef1fbc23fa3f01e5943629016237b902c42015626efa7ffedfe14ccf4f104bfb7ffe5fd63323156290fe9fff773ee6475a8218a4ffe7ff65452c12d3106bf7fffcbf64a548ecc88ae1fff97fe9148bf4fffcbf1c8645220b41ccbd8115733e4dffcfffcb61a6b348ac874562ca822cd2fff3fffebd1c2afd3fffef74b048ffcfff4b5d582426b148f87ffedf4f9c63f7fffcbfe486452201d2f4fffcbf5e39a5cf0f0056503820300f0000505b019d012aa4049c033e9d4ea04c3fa4a32224ff2813f01389676ee1767f02ff5f3289fd7f7e85f8a9fbffb865ca7f16ff2539164841fd5fee9bdd97f3df66fe601fecbf8f7afff451fc03d007f20fe49fa7def3bfc03f457f8076807f5cff1deb15fd83d803d003f807f2fffffffffda0bf6fbe067f774d08027d9e3f603afd3a6ffa4dfdd7da27202cb2b23ff4ff4ccff1579fe13839a4897ba50b8755c34c1cd244bdd28483f994b7250b8755c34c06be79e4cadd3fd3488e89e02f5ca170eab869839a4897ba50b8755c34c1cd244bdd285acbb0d244bdd285c1dd8837e4946fb3be8b0734912f74a170eab869839a4897ba50b8755c33ebe15c3aae1a60e59312d8544d82a27160e69225ee942e1d570d30734912f74a170eab8698105136bb7a2c1cd2122edd5f1b26d1841cd244bdd285c3aae1a60e69225ee942e1d570d3072c98976f45839a488c243774b230ff36f78eb2ef02923afc5e530734912f74a170eab869839a4897ba50b8755c34c1b3456736bb7a2c1cd2446fc92fa2c1cd244bdd285c3aae1a60e69225ee942e1d570d3072c98976f45839a4897ba38e5be8b0734912f74a170eab869839a4897ba50b8755c34c1ccfc5136bb7a2c1cd244bd58d234c1cd244bdd285c3aae1a60e69225ee942e1d570d3073482ebf10ee28bf1940f6e6aae40d32a736bb796061a56edcd55c839c021b9aeae41ce010dcd2ffe468700e6d76f45839a4897ba50b8755c34c1cd244bdd2849c45cd244bdd285c3aae1a60e67e289b5dbd160e69225ee942e1d570d30734912f74a16b2ec34912f74a170eab869839a4245dc34c1cd244bdd285c3aae1a60e69225ee942e1d5557c2b8755c34c1cd244bdd285c1dd885ee942e1d570d30734912f74a170eab4d0802869839a48198976f45839a488c9dd42e0ca07b7355720e70086e6b58739b5dbd160e69225ee942e1d570d30734912f749dc1bd160e69225ee47cd5ca170eab869839a4897ba50b8755c34c1cd244bdd285c3aae19f5f0ae1d570d3072c99044bdd285c3aae1a60e69225ee942e1d570d30734912f74a170ea0427ba50b8755c33ebecefa2c1cd244bdd285c3aae1a60e69225ee942e1d570cf2d5a985772ee9a220ab04e00bf0ae1d570d3072b059d7d41ce010dcd75720e70086e67dfb9106ab869839a4897ba50b8755c34c1cd2446fc929698976f45839a4897ba50b8755c34c08289b5dbd160e69225ee942e1d570d30734903312d9f312ede8b0734912f74a170eab8698105136bb7a2c1cd244bdd285c3aae1a60e69206625b3e625dbd160e69225ee942e1d570d30209d9752d813977814783cbef6e6aae41ce010dcd75720e70086e6bab90738043735d5c839c021b9aeae41ce010cf54194103e364f45f8c9f7d680c866aae41ce010dcd75720e70086e6bab90738043733ef81928a6994cd809b36026cd809b36026cd809b36026cd809b36026cd809b35ff1b56a78696774e12d14c1cd244bdd285c3aae1a60e692066526ac3c2db0b7b0b7b0b7b0b7b0b7b093e2dfffadfb3fd9fecff67fb3fd9fecff67fb3fd9f9d2245839a4897ba50b8755c34c1cd244bd58d234c1cd244bdd285c3aadcd00e048b0734912f74a170eab8698394d0802a4897ba50b8755c33ebe15c3aae1a60e69225ee93a8bc29c5839a4897ba50b8755c34c1cd244bdd285c3aae1a58a73cdaede8b0734912f74a11d665d2c1cacf00aff21a60e69225ee942e1d570d30734912f7471cb7d160e69225ee942e1d551fa0fd942d6ac0912f74a170eab869839a4897ba50b8755c33ebe15c3aae1a60e69225ee93a8bc29c5839a4897ba50b8755c34c1cd244bdd285c3aae1a58a73cdaede8b0734912f74a11d665d2c1cd244bdd285c3aae19e5173db9aea5ce68439355720e70086e6bab90738043735d5c839c021b9a2f700a890e19380433a44a15c83963b71db9aea55beb4e010dcd75720e58ee250437341cf8f56ab869809679cb23103a1eede8b0733f144ca78ada29d1190319031903190319021df06e6bab5c7380436e3a7c839b7c1b9aeae41ce010da212de6d160e67ea7064fb399311eedcd244bdc8f9251be15c3aae1a60e69225ee942e1d570d30734903312ede8b0734911bf24be8b0734903312d8544e2c1cd244bdd285c3aae1a60e69225ee9424e22e69225ee942d65d869225ee9424e22d9a36960e69225ee942e1d570d30734912f74a16b2ec34912f74a170775b6b8ca07b7355720e70086e6bab5c5600436ebc578ad23c6503db9aab90738043735d5c839c021b9aeae41ce010dcd7571f84384a7b73555c69706503db9aab90560fc67e13ef4f66fa8fd0be63f19f84fbd3d71babf1302294d0802c3112a0a9cdaede8b072ba92738b9a4897ba50b8755c34b14e785ee9d8044cd3206320632063205f0537285ee942e1d570d30734901524e71734912f74a170eab869629cf0bdd3aa0a9cdaede8b072d198976f45839a4897ba50b874f78552c1aae1a60e69225ee942d65d85a3320897ba50b8755c34b25312ede8b0734912f74a170e9f45a654b6f5206dcd75720e70086e6bab90738043735d5c839c021b9aeae41cb841ffbd6ab9072f92feecfb54941ce010dcd75720e70086e6bab90738043735d4f7d5a0b576f624f266aae41d95c3aae1a60e69225ee942e1d570d30734912f74a170eab869839a4897ba50b8755c34c1cd244bdd285c3aae1a60e69225ee942e1d570d30734912f74a170eab869839a4897ba50b8755c34c1cd244bdd285c3aae1a60e69225ee942e1d570d30734912f74a170eab869839a4897ba50b8755c34c1cd244bdd285c3aae1a60e69225ee942e1d56e75c341542dc9cd55c83b2b8755c34c1cd244bdd285c3aae1a60e69225ee942e1d570d30734912f74a16b2ec34912f74a170eab869839a4897ba50b8755c34c1cd244bdd285c3aae1a60e69225bdcb7d160e69225ee942e1d570d30734912f74a170eab869839a4897ba50b8755c34c1b3456736bb7a2c057791c5e059154a4a7a57658ab04e010dcd12971845ee6c5c15f804168c40e5c1940f6e6aae41ce010dcd75720e70086e6bab90738043735d5c4d0802839c021b9aeae41ce010dcd75720e70086e6ba9ac70b67e50bdd285c3872812a0a9cdaede60a654e6d76f45839a4897ba50b8755c34c1cd244bd6098976f45839a4245dc34c1cd244bd58d234c1cd244bdd285c3aae1a60e69225ee942e1d41ee9d5054e6d76e367dc3aae1a60e67e289b5dbd160e69225ee942e1d570d30734912f749dfd357942e1d570a11679a921706503db9aab90738043735d5c839bfff33e6b117658ab04e010dcd75720e70086e6bab90738043735d5c839c021b9aeae41ce010dcd75720e70086e6bab90738043735d4f7d59a1121225ee942e1d570d30734912f74a170eab869839a4897ba50b8755c34c1cd244bdc9677285c3aae1a60e69225ee942e1d570d30734912f74a170eab869839a4897ba50b870e502541539b5dbd160e69225ee942e1d570d30734912f74a170eab869839a4897ba509388b9a4897ba50b8755c34c1af5c3cc207b72dbe063977816462072e0ca07b7355720e70086e6bab90738043735d5c839c021b9aeae41ce010dcd75720e70086e6bab90737ff7c96be6fa33997de059188b72f74a170eab86983668aca1e4bb7a2c1cd244bdd285c3aae1a60e69225ee942d6a5ba05915b1d4742245839a4897ba50b83bb106fcd5ca170eab869839a4897ba50b8755c34c1cd244bdd1c72df45839a4897ba50b83bb106fcd5ca170eab869839a4897ba50b8755c34c1cd244bdd1c72df45839a4d08024897ba3505b94e010da1bcc15138b072d1273e13ef4f65e88bea17ba50b87506daed3fa8fd0be6143dd6ed644d835b6ea9aab9005ff84fbd3d543b4645c14d69618ea000feff3e2585a092ea1c2ddfe9dd2a0d4a5b73917ab8752cbb2c80765432ce1304d264271e15c5409cdb8eae93990a372a3596569a570c5b74d6bee50262a66dc02112f9f7f7901f0ca30dc8950c02013e918aa906000016800302010a540338e56a1731fab328515b3132709448e5c975d6597456cef85f89ebf299446ed4e07431c0b215ffbbaac52920b3f608a4cb00083001230027c05066dc75cce19b8d296bd8e20c25c9385679a624bf2257e4f802674e597f5986d6c28000b7c630000000000215169cc4be7dfde407c328c3600000000000000b4546a5b2ecb201ee74a0bca93519842ee0e6e820c8eb68627d04dcaf0a034016b0afe5b3398000000000165b79ab70f2cf16806e0ae3dfe2dad1d1ffed8d2e794643dd19230e2df3c9a89456c2f3fa9876108ac69932e280a930b2b47ed284db1ad311100e9edb72e4c8600efe9997e21275102cc4a0225c92f60dd3b58048c0b8e913a8817f87296cbdc63208ef700292400e7bd8dc98a4eb7f2d9ad03e2208ca45551af43b436218cfd3006d26c0000000000000e500122001ab5ad79e97bb6edcadba76b67391ab3db7bda58466375e86d41000e90004d8006cf5dac3e052e0efdfb56706798b17b58134994d0802d133ef5054f350c648eaa1008df55342039a9d49bf3c647c26536a6ded742725a2c576f21630a0ffc9a62894d0c2f6221408fb2a34692f0c3cddbc179f994794b731526ea3d15a8693b81774c8ee630cf4a536c94c1cfdc39557fdba69863476378ca6d8fa42ddd7840975ee5b64d2b0baad8a92f634a4cf0a0979f51eb8bb83699c424f59c906943171321afdc88e10af0b98f7fdaab40a3e39009aa98b524a9cc5a26b09052a58aa30e1114008c6f0c8153be70de4f480000000000021bfdaa5bdf3a341cb726a8c366ed26af3cbb2c752db0341c0e4c5211019afd77dee6e1a17006136207afebf315fcff0123f951dbe70ded3cb3a406e87e6b490b300c2b31280777a8c79a8c3601270002e6009c86e279fbbf9c636db91ff845962a47e175f65830d75000243be742d4f8dce6e19e962079d6ef6c69754fb961a3fea4beeb595c5258bc50e1b970480fcb484b0c0000000000000021b1cdadd0fcd13411f8710877e4c24fc6ec6ee85df380b11800079b60000000316d7d8627869585d56c549777a5ed7177dfc1f9238be27bf76b6cb5136de7428c0faee85a6a59eead1ae235694d77a5693a48d71ad5dbfed44db85fb349a77f4667ffcc489f920e343ffe54c45d5b80ee09877ceb7e49e937a6400aa5ca95686d0ba9c0556772bf1a705cc004a400a200baadf927b0828ce1d6fc93bcab197e7ef3018d9bd0000000000002aaec51ba24d080232324e00b32b7e49ecab197e8c8206968136165a5c5c49bb60cfdad0bca95a00a8da63066049a01401379dcb8a26290ae00082803ba6907745072f7fc573ffce3ac7f7649518eb37c0f22b95311681105f058278b59660e77b94ce17893b34c5f6f61f6b7f5af9ff8cd7854db7a487a777700fb8e86e6e773adeeb73cde80000414e4d46f2130000210000000000690500dd030064000000414c5048e3000000010f30ff1111c27124b971832a07b6a96f66f80024755f3037a2ff13e0e7fa57c1c076970360bb49fd4fff2be6e8298aa946ea7ffadf74dc1eea7ffa9ffea7ffe97f7f31e829b6434f61a3e929127a8a2e7a0afd4fff9bd9f21447b4c58ae65899c142f6c162ea7ffa5fb9b762fd4fff9b44a36fb884811def12b090036021f53ffd6fea8b9e623df4143d8dec5ba1fea7ffe97f87b2cb03602107c042ea7ffa5fb9b742fd4fff9be1a2a748e8ddd0fff43ffdef620c6c80edaffea7ffcd96e5a8ae721e022ce4005848fd4fff2bf75658eaec72032c641f2ca6fea7ff955a7eae3f330056503820ee120000f0bb019d012a6a05de033e9d4e9e4cbfa4a2a224bda823f01389676efc7c99c0e27fd55d3c45ae549fdc57e4ff8c5fbf5b8bdccbf1eff9b7eca72251a6fd317d8fee1fde67f45e900ff5ffc77a4f7980fd22fd4ef7a7fe41fa35ee03ff37a807f86febbeb11fd8fffffba07a007f00fe5dffffd617f6c3e0bbf777d94d0802c7f59bffaeb7ff81bfa67d8a7d41e42f0ddfc6b7ee723ff4ff87ff933fe8b565e8de084aede8b01602c0580b01602c0580b015f71fcd04b4d5d3ef29d579c4a82a736b59efa131164084d471d38a9f254bf4f77c315d4084d4cd9b5ff52131e2559d17a862ba8109a99b36bfea4260a285c3a721a602c0580b01602c0580b01602aee092f74a170e9c86980b01602c0580b01602c0580abb824bdd285c3a721a602c0580b01602c0580afbed2e097ba50b874e434c0580b01602c0580b01602c03c98465ee942e1d390d301602c0580b01602c055daf73874e434c0580b01602c0580b01602c0580b015f7e02f42f74a170e9c86980b01602c0580b01601c195aa194d2648109a95b75415283189fd484be44c4a0d8a66cdaffa9098f12ace8bd4315d4084d4cd9b5ff52131e2559d17a862ba7fedbe8b01602c0580b01602c0580b01602c0580b015770497ba3c218219e8b01602c0580b01602c0580b01602c0580b01602c0580b01602c0580b01602c055dc125ee8f0860867a2c0580b01602c0580b01602c0580b01602c0580b01602c0580b01602c0580b015770497ba3c218219e8b01602c0580b01602c0580b01602c0580b01602c0580b01602c0580b01602c055dc125ee94672863940661c8109a99b36bfea4263c4ab3a2f50c575021353366d7fd484c78956745ea17742b874e434c0580b01602c0580b01602c0580b015770497ba54d08020b88876bb7a2c0580b01602c0580afbf017a17ba50b874e434c0580b01602c0580b01602befc05e85ee942e1d390d301602c0580b01602c03c98465ee942e1d390d301602c0580b01602c0580b00f261197ba50b874e434c0580b01602c0580b015770497ba50b874e434c0580b01602c0580b01602c055dc125ee942e1d390d301602c0580b01602c057df80bd0bdd285c3a721a602c0580b01602c0580b015f7e02f42f74a170e9c86980b01602c0580b01601e4c232f74a170e9c86980b01602c0580b01602c058079308cbdd285c3a721a602c0580b01602c0580abb824bdd285c3a721a602c0580b01602c0580b01602aee092f74a170e9c86980b01602c0580b01602befc05e85ea231159098f12ace8bd4315d4084d4cd9b5ff52131e2559d17a862ba8109a99b36bfea4263c4ab3953717c5750212430cf0cbdd284b847baad9b5ff52131e2559d17a862ba8109a99b3675d2584133a304d4cd8bf2a73673c18fb8663c818c818c818c818c818c818c818c818c19fc1d3728783284217ba50b8339311234c0580b015f7e02e736ebf8ae1d374b4569269e3064ff04d715a3e09ae2b47c135c0bea625ddfc79276f4580afb5665d2c0580b01602aee091b67dc3a721a576bdce1df2826b8ad1f04d715a3e09ae2b47c12d7d4c4bbbf8f24ede8b015f6accba580b01602c055dc1236d183ee6d4b6047f5212de3b4a2041e78ece882916324d0802ecfecea47d9fd9fd9fd9fd9fd9fd9fd9fd9fd9fd9fd9fd9fd9fd9fd9fd9fd9fd9fd9fd9fd9fd9fd9fd9fd9fd69a512d5254dca9ea18abc90379a0380f522f50c575021353366d7f8e29bb013ce8bbd1f717e3dd285c3a3dc6ad415162a3d5ced4e77982797931e0dee02c7747e98f12d1d5a2bad9d8c718e31c638c718e31c638c718e31c638c718e31c638c718e31c638c718e31c638c718e31c638c718e31c4d4b76ef408518f966332bcd7b9fa3f84757f18ecbe5fdc0d2d21351865cbbd1602c057df8403b218446ab76f6f368ac3fc135c568f826b8ad1f04d715a3e09ae2b47ddaa0a9cdaede8b01601e4c232f74a17073b6f17048db3ee1d390ac9f9c568f826b8ad1f04d715a3e09ae2b47c135c568f826b8ad1f04d715a3b94095054e6d6be02e736e98a2b39b5db93fc135c568f826b8ad1f04d715a3e09ae2b47c135c568f826b8ad1f04d7155f3cf0cbdd2849392d47851fd2ec84c7837b8e841e19e197ac78c193fc135c568f826b8ad1f04d715a3e09ae2b47c135c568f826b8ad1ef5d0ae1d390d2bb824bde19695c5e6f43fe09ae2b47c135c568f826b8ad1f04d715a3e09ae2b47c135c568f826b8ad04c232f74a17073b6f27f828e170e9c771f04d715a3e09ae2b47c135c568f826b8ad1f04d715a3e09ae2b47c135c557cf3c32f74a124e4b693fc294a170e9268f826b8ad1f04d715a3e09ae2b47c135c568f826b8ad1f4d080204d715a3dca2d28bbd020f3c767cc619e197ba4ed176ee3e06470915ad3f13fffd650146568f826b8ad1f04d715a3e09ae2b47c135c568f826b8ad1f04d715a3de51e2db26886b2826b8ad1f040fca94b18b8741ef1009ae2b47c135c568f826b8ad1f04d715a3e09ae2b781602c0580b015f8ab79dd0f7ae65c568f826b87e6e7fe099b3ee1c34327f826b8ad1f04d715a3e09ae2b47c135c568f852942e1d390d301580f48373ec1a1715a3e09ae1f9b9ff8266cfb870d0c9fe09ae2b47c135c568f826b8ad1f04d715a3e14a50b874e434c05603d20dcfb0685c568f826b87e6e7fe099b3ee0dc8c4ab3a2f50c575021353366d7fd484c78956745ea15247dcd7791f8f826b8ad1f04d715a3dd2e6cce3a9774ef0deb9e43dabcd7b81b30e3c37a8c2bb83e942e1d38e9f3cf0cb7b07fd484c7afe942e1d390d301601e4c8b5ba1fe09ae2b47c135c568f7ae852a6c24be83de83de762ac60b7a2c057df80ba338bcde8b01602c0580b01602befc108630c49f9c568f826b8ad1f040fca2d175225ee94254d97fc86980afbf01746715bc0b01602c0580b01602c03c9916b743fc135c568f826b8ad1ef5d0a54d97fc86980ac07a717a17ba5092725b49fe14a50b874e434c0580b015f64180c98a8218956745e8e0aea0426a66cdaffa9098f12ace8bd4315d4084d4cd56488f8e709a99ac14fc3d4588ce8aea0426a66cdaffa9098f08bc454d0802e42779c568f826b0810eb709a99ad1417dbeb4b246c84c78968e736bb7a2c0580b01602c0580b01602c0580b01602c0580b01602befc10860c9fe09ae1f9bae2bfb9a8bb86980b01602c0580b01602c0580b01602c0580b01602c0580b01602c03c9916b47c135c568261115cb459db7d1602c0580b01602c0580b01602c0580b01602c0580b01602c0580b00f2645ad1f04d715a0997f0f2f8ee502541539b5dbd1602c0580b01602c0580b01602c0580b01602c0580b0157708a864ff04d7155f55fe764260ceb4150213533c7285c3a721a602c0580b01602c0580b01602c0580b01602c0580b00f2645ad1f04d715a0997f0f2f94217ba50b874e434c0580b01602c0580b01602c0580b01602c0580b01602aee1150c9fe09ae2abeabfcec850842f74a170e9c86980b01602c0580b01602c0580b01602c0580b01602c03c9916b47c135c568265fc3cbe3eed5054e6d75fd7ec355d4083cb9f573ef0e46b9b2fc6815cc78953882eafbc856cda5846b56a66cdaffa9098f12ace8bd4315d4084d4cd9b5ff52131e2559d17a862ba8109a99b36bfea4263c4a983e226189935c568f8266d182edfdce7d76f4580afbf017a17ba50b874879ba602c0580b01602c0580b01602c05807d19044bdd285c1cef0ad081a1d1602c0580abb824b85715a3e09ae1f97c7c135c568f826b8ad1f04d715a3e09ae2b47bfc357b874e434aee155530783964d0802570e9c8695dc125c2b8ad1f04d70fcbe3e09ae2b47c135c568f826b8ad1f04d715a3dfe1abdc3a721a5770aaa983c1cb2b874e434ac5c10105a8ed9b5c752131e06fdc8834e428068f7ef84757f18ecbe5fdcfd1fc23abf8c765f2fee7e8fe11d5fc63b2f97f73f47f08eafe31d97cbfb9fa3f84757f18ecbe5fdcfd1fbcc988eb33ca09ae2b47c10404d7dfb7e715a5085ee942e1d390a780bd0774970e9c86980b01602c0580b01602c0580792e4135c568f8266d182edfdce7d76f4580b01602c055dc125eb1e3064ff04d715a3e09ae2b47c135c568f826b8ad1dca55a3e09ae2b47729f34f92d0a5285c3a721a602c055dc125eb1e3064ff04d715a3e09ae2b47c135c568f826b8ad1dca55a3e09ae2b47729f34f92d0a5285c3a721a602c055dc123d278480879af73f47f08eafe31d97cbfb9fa3f84757f18ecbe5fdcfd1fc23abf8c765f2fee7e8fe11d5fc63b2f97f73f47f08eafe31d97c77616fd27982a736bb7170aaa983c14ebb3f45ea18bd6a736bb7a2c055e70575021241e0acaf4bb7a2c0580b01602c0580b01602c057e19b861120f826b8ad1ef5d674583c186dd5054e6d76f4580b01601e4b9036179a64d715a3e09ae2b47c135c568f826b8ad1f04d1dcfb0d1f04d715a3b94f9a7c967ae8570e9c86980b01602c055dbfb98e855816327f826b8ad1f04d715a3e09ae2b47c135c4904bf94135c568f820809afbf6fbb34d0802ee1d390d301602c0580afb5762e9f8d3366cfbb196b34135c568f826b8ad1f04d715a3e09ae2b47c134773ec347c135c568ee53e64c0f8abb66d7f9e85fbe90366d7fd52c0580b01602c0580abce0aea0425a66cdaffa9098f12ace8bd4315d4084d4cd9b5ff52131e2559d17a862ba8109a99b36bfea4263c4ab39b304ce34791f04d715a3b94f999d8705c8b3fcc32b539b5dbd1602c0580b01602c0580b01602c0580b01602c0580b0157708a864ff04d7155f55fe76de2e092f74a170e9c86980b01602c0580b01602c0580b01602c0580b01602c03c9916b47c135c568265fc3cdcf9308cbdd285c3a71cab962d59d17751104ab3a2f50c575020e02fa1e8bd4315d4084d4cd9b5ff52131e2559d17a862ba8109a99b36bfea4263c4ab3a2f50c5750213533581bf553b8327f826b8ad04cbf879b9f26118c0000feff3e2577b2676501a3f7a77002b67b6710830bb4db712c9962221090906d6818bc299367c6ee7dd538ad65cda49c785615f557990b7f268ea9c63a3a061b14c5d3fd89c6ad844d87d19a32f5a0079c0c000026e20007718004e6e841d3368b3fae25adf0077d11fc36482a2d48d981ad2a7a12120bae360729122c79d40bf1a7e7063e432abdfe2ca7cb0cec733b0c000022f48333e265ad67977c4cb0a0789f3000000000da8b6004575aa6a9cb17983d1affdf48c3e59078caf141227d000354200028940007a880014d08027cb0004950000e750002c260e6eec8fb5a0627fa7690dad2be0a4706da45f7599c8e8c83f10a9f4d988f8f8e81849534145fe175b974e283a7a7d3bbff6a01ba10696bc52a7d32e9fe6416e25b38b64e0174b6aa74dfd6200f9540699f0137723b9d10a9499deac54049e75379c6b5a4e313db867cd5c9a336a2490e305ddaeb55bf2c50b43861e0e048115a4a2cc41adeb8000002872bd9792fed8ccccabfd5ab04ab027085684e2a018c3261841e72478c40f92a3a0dcab9567656fa354c1e3b37e7aa36d918b21b048a3be019117b139bab7fba05cd502ee5d7aeba53dd9278b3bfe49cd04bd423d12e47e8d046364e615405fc0b42b1168539165bf7be6735c1aae0885f16e0368a15e0afdf0d8c5ec876fc148c906493cf9ae55101d0f01a39b9a3cbbb27efcbfa002eea99835228314bac20027d4c13f404f16ae922e2600000145000fc0065335fd1f7ade88ff0dcf50c45568aa0c27a007770115ee000ab0009088eadbea8d0bf8bee3e3cbe5c05eaedcfdd05bea0fbae0e489625fc6554bcf880c4f27fdf87851176493e45f153aa55ed6b40a224c5a65415f31277ea8ad1c7445ba8133fd8a7157fb57bf7f40b6d929c093d5c2801cd4c10d699f33ca54b40075ac0065a800c0121fed52dee83821ef72ecaa9d20000236080acf2658882253f7c4b6fbdbb0635fb9e5302a17854470773e580a8831fb53fce0ee13afcda300513a8b64d08022091ddb343feb526d3a0758b6030998ead553a4478642220004840063eb9963dd071c5878cc884b4dc8bab8826477a3527d4aefca267277f9341c40000053e5d3898b83b9e986e60e878e0e2dcc5fd1982ec05611b039a79bc1e7bc5b9d4db835eb6b564953447657dbf57ab0096aeaec53170f68da62f8aa80f005eb395e60dbde5ccd18d44dd76f83d16fe111ebef177d3e4dab1faa89c0751277abcafb300fe2094b40041bbf553c914f4000ce0000ad0004c66ab93cc59e2776c0096e9218009f00043201cd04b8ccea4a96206d9fafd410141e53397d84690405d4a2ddf245b5ddb4839b050ab918f954b4c09262e0d6a1f9603e07847868202ea516ef922db5ecbdaf81883839041fd30028e92373957deefb4b251ca72ff6d56c3f7a48230e5da6c317076056bbe70c779782c8f269d12bc48d190c09f1a00481300b5abef77f9a71a78b73050d28c83f3caaf3223a95a37e03e3c005cc65b63ddcc199ca944f7a4150e3050f17a43c03f81582941e7374557deeff54eec23fd0d06f05fa5f134cfca6d4c7c2d624ef6395f660200d249fb9c27977c4cb5acf2ef8996b7cb620208e5f13cf4799200450ea1257c0c82548208f26ea15a1c64aeb5f23f06d72b1839c8dd7c4960fb95128496842ef07d3cbeb04cade35d985bdb311f22e13dc92f9c931a5767d5803797bb46df93cd1d3f2cd050000000000008bcc5cb633fc7e19eba5dab4d0802db000bb000084222080ae60a92786ad15af6c1bfe45ba861d5c855449eeea35d81d7fa76884cde300000414e4d4674130000210000000000690500dd030064000000414c5048bc000000010f30ff11110262db488ea4012eb04ebd32eb73756ecc99ef1f45f47f0272ad0f154cb471cb451ba6f81fffe37ffc8ffff13ffec7fff81fff83358f57dae07ffc6f0f2c6d14763df33ffef77f80d2f23ffeb7d5a5b4311ea58d9a27daa3d7c28bfff1bf9d2da50de395cf8f87296ddcd3a3d2f23ffeb7c5d563d1e43592118bfff1bf4a4bd9f10c94b6a489765dfc8fff957d39c02a2ada75f13ffeb7bd16ed4eb957e67ffc8fff7da2bcf81fffabb4941df33ffeb77b966bfd6706565038209812000090b8019d012a6a05de033e9d4ea04cbfa4a322245cc85bf01389676ee1769e83fe9bac07ffd6e9a0863f72df987e437effee03715fc6dfc88e426ee4e1967d73eeebdd7ff21e900ff5ffc3fd7ffa40f301fa45fa57ef3ffc2ff45bf8676807f77ff11eb49fd37d803d003f807f48ffffffffd993f66fe06bf75bd9ebf65bafffa65fa7bfd27dc1e183fa6eff1e47fe9fe9abfe76f3c41d3277c43a956f79081f35f0dbaadef2102d62d6d2bcdf9ab581916fa29267e15f0bea17137ab8251c9a47a13131493c078d128fcd5ac0c8b7d14947e6ad60645be8a4a3f356b0322df45251f9aa834b9af86dd56f79081f35f0dbaadef2103e6be1b7034d08026cb9af86dd56f79081f35f0dbaadef2103e6bbd178840f9af86dd56f79081f35f0dbaadef2103e6bdee5c10ea55bde4207cd7c36eab7bc840f9af86dc0db2e6be1b755bde4207cd7c36eab7bc840f9af86dd3d80e6be1b755bde4207cd7c36eab7bc840f9af7b97043a956f79081f35f0dbaadef2103e6be1b755bdd5fd35a956f79081f35f0dbaadef2103e6be1b74f6039af86dd56f79081f35f0dbaadef2103e6be1b755412caf86dd56f79081f35f0dbaadef2103e6be17ff4d6a55bde4207cd7c36eab7bc840f9af86dd56f78c1dbb755bde4207cd7c36eab7bc840f9af86dd5504b2be1b755bde4207cd7c36eab7bc840f9af86dd56d164e779081f35f0dbaadef2103e6be1b755bde3076edd56f79081f35f0dbaadef2103e6be1b755bde40b070207cd7c36eab7bc840f9af86dd56f79081e96d9735f0dbaadef2103e6be1b755bde4207cd7c36e9ec0735f0dbaadef2103e6be1b755bde4207cd7bdcb821d4ab7bc840f9af86dd56f79081f35f0dbaadeeafe9ad4ab7bc840f9af86dd56f79081f35f0dba7b01cd7c36eab7bc840f9af86dd56f79081f35f0dbaaa09657c36eab7bc840f9af86dd56f79081f35f0bffa6b52adef2103e6be1b755bde4207cd7c36eab7bc60eddbaadef2103e6be1b755bde4207cd7c36eaa82595f0dbaadef2103e6be1b755bde4207cd7c36eab68b273bc840f9af86dd56f79081f35f0dbaadef183b74d08026eab7bc840f9af86dd56f79081f35f0dbaadef205838103e6be1b755bde4207cd7c36eab7bc840f4b6cb9af86dd56f79081f35f0dbaadef2103e6be1b74f6039af86dd56f79081f35f0dbaadef2103e6bdee5c10ea55bde4207cd7c36eab7bc840f9af86dd56f752e2924bf2c0c8b7d14947e6ad60645be8980e50735ab581916fa2928fcd5ac0c8b7d14947e6ad60645be8a4a3f356b0320846c36eab7bc840f9af86dd56f79081f35f0dbaadefca053f8bf33628ac8c9ca2c9cef2103e6be1b755bde422e2b7bc840f9af86dd56f79081f35f0dbaadef2103e6be1b755bdd5fd34ceaba562103e6be1b755bde4207cd7c36eab7bc840f9af86dd56f79081f35f0dbaadef2103e6bdee5c0e25db89df10ea55bde4207cd7bd83e9bf2ffcd5ab9d5f351bee45be8a4a3f356b0322df45251f9ab581916fa2928fcd5ac0c8b7d14947e6ad60645be8a4a3f356b0322df45251f9ab581916fa2928fcd5ac0c8b6cdc472b88d93c73271ff8920492779081f35f0dbaadeea3b47ac08bc6c6be1b755bde4207cd7c36eab7bc840f9af86dd56f79081e998b56b0321c2cf251f9ab581916fa2928fcd5ac0c8b7d14947e6ad60645be8a4a3f274d69ef2103e6be1b755bde4207cd7c36eab7bc840f9af86dd56f79081f35f0dbaadef2103e6be1b755bde4207cd7c36eab7bc840f9af86dd56f79081f35f0dbaadef2103e6be1b755bde4207cd7c36eab74d0802bc840f9af86dd56f79081f35f0dbaadef2103e6be1b755bde4207cd7c36eab7bc840f9af86ca92d154040f9af86dd56f79081f35f0dbaadef2103e6be1b755bde4207cd7c36eab7bc840f9af86dd56f4b93361b755bde4207cd7c36eab7bc840f9af86dd56f79081f35f0dbaadef2103e6be1b755bde4207cd7c36eab7bc840f9af86dd56f79081f35f0dbaadef2103e6be1b755bde4207cd7c36eab7bc840f9af86dd56f79081f35dc600514947d1fdb98c1be8a4a3f356b0322df45251f9ab581916fa2928fcd5ac0c8b7d14947e6ad60645be8a4a3f356b0322df45251f9a9208f937d2542a2c2abf4b9b07a4211e1c9b278e8f95aa128e4d93c747cad5094726c9e3a3deb4220d81916fa8bde4207cd77a2f157ae0a829fc5f99b1458f91a9098a053f8bf33628b1f235213140a7d00fd324868401e34e476bda9098a053f8bf336060a828b16443a956f757f4d33a8f4b9af86dd56f79081f35f0dbaadef2103e5380e5381d323a956f79081f35f0dbaadef2103e6bbd18d6dc0e991d4ab7bc840f9af86dd56f79081f35f0d98c7b63adb952d3de4207cd7c36eab7bc840f9af86cc63db1d6dca969ef2103e6be1b755bde4207cd7c36eaa82626c4bb713be21d4ab7bc840f9af7b24df722dad842dfc4324d1f9ab58038e1a2926f5ab581916fa2928fb5d1a89b2b7bc8145ac0c8b7d14947e6ad60645be87db6175cfcd5abe86400726c9e4d08023a3e56a41f82dededfcf1009e3a3e56a84a39364f1d1f2b54251c9b278e8f95aa10e8b0322df517babfa699d47cccd89f501cd7c2ffe9ad4ab68b275829098a053e844ebef9509b1751f23521d3de26b4b909391daf6a4262814fe2f43d32a969e956a5056a5620f6d3b8b7babfa6b52ada2c9cef2103e519e739badfe562103e6be1b755bde4207cd7c36e128352adeeafe9a6755d2b102c1c081f29c0735f0d98c7b6f88752a9bffb730c6dc6bd3de4207cd7c36eab7bc840f9aefeb6f10ea4f0838247ab5818bb63366bf356af3c458a6c0726c9d4249d3304ff6f705e355dfc8cf92d3dd5fdb90af86dd3b9f3f2c46f7e3fcd28e33ad70dbaadef2103e6be1b755bde420599e50956042fb916e40a2d4256f7902c1c081f35f0bffb7176d9735f0bffb7215f0dbaadef2103e6be1b755bde4207cd7c36eab7bc83db4ee2def183b76eab7bc8160fa1826271d4aa826271d4ab7bc840f9af86dd56f79081f35f0dbaadef2103e6a020bc840f4b6cb9af86dd3d81d294635e9ef183bc924ef2103e6be1b755bde4207cd7c36eab7bc840f9af86cc633a7f1dcc95768bee2d476594947e6a597f752e6fee7e3fd0faa760e1d223cc0e991d49ec0e991d4ab7bc840f9af86dd56f79081f35f0dbaadef204e451d9d97839364f0240a8082eafe9a6751e360c7ccc784e074c8ea4f6074c8ea55bde4207cd7c36eab7bc840f9af86dd56f790323a644d0802752aa09654041757f4d378383a95504c4e3a95504c4e3a956f79081f35f0dbaadef2103e6be1b755bde41ef831e9735de8bc3076ecc6339998e4927757f6e42be17ff6e42be1b755bde4207cd7c36eab7bc840f9af86dd56f4e13fdd5480bf7d0747cad07217eac178f1f87d33405734a4bc17cb5d972a1390dbaaa0989c752a9adce491e99bf85e3bbe36968bc777c6d2d178eef8da5a2f1ddf1b4b45e3bbe36968bc777c6d2d178eef8da5a2f1ddf1b4b45e3bbe36968bc777c6d2d178eef8da5a2f1ddf1af42361b755bde4207cd77f5c245bdd5fdb90af85ffdb90af86dd56f79081f35f0dbaadef2103e6be1b755bd4232f2103e6be1b755bdd6ff4d716f4ab6e54b4f4ab6e54b4f79081f35f0dbaadef2103e6be1b755bde4207aab642be1b755bde4207caa8c3221d406df71081e96df71081f35f0dbaadef2103e6be1b755bde4207cd7c36741d91630ed17dc8b7d14947d215746dd56d42721b755413138ea553e3ce6dd6fb7b82f2de9dda7c1796f4eed3e0bcb7a7769f05e5bd3bb4f82f2de9dda7c1796f4eed3e0bcb7a7769f05e5bd3bb4f82f2de9dda7c1796f4eed3e0bcb7a7769efc82f2103e6bdee5c10ea4fd186443a80dbee2103d4506a55bde4207cd7c36eab7bc840f9af86dd56f79081f2aa2ee2def2103d2db2e6bdf197b952d345994ea55b509b13be21d4ab7bc840f9af86dd56f79081f35f0dbaade9c27fbc840f94d0802aef45e2103d4509c361b318f6df10e6662edd56f79081f35f0dbaadef2103e6be1b755bde4207a8a0d4ab7bc840b940f277bbeb848b7babfb7215f0bd70d45c1cd9c6fa1f60ef0e37d0fb077871be87d83bc38df43ec1de1c6fa1f60ef0e37d0fb077871be87d83bc38df43ec1de1c6fa1f60ef0e37d0fb077871be87d83bc38df43ec1db29a8314539fd036fbcf6425fbfc6d7c36e06d9735ef8cbdca969a2cca752ad9fd7c777c6d2d178eef8da5a2f1ddf1b4b45e3bbe36968bc777c6d2d178eef8da5a2f1ddf1b4b45e3bbe36968bc777c6d2d178eef8da5a2f1ddf1b4b45e3bbe36967472df4524dba2103e5380e6be183fd35c5bd2adb952d3d2ad4ac4207cd7c36eab7bc840f9af86dd56f79081f35f0dbaaa09657c36e06d9735ef8cbdca969a2cca752ada2c9cef2103e6be1b755bde4207cd7c36eab7bc840f9af86dd3d80e69cadca8a4a3e9f49cfd37d14947e6ad606339dfeef35bd4ab68b329d4ab66fa1cf7a0792e8bc777c6d2d178eef8da5a2f1ddf1b4b45e3bbe36968bc777c6d2d178eef8da5a2f1ddf1b4b45e3bbe36968bc777c6d2d178eef8da5a2f1ddf1b4b45e3bbe36968bc776ca40e197440b0853fe438103e5095edca647501b7dc4207a4bcb45e3bbe36968bc777c6d2d178eef8da5a2f1ddf1b4b45e3bbe36968bc777c6d2d178eef8da5a2f1ddf1b4b45e3bbe36968bc777c6d2d178eef8da5a2f1ddf1b4b454d0802e3bbe3633fc2ffe9a6751e9735010e855bd2adb952d3d2ad4ac4207cd7c36eab7bc840f9af86dd56f79081f35f0dbaaa4f4fa68b2728b273bc8160fa27bc60ef2493bc60eddbaadef2103e6be1b755bde4207cd7c36eab7bc840f9af7c65d2ad7f4d33a8f4b9a808742ade956dca969e956a562103e6be1b755bde4207ca8fcad5ac0c8b7d14947e6ad60645be86587d413c747bd3295d37d14914f0485f1fe87d6a7000fee6bde02f0aba666e37424eeaae135ffa0836ba1624d953a5430048ad27e4e63e3ec60d236b8917e8c4f3a8dad5ef156c599ec230bc482621ef5e4410b3d61de2b27975c8f85d94cf64c07cf258800000000000000000000000000000000000000000000000000000000000000000000000022f0691b5ec95ead2a6ba4f588595920008b48000000000256bac2fcd8f8a6d3bc13f42b12549019577189f4aff38b9cd726f01b0106c57b8e2290f698fb71dc3b25a1aaaa60b1d3f53cfa678b86c7661e6ac573a7dad5107945ab6371a9ff8e0dc8d75fc6a8a35772933a74e9d3a74e9d3a74e9d3a74e9c3c000000000000000000000000000b6d57c2a822f3419861511d90b6ce9b40000000000000ae99449499da99daf87987923a22254c7cb0cb4e905d65714b38a665c75f5a5a6a13a326d421eea47f400709bef84bbc678ab3cc48faf6af05865dc84af0306cc9af59cd1964e4065fa5da89b49a716743d31f1c44d08029924334fd1d8d16b61e48e888881e724b58e89542692d5f6f9b4a477f3b8942de3b58363288ec47ab7586e7dc20b0cb8717c096d879c5578ffc8f4a7a3b584938228dd972c290e3600000274000768002f1338b1af3f2f724b5dab0ccb5bf86ca4f8675c2cc63a0461bb53335a3fb154caed586e84361d6a3f324b20d734a51b481586ed4cb37c4b6f2b33dd6a16e81388ca4c2f06d6ca3e21228d89e8bf7e778231b36b539d416ffb90dcfc9e0df5b0206e5a7fe649641ae694a3568d22972c3318c2f161ba1619704aac3742c32e00434000024a000ee301d3093be013efeb067bc4065a60f433d082767d7f3a7e9ae0e41d6eb6de0113b3d687889118a3e317cb6daf4ee1b3197c3c5c078a2c0ce89810103dbd1e0e39d9be40c6a694c839f5319868ff2047d68f73da62cb2bf5bbfdfbc3f64ad9efcd686a2599ff48b7900a94e42d63e21221b80637649edf9e737ad1d30ec251398bcb627dbef1b8e320196ddaee291fb07a86ed00001750004f00043b30b265d753cdb7043df4ad3d06ee3c8ac59dc6531e11756259a0db4c21d0b48a411667645996a39887e7bfb70f6e1740ce08a019627d1a65f980b0c86e60f48d80e8934645418bde9a6cdb21c3df4966c36ba6a73a3bcf6b900e8747ab5d798f08e38d03d2099de3736916ac5158032ed61ba16199f05000107800017020042a1f8c6717e7543e9404952a8b513a9eb2d16d11f0d94d0802baf28b77ce5835d25ac2deb8c9d213017f0157ca210efcdbf5cc16352d4e0151d1afdc5066ff35c24572bf16337d7ab243c1ad243d2990a8001884eae22996f00198e009801a0270027cf281dffff65b673f2f7d597ae1bc5dff6bfd46a7fddd5b7a29ff870c68000a950000d7c000a8b8007d814fca24a25a2e8ad724dbd6362ff39ad76c5d016833fa13f69fef68c1b4e89256eceb449947b26d9488f228200596a850a356aa1480006a800644f50fcbaea79b6e087be95a7a0dda4ef5a53ff897c13c263fedeaa3bb3bb2cac6ce72f77fcd8a9e0cb6d104b0fcd0f213a78f98a5e2b640fa4afafd09699006975899592077c11e589146857070a23b216de6c9c6fda3d0b0718946e9695c5c9722f0abe271889ba381e31d4f78019d2c4db4a5b03be8860ad2680010c0000458001b00f707cc024402e37e4eb9ae67b181c72742b97f523cca744ff25a63b9af486773015ffe3e054b274ba050000000414e4d46d02200000000000000002f06002f06006400000256503820b8220000d0ec029d012a300630063e9d4ea44d3fa823a2239c8813f01389676efc7c99a682b02ffeed55d3335d4fe64fee5fd53f6b7fbaffffe312e73fa4fe477f63fd87e546ed17e27fdf5e711e6bc47fe03fb17e507cd6fea3fd73da57f87f500feedfca7f4fbdfd7a6efe65e803f453f5bfdf07fb17e907b91f400fef5fd77d5ebfca7ffaf754fe7dea01fc034d0802fadffffffcfed11fb53f08ffbc1fbb3ecf5fff35babc39fe03db1ac01ab8f06efd4aa7ffa7f97ff95dfe8b55fffe0f1a4f5a2ff9e0bca81b0ed354baa9f141cd8769aa5d54f8a0d9eb8c7b640769aa5d54f8469fa6902b69fa690307a47c507361da6a97553e2839b0ed354baa9f141cd8769aa5d54f8a0e6c3b4d52ea94579a7c507361da4b93af505180f2bcd3e2839b0ed354baa9f141cd8769aa5d54f8a0e6c3b4d52eaa7c507361da6a96fa99d2a06c3b4d526035e96f47e8fe00154baa9f141cd8769aa5d54f8a0e6c3b4d52eaa7c507361da6a97553e2839b0e40cd2eaa7c507360fa30df82adc8b461d90552eaa7c507361da6a97553e2839b0ed354baa9f141cd8769aa5d54f8a0e5be00154b840d52e9a00f25d9fa62e7a3d5254ae675c7533a540d8769aa5d54f8a0e6c3b4d52eaa7c507361da6a97553e2839b0ed354baa98bcfe9aa5d54f8a0e6c1c3b4113880d8769aa5d54f8a0e6c3b4d52eaa7c507361da6a97553e2839b0ed354baa9f141b4cd16064f9e9aa5d54f478227101b0ed354baa9f141cd8769aa5d54f8a0e6c3b4d52eaa7c507361da6a97553e283699a2c0c9f3d354baa9e8f044e20361da6a97553e2839b0ed354baa9f141cd8769aa5d54f8a0e6c3b4d52eaa7c506d33458193e7a6a97553d006f18151f65fa64d79a7c507361da6a97553e2839b0ed354baa9f141cd8769aa5d54f8a0e6c3b4d52df533a5404d0802bc5aa9f141cd87692e4ec406c3b4d52eaa7c507361da6a97553e2839b0ed354baa9f141cd8769aa5d54f47800552e10354baa9f141cd5cffd8769aa5d54f8a0e6c3b4d52eaa7c507361da6a97553e2839b0ed354baa9f141b4cd16064f9e9aa5d54f8a0e5be00154baa9f141cd8769aa5d54f8a0e6c3b4cfb4e6c3b0d75d384a5e6b3ca469c00d5cce3fbba8b3f8a0e6c3b4d52eaa62f3fa6a939e9aa5d54f8a0e5be00154baa9f141cd8769aa5d54f8a0e6c3b4d015e6515e6515e651610fb4d52eaa7c5073573ff61da5000769aa5d54f899d18764154baa9f141cd8769aa5d54f8a0e6c390334980d7a3c000b94b0ec82a97553e28396f800552e10354baa9f141cd5cffd8769aa5d54f8a0e6c3b4d52eaa7c507360fa30dea30dea30dea387ca6aa7c507361da4b93b101ac5d53e2839b0ed340579a7c507361da6a97553e2839b0ed354baa98bcfe316594e4d226611ed7a921e80fa387ca6aa7c507361da4b879a29fb13d8645c03f7735a14f8a0dba1b69ffb0ed354baa9e8321378f3f4c57cb6d40d8769aa5d54f899d187641534fd0347ea03b4d52eaa7c5028c2ba62d8af6003b4d52eaa7c24ffd8769aa5d54f4780010de99c2839b0ed354baa9e8f000aa5d9f0dc62a14d54f8a0e6c3b4d015e657cc354496aa7c507361d8ecd160653553e2814615cd82e2839b0ed354baa9f133a30ec82b1d0701d402a97553e2839b0e40cd26a44d08029ff0a72f25aa9f141cd8763b3458194d54f8a051857360b8a0e6c3b4d4c01e9bf2ebaea3ecbf4cabae9a7c03c3c119f1eef7d89ec4f627b13d89ec4f57edb7e0991d2a06c3b4d52eaa605d6f70e2806ad81220ffcc240835733ae3cfd32aeba8fb2fd32aeba8fb2fd32aeba886be2839b0e33e8610cabae9cf2e610f1257732e109dea8f3f902087b0d28769aa5d58ac7eb9841bd69929143782aa1c6cc61da6a97553e283e75e91b8c3b4d52eaa7c507361da6a9755317aa936799d62c04cfabcb3b83b4d52eaa7c5072e9986a5d528b087da6a97553e2839b0ed354baa9f141cd8769aa5d54f8a0e6c1f461bd461bd461bd470f94d54f8a0e6c3b49c4c352ea945843ed354baa9f141cd8769aa5d54f8a0e6c3b4d52eaa7c507360fa30dea30dea38779610fb4d4c01ff5919575d34461d90507cc352ea945843ed354baa9f141cd8769aa5d54f8a0e6c3b4d52eaa7c507360fa30dd119bce740d5cce5a18cb7103bcd1c3e53553fbccce30ec8283e61a9754a2c21f69aa5d54f8a0e6c3b4d52eaa7c507361da6a97553e2839b07d187642d0103d24887ca6aa7c4ce8c3b20a0f986a5d528b087da6a97553e2839b0ed354baa9f141cd8769aa5d54f8a0e6c1f461d9050c9740d1fa80ed354980d7c506db986a5d528b087da6a97553e2839b0ed354baa9f141cd8769aa5d54f8a0e6c1f461d9050c9740d1fa80ec6e88837aeb1df3ccc1f747e4d080281b0869869733c451f851f851f519c4f090552eaa7c507361c6519aeb1df3cc2066ee67d85bfa6f5b44355fa6e1df46349bd5eeeb1df3d03b639574a7146757a9d71e7c968c554f8a0e5be00154b7ed2e01ab99cad267940ed8b0329ab157b1ec00434c34c49d68f4749cd00aa5d54f8a0e6c39033741ba1935dc3c3b3a0bffc472a9a6c3b1f1ca74b486543e53553e267461c154c315fa89fd7194baa9f141cd8769a0f9869890f87916d0361da6a97553e267461bf0517a5f8debe7a7f327979276f0f21f29aa9f133a30e0aa62f4850b553e2839b0ed354ba73cc34c48b65c62a14d54f8a0e6c3b4d015e65656735d345229cdd8763f25c21f206697553e2814615d54f47816cbaa7c507361da6a9754af98698916cb8c5429aa9f141cd8769a02bccacace6ba68a4539bb0ec7e4b843e40cd2eaa7c5028c2baa9e8f02d9754f8a0da019d71e7e99575d47d62a2bf399308980c7e99575d4de37d4bbcd6aeba8fb2fd32aeba7ad35180b8387f3ae00b21eeba8fb1de7a076c725571bd62940d905361c8e8442605d27baf949a2c0ca6aa62f3fa6a93110774cab9eb80819c06c3b4d5d169890ca87c3c87d21e68d8769aa4d1b264baa98bcff2b88feb8c99640dea0ab72a06c3b49727620361da4b93b101b0ed25c9d880d8769aa5d376f81f461bd461d90552e9ca0f4fe652fa99d2a06c3b2098699642e1d90552e9bb7c1da6a974ddbe0ed34d080254ba6edf0769aa5d54f899d186f51c3bcaf34f8a0e6afd378e9502df044e20361d904c34cb2170ec82a974ddbe0ed354ba6edf0769aa5d376f83b4d52eaa7c4c836199d71e7cbd2a15cfd32ae568cdf3fc2b0123e7ddf3d03b0b0d0d9024154980ea0154baa9922781f470f94d54f899d18764154ba68ae909079d47d61fa06ad281374343226611e8f9b113a0653553e2839b0ec7668af54c1bbf36103f20a75fec3b4972961d90552e00827e99bc74a81b0ec7668b0329aa98bcff097076e7c7470361da6a97553e2839b07d18764154ba6a79a941963ac82a9301d402a97553244f03e8e1f29aa9f133a30ec82a974ddbe07d1c3b9f1d1c0d8769aa5d54f8a0e6c1f461d90552e9a9e6a50658eb20aa4c07500aa5d54c913c0fa387ca6aa7c4ce8c3b20aa5d377076f2c21bab89b4baa9f141cd8769a978cdad22041ab738a95f67e99572cb54f6590fbd65758ef9e65bdfa602105f1d40763b378e950361c61737e05d27baf918e5c72b395758ee7805d712a9754a2bcd3e2839aba029c794b038a3b735abae9c0c5e70b20653553e283699a2914cec8630dea387c4bf49d25901da4b94b0ec82a97553d1e00270a91480ed340579a7c50735740538f2960b7528af34f8a0e6c3b4d015e6515e6515e651610fb4d0658eb20aa4c07500aa5d54f8a040bebf37dc892f5c8193c06be2839b0e40d1f75e99be7a4b93b101b0ed354ba6edf03e8e4d08021de579945843ed341963ac82a9301d402a97553e281462a069010be9aa4c06be2839b0e40d1f75e99be7a4b93b101b0ed33eead6d4351f65f3777d7b9251fe4e7dccc23daeb0d6922c0f044e202e6f7320e6c1d2dcd16e3a66969c53de714f79c53de714f79c53de714f3faba41cd8384126241cf13ec2e0323a83a355fd86cc2593ec4f627b13d89eb8cd2eaa7c5028c540f22d98693c06be2839b0ec7668b0327cf4d52e0084316062191d80552df533a540ba7c990552eaa65261a97553179fd354baa98bd33780e9f72c127fec3b4d52e9bb7c1caa66a9aa9e96f47ea03927496407692e4ec406b0330d4baa9f134d31c006c390334baa9f140a31503c8b661a4f01af8a0e6c3b1d9a2bd7fc5d53e269055b9502e6f7320e6c1f461d90507cc352eaa7c4d34c7001b0e40cd2eaa7c5028c540f22d98693c06be2839b0ec6824aef65751f65f9ad5d751f4deb620e3b98cde3a53f64cf32839ab315f9cfd9ec2e25cebf574b733ec2e1fdae1f0f61da6a96f0242f8f8aef9e624d92bdd7cbfb0b858c3b20aa5d377076f27f8b21e92e4ec406c3b4d52eaa7c24ffd846a51610fb4d0658eb20aa4d1afd5b4d17c5068d8769aa4abab260bea209e8d7e81b0ed34058437d1c3e534ddbe0ed354baa9f141cd5cffd846a51610fb4d0658eb20aa4d1afcb8c2ba4e6f329aa9f0874295b480a78fcace950361d8ecde31f9e9fcc9e035f141cd8769a4d0802a5d54c5e7f49ffba029e65300440352ea9595cdb7c112e08281b0ed352f202681e45b3f64c352eaa7c4ce8e1de5843ed340579a7c507361da6a974ddbe08d5317a673553245537f32979ed394bcbac77cf323b1d3b29e3733ebf03031994baa9f1408c7707d73a4f6db67fb149f9aff2fec2e1630ec82a974ddc1dbcb086e56c94cabae9d9212bd2b2b0329aa9f141cd8720669708136de0941cb9bdcc839b07d18f68730f3f37361da6a935496f61c0446a0aa79fd354baa98bd33780e9f5673253fb3553e2839b0ed354980d7a86bd2fd05361c93a4b203b497275eb4c352eaa7c507358092de657cc5bfd1668b0329aa98bd33780e9f56627429f141cd8769aa5d54c5e7f24b04f4de3a53f64cf32839ab9ffac0cc352eaa7c507358092e10e1a62dfe8b3458194d54c5e99bc074fab367375fa6a97553e28364d584c2408274dab25d373309020d5cceb8d3e4056d92839abf4fb29f1408cb5414b5ec4f627b0c6438e16e0e20361da6a6649b4bfadff53f61e4b73bbc9298de7f4d52eaa62f4cdd0b06b1a13f8506add878b13b155b51f4d0441400361da6a9754a2c572b95e69f133a387ca69ca14414d54c5f1f630d2a06c3b4d52ec1007f1854c487ca6aa7c4ce8e1de579be9a119bab0e6aa7c507361c819a4c06be283699bc74a7ec99e65073573ff61da6a97553e2839b0ed354980d7c507361c81a3eebcfe3b345229cdd8769aa5d54d08024f478225aa674a816f8227101737b9907360fa387ca6aa7c507361da6a97553d1e00154baa9e8f044b54e6dbe0005ca58764154baa9f0827fbaaf7d1b99f577fdb4384c240835733ae3cfa9d1cd86670c72ae94e05eeb1df3cd5016f83ac985fcef97f61712e93dd7cbfab314f566c58194d52a041ea2bd45bab9126f57ad457a8b757223751ee3be7a076c72aeb1df3d03b119c4102f61712e93dd7cbfb0b7f4fffd12855b859714591f4e81ab99c9a17f3747db109beef9f5ce93db5407bf2461cd54f8a0e6c3b546ae541b0ed3bac84f3d379389e8a06bbce0e20361da6a975499fb6fe765f94930bfe7c3b20a02c57312286c7353f7a1dce43bf25fa2b88d43aa5d54f8a0e6c3b4d52eaa7c506d3378c94577e9836e43d354baa9f093ff61da4b94b0ec8280af34f899d186f518764154baa9f141cd8769aa5d54f8a0e6c3b4d4c48b65de909b1cfd2a6a5d54f8a0da668b032780ea0154b7d4e6ec3b1d9a2914ce950361da6a97553e2839b0ed354baa9f141cd5d014e46b2b007e950000ed354baa515e69f133a387ca6a945843ed34057994579a7c507361da6a97553e2839b0ed354baa9f140a315040e030cfd306eff4d52eaa7a432bdd7cbfb0b89749eebe40490ca95d52ea945843ed34057994579a7c507361da6a97553e2839b0ed354baa9f140a315040e030cfd306eff4d52eaa7c507361c81a3f501d8ecde3a53f4cd148a674a4d080281b0ed354baa9f141cd8769aa5d54f8a0e6ae80a723595803f4a8000769aa5d54f8a0e6ae80a7994d37707794d376f81f461d90552eaa7c507361da6a97553e2839b0ed353122d977a426c73f4a9a97553e2839b0ed353122da06c390347ea0390334980d7c507361da6a97553e2839b0ed354baa9f141cb7c112dca9b41c83f4cb0a0e6c3b4d52eaa7a3c113880d5d014f3297d4ce68ffd8769aa5d54f8a0e6c3b4d52eaa7c507361da680b0870369b8c1b71eea9f141cd8769aa5d528b087da6a6245b40d83e8c37a8c3b20aa5d54f8a0e6c3b4d52eaa7c507361da6a6245b2ef484d8e7e95352eaa7c507361da6a6245b40d872068fd40720669301af8a0e6c3b4d52eaa7c507361da6a97553e28396f8225b953683907e996141cd8769aa5d54f478227101aba029e652fa99cd1ffb0ed354baa9f141cd8769aa5d54f8a0e6c3b4d01610e06d388507361da6a96f3c0b9592bb99848106ae675c79fa655cd7f89a3bcb46c3b1d9bc74a7e99a2914ce950361da6a97553e2839b0ed354baa9f141cd5d014e46b2bc82a97553e283699a2c0ca6aa62f3fa6a96fa9cdd8763b3452299d2a06c3b4d52eaa7c507361da6a97553e2839aba029c8d65790552eaa7c506d33458194d54c5e99cd54f4782271016f8001727620361da6a97553e2839b0ed354baa9f141cd83e8e1df296a8d8769aa5d54f47800552eaa7a3c113880d5d014f3297d4ce64d08028ffd8769aa5d54f8a0e6c3b4d52eaa7c507361da680b0870369c42839b0ed354b7885a69418f3f4c628e5d07b2fd32aeba8fb2fa82adc13a7107360fa387ca69bb7c0fa30ec82a97553e2839b0ed354baa9f141cd8769a98916cbbd2172a06c3b4d52e9ccb4d1974c986ba2d30fde1bbb0ed25ca5876405c9d79a30ec82a97553e2839b0ed354baa9f141cd8769a98916cbbd2172a06c3b4d52e9cf30d33661a9754a2c21f69a98916d0360fa30dea30ec82a97553e2839b0ed354baa9f141cd8769a98916cbbd2172a06c3b4d52e9cf30d33663800d83e8e1f29aa51610fb4d015e6515e69f141cd8769aa5d54f8a0e6c3b4d52eaa7c5028c5410380d8f5c8194d54f84cd96536bbec4f627afb05f2e6c630ec766f1d2a05be08962b9a95d47d97cdcf257103573394ed932f7839b0ed354baa9f141cd8769aa5d54f8a0e6c390347ddff40f329aa9f141cd8cc9eaf640ca5f539bb0ed25ca5821ffb72a1f50d40d8769aa5d54f8a0e6c3b4d52eaa7c507361d8ecde32515e6a5d54f8a0e6c3b4d52eaa7c25014f329a6ee0ede57994579a7c507361da6a97553e2839b0ed354baa9f141cd83e8e1df296a8d8769aa5d54f8a0e6c3b4d4c48b681b0e40d1f75e99bc07500aa5d54f8a0e6c3b4d52eaa7c507361da6a97553d1e0896e54deb90329aa982b9610bafd0d5a8107ba0a8afccf473b53dd494a761f21439f02cb40af567aa88808bafd04d0802d5a70ec1b61697070fe608ebf60dc388a761f2143c97aaa674740356c5bd992fb402a97553e2839b0ed354baa9f141cd8769aa5d54f478225b9537ae40ca6aa62f41635c43782e23fae61fbc2b4a81ab9ffaba0555e6db8c3b4d52eaa7c507361da6a97553e2839b0ed354b7d4e6dc8881fa6a97553d1e00154baa9f141b4cde3a540b7c000b93b101b0ed354baa9f141cd8769aa5d54f8a0e6c3b4d52e9bb83b7ca5aa361da6a96fa99d2a06c3b4d52603a80552df5339a3ff61da6a97553e2839b0ed354baa9f141cd8769aa5d54f84a029c8d65790552eaa7a3c00088a488106ae675c79fa655d751f65f508a685ae7070fe6090e53f4edccc23d6978ee1352eaa7c507361da6a97553e2839b0ed354baa9f141cd83e8e1df296a8d8769aa5bea67348004feb989143781f07101b1993d5ec8194d54f8a0e6c3b4d52eaa7c507361da6a97553e28396f8225b9537ae40ca6aa62f3f8ecd160653553e2839b0ed354baa9f141cd8769aa5d54f8a0e6c3b4d52eaa7c507361da680b0870369c42839b0ed25c9d79a30ec82a97553e2839b0ed354baa9f141cd8769aa5d54f8a0e6c3b4d52eaa7c507361d8ecde32515e6a5d54f8a05185740cd2eaa7c507361da6a97553e2839b0ed354baa9f141cd8769aa5d54f8a0e6c3b4d52e9bb83b7ca5aa361da6a96fa99cd1ffb0ed354baa9f141cd8769aa5d54f8a0e6c3b4d52eaa7c507361da6a97554d08023e2839b0e40d1f77fd03cca6aa7c4ce8c37a8c3b20aa5d54f8a0e6c3b4d52eaa7c507361da6a97553e2839b0ed354baa9f141cd8763b378c94579a97553e2814615d0334baa9f141cd8769aa5d54f8a0e6c3b4d52eaa7c507361da6a97553e2839b0ed354ba6ee0edf296a8d8769aa5bea67347fec3b4d52eaa7c507361da6a97553e2839b0ed354baa9f1320000fefefe0ea6d5337bb447d500c000583881e06c00ed694714363ace81e5630f7ec5437a75e58f9859f459dc9bf5fdde844d4ba3793c5c8051ad056431d0331a5853998bfc828fd3ede2e5dd88eba0882126bdf6c02076e4b1800001f19180046a86000a2258e25870dd64e7224528276cc09a9411b6a7ff47c4cdf30e593d8470db080cca913c26587ea0d1d8b5c000aeb38c84ed8000cbca000721500060248b059e658d31740f9f4d6b8000ec7d69c0040800002bfe01c093bd6ac5b6bb8342609c1e45668e607b68818b770e4df51e4034d4ff0ffaa2039effa4205553aff279613334308510c5bb0726fa8f2039b7ff10d992163b6bbcf25b0b3c080032d9b29cdec8b685399fe8e654000c979168280e5da4a3c034eef3b7cab5b4b0dc27a1ac209c87dac493fa76e88f9a8fad4bf8b13845ce4ce5eef995e88618b195b2d5ad677b61c4a1d3ec730334aba274ad0cfaddd2e57429c459780424d9fb060251cc5cfb9c5dff2b920d825ab5586ac0c92f99c5cab59cf3b7784d0802d49f339a7a237aac9df18d26845b52687ab8340682105bf94fa99c952aa870ec146ef6fdcc49c9f339a43b80988eea47f55857acd46fdf54854812432ea62d2cfc245beded75d0b4418c35cbac0b9bbb14ef8941fc7f3f7e6828fa4b01841e344eea8d85f43d5c1a0340b7a95329cc5f0dab0d1847772e1a91fc0004bc43ca8485ab9fb7b33caacbbd0a7317d5d4f06e90a0000bea11a88c006b8abc2f2e3b688db00142c23b16ed7e67d53359bbb4014b800ece157b0b1737325dbbc7d5de1155bbd36c6dae852f0534b459000282715c0000e85dc0018b92a00076133342ca29b90c6c7c243dd6a3701c0454a666d1e06422e1dbb598c3e5fe3102a612b18d96394e5c766523de84b14ffc9759dfb6d103c5277b4116daee0d098270791528248d207da5a6a7f87fd5101cfacd6dd11d5d0110e33a8d0def08b0999a18298dd8f162f890e7da58aab9ff10d992163ba6dfa336feca867d5dda529ecf3409375ce0e624a57f5801cc490deb076067ac17b843e8a1b137317a475573f48d1be89688b872154ac7357aca7363386c0e596a2e849adb01a254d6878a7407257c7a61ce3aa0144b0fea522437d590c8ba08a776d7952486531448caa139adc30b2b8033d4045ccdfc60017467876d9c19e008f5e83623880444e338a8811299476116a06ffc828fd3ede2e5dd88eba0907e4e6c6bdd9b19210047edbbcedf2afee8d0c6a44b5b210d9e4d08028865fb13e90ea43ea31bbcedecf0edf1241bba86d17f4f8e9ae9679caf3455c7d33590e155713937ebfbbd089a96ad633080c8ae8246678c2f2445d04400001d6700009bac005d88b68e70d3b85543fb0a87e1b69cd046176f3bdc7eb9bc3360305312dd2ee0f1ffafc87dc4d842ccacefadfff866866cceae818b6f620c0d4bb4201cb6931cd913932e95fb05fe240f46aeaa0c6a7b4a6715b436c7cc8d0eb8a1c9b35c9e461fea5f5d506dc5b55c8f84b74ad6173d03a7bb14072c780e9f835eb2da8bd99bd89ad986681d7e4b2568253f97bab0e93f738e1afb1273ebe3e15602e0627c9dc6f0c2b63f0a62dd3eb24e3ed4402a8fc3af59745af6f6d7b71cad1541a1250a7475309b29cb61e45b4c09e97411b68014c0010b19400cf35be5d598f40ec11078b537374005b84894381891791447ff54ee8ce3077ec7225dc70f7235ffa8275962f855ec2de7a418b5f4bb190b1ada683db319937edd9ea8b341cc68f20ac4357d1c7d48d54692391d0f2ebb20791883c08aba6800ad8d84bee15688d8be9c889e613ac5b20cbc577c11f3f8836262838aff181968a2b880bc1ee61e4db290baebb3c42fdc0bba27d555cc3f217782ebd512a71c9b191464d92d5c0ab44918d628d3dd75d9e240340dab271e0616846f06ba0a50252239ddee6b85d04b08868000c8a0000610fc3c978a28616b13dfc1bcc750ecba2b3795d3a919aac941ddc1a84d0802d8a3248e6e465d47c8a3d3d487ae2b57da19a779fdda80ac18481fe6c2666830a39708bf2a8846f42371e8063b6c0ea10b12873139bc178f96f4422485eb5a7ef30dd241b745132a37184663aa90ebdd77453005cc4fd0fde76803a54061d8b2dbb0074a6a4a7bb92c313f04cb63647d3cf457cf90c69e188f370011b96b4809398adf7b61c4b18056c1c44b0a7ef710b26a45d106f1920c7a371924b913ec5655cfdb2d4ce12c676c68e3e4d4315bdd9f491641a741bddd4369799143b35cf93072f3bcd93b4ff07f606d2c369e4c4cb84960a1a2569e7e7551ae7e1101bf592c7426b95d0a70eeba16f29d2b36481328857264fa8d80004593826e0071341ba71b2ad48166c43562793f07f8731e9ef6fe3f8baa79977008311ca9c8de704ae8fa439f4df5bed0555006f5177d11924fc231e0061e234f0fd41a55b766007bc194f7f9c0502adeb90297efcbf4d70bc5409b39394cc5da2225f438180e4db6a2fed2f7d0f6c8f72a1afd5eea034e484b6ea8b3b1f32f2f49d9c26258c51f20c47589b98bd1adcf0ac404b59515d0b0a43aafd4f49d913f02a66052ac99eb09236ed7e7a976f76a112a0e85895077c090e727b5970ba4d7e14a0c00000bb9fc0009837ea37d0b663d03b0441e2d4dcdcf83f02bdd1624a3aacf4ba3895d747d5e5e9d2ff70e20eb6dc641534d9fecb64302caf598aae57e2b0f31bff56c0e888bf7e3b62da4f0884d08025ea9c0191a2c5b49bde0cc0b7e17bf1db446d9105835f4b2eec2bfc577c41ddbbc41b13140c62ebaecf10bf6cd2a1b8018290eba0ac0622cbad4604de15bae96146b5b21915cfc11ac29b5f86661ef0eb6a7cba15358071d6708ba153563995f8ac614e82fc99854e2f419500001dc00008f2422db7673236969068532676c495315d1c30d148d09f81361faefb2f12e894de30847b0fa4e4d1eb1dd48fea4a6dfe7efef502e6e4ecbad36f3bb79588a6ec78b8ec6bb96d4f6b68938306999821291a39ff89fd44b068eb94e62486f58965b63cdd37a8eaefac1292ecd68fe6db1b8b3dd6d53bb2511a4eb7aad5a1dfaa20ae105117b6e6c9243739e9a96b670c7084497e84199f200cddf29552e1068adb5f014bfb754edf6649180d2f617c1dfaa1fe732287d4209e4cb7a069cbfaba6ce75a8a80bff83c71a4353b48f7aac0c9b5351aa83199fc209097e0458ed60bb994e996484d399d4678b01bc30ad8fd95573f48d1be89688c5b6b9c8f38d775bfcabd00babd2c5aafd302e8239beed680e2ae3b117db152fb4ab2e8a7333e18eb6a1b4379e8273a89c1a3bd965d072bba437b9bb7803d4c5bbf665d34fdae11d83d754603493f33800ac800028fcca000d900000c880001ce2b755a6c58d2958001ba00002c000006100000dc00001ee000042c000094000014c457dc6529e79625f6048ea5c2467ea19ccd6fd4ce5c4de082e1b350f1e4d08029b268ad4fd98d688ac5ae52437ced6622f8334959ef70000567862a52c24160011f800048800028b053f8a9f5931c2359954abed0cd3bcfeed16606a13bbcb70b534a54610ad1b276a2622e4b801359a0c2195a3c8d5760c820e768ca78821acda56c1c000a967ac4a3f5d044000fc10a0004a00001a794221b890725a84bfdc652c7b7bce4af8b35253d86c2dff97dcf5a72313ab6b38922eeba24b7691125f29a472602020cf61b5b82c5d06cb2b95d0b7ae82200454083003a4000134c6d5817edb43d595f63f337bf9df8cb4f115f55ccfa935b163ef13daaef04242c6c395720f60d5b063bbf5430ebbc72d7728fe70bbe74d56f70d0878b067dd23c6f50fe4644f9df06abf75c825efe0ec24235322f1884799b4f1c3422413396d5fafb6697c38214e3fd00c0ef59891e762aa1807c7f276d6f4640b6a579c15007d039b8800668000026a00015f2797bd4c363a8e3d36a1086cc695d1beeb423ed6656b5694fe75574a46fb800f419a335a1db8735400579e396b0def027d24a77709a1184aa63be951440e9ee00159e5270d55a174a000002fa00007480003e400022000012f0000a780005a40000000414e4d467e020000210000ef01000f02008b010064000000414c504827000000010f30ff1111c26c24193ad3bace842222331bd1ff09e8d3664ccbfffccffffccffffceffdd915005650382036020000f037009d012a10028c014d08023e9d4ea34da5a42322220800b01389696ee177611fce79f5680055207eaae5807eaa803bcf181fcddf04412c5bbc70015e2171805d4d40154cb4ea69e462a6775350057885c60174df28a1717a7f2bcc62171805d4d4015e0f7eebabff1c99c8c02ea6a00af10b8bfed932b7c0ce5a75350057885c6017534f231b299dc54b62db4ceea6a00af10b8bd3fa94f315460c9a802bc42e300ba9a7f8e5142e2f4fe5798c42e300ba9a802bc1efdd757fe393391805d4d4015e21717fdb2656f819cb4ea6a00af10b8c02ea69e4636533b8a96c5b699dd4d4015e21717a7f529e62a8c19350057885c6017534ff1ca285c5e9fcaf31885c6017535005783dfbaeaffc72672300ba9a802bc42e2ffb64cadf033969d4d4015e2171805d4d3c8c6ca677152d8b6d33ba9a802bc42e2f4fea53cc5518326a00af10b8c02ea69fe39450b8bd3f95e6310b8c02ea6a00af07bf75d5ff8e4ce460175350057885c5ff6c995be0672d3a9a802bc42e300ba9a7918d94cee2a5b16da6775350057885c5e9fd4a798aa3064d4015e2171805d4d3fc728a1717a7f2bcc62171805d4d4015e0f7eebabff1c99c8c02ea6a00af10b8bfed932b7c0ce5a75350057885c601710000feffb619ffff6337ff6337ff6337f600ff8a0bb4aadc7d1d2e31cd4fa631fff3bf2f7cfabca4261fc0827dae304201ed717a92d8a317f4fd860fab509ff0b9e33bfb09ed49e4ffc80e4d0802507047190ab78c35a1dc03b6016000000000000000000000000000000000000000000000414e4d46bc100000630000000000a304009b030064000000414c5048aa000000010f30ff1111c2911a498ed490d731ffff6b41f778d366d6141311fd9f80d8a4a34ba090bd6d2994b3e37ffcaf701637e5845cc8fff89fff9433ffe37ffc8fffbdeb899bcac7e2a60a60717de47ffcef958a428b76fc8fffe565f1fee27ffccf09b298ef0d2cae8f4e85c2ba6d291c34fec7ffbca10bcfcbb1e37ffccffbd916ffe37f4e91c57cc5fff89f4f1337e5cf5491fff13fbfc9e2fac8fff8df2b158596e47ffc6f5f6cd2ef0f56503820f20f0000b066019d012aa4049c033e9d4e9f4c3fa4a2a2257cd81bf01389656ee1767f08fd8c43052bf7ce4467de7fc42fc9bf1e3f7ff8b1f8bfe1bf8c3f939c877d94c389fcdfdcafbd4fe85ce3dd3b3cc07e857ea47bebff1dfd00ec00ff11fd27d267d803d003f807f6cf489fd96f82efddff4b0ebffe9dfe807f66f67fca082e7c8ffd3fd327fccde7f84883043bafb47563ed1d58fb4701b252e56afc5903c0568a19b571d66d108ebd054df646ea3168d293778a459b4f611e05c15dde065ad54d91ba960661680b736f92bf16441024a76f0f766d5c759b571d66d5c6d7a303043bafb47563ed1d58fb47563ed1d58fb4341c7563ed1d58fb47563ed1d58fb47501e4dab8eb36ae3acdab8eb36ae3acdab8eb4d080236ae05ea87563ed1d58fb47563ed1d58fb4706b3563ed1d58fb47563ed1d58fb47563ed1d58f8851b47563ed1d58fb47563ed1d58fb42884511976b38e80b736052bf164781705776e092d8a2dcdbd844ea8d91e05c15dde065ad54d91ba960661680b736f92bf164781705777582f7f21dd7da3ab1f68eac7da3ab1f68eac7da3ab1ad6abe809b7fbf22b1f87fdb7f31db89981522127c3c8929dbc3dd9b571d66d5c759b571d66d5c759a6ba34546b0f766d5c759b571d66d5c759b571d66d5c759b571d66d5c759b571d66d5c758f3c9a6baea4a76f0f766d5c759b571d66d5c759b571d66d5c759b571d66d5c759b571d6693f7869b3e23c0b6fcdfcf1f2ed445a278e712dfdbdf76edbda3aff5aea3d23a2f38e67cab91712dfdbdf76edbda3ac120c10eebed1d58fb47563ed1d58fb47563ed1d58fb4d98e39391253b787bb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3a4d0802cdab8eb36ae3acdab8eb36ae3acdab8da23c591e05c15dde065ad54d91ba960661680b736f92bf1647817057778196b553646ea581a0cd91ba960661680b736f92bf1647817057778196b553646ea581985a02dcdbe4afc591e05c15dde049c68d10cb5aa9d507563ed1d58fb47563ed1d58fb47103cd96e2f68eac7da3ab1f68eac7da3ab1f68ea0b935bb36ae3acdab8eb36ae3acdab8eb368ed7e83368eac7da3ab1f68eac7da3ab1f68eac6b5ab0f766d5c759b571d66d5c759b571d66a1c9c2dc5ed1d58fb47563ed1d58fb47563ed1d4079368eb02f1ef6a78f2950c4f6a78f7b53c7bda9e3ded4f1ef6a78f7b53c7bda9e3ded4f1ef6a78f7b53c7bda9e3ded4f1ef6a78430989e7e9ef36f032cfd278f5fc0cc2d016e6df257e2c8f02e0aeef032d6aa6c8dd4b0330b405b9b7c95f8b23c0b82bbb6a157aae7405b9b88c87c62f7e4563f0ffb6fe63b713302a4425158fbde9aaf7c95f8e2d24bdbdf76edbda3aff5aea3d23a2f38e67cab91712dfdbdf76edbda3aff5aea3d23a2f38e67cab91712dfdbdcfad6aa6c2d587bb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab817aa1d58fb47563ed1d58fb47563ed1d58fb47563ed1d58fb47563ed1d58fb47563ed1c1acd58fb47563ed1d58fb47563ec284dc7405b9b7a014fb0419b42c9cba817055e7b300b736f92bf16477b930eb716477b93062014d0802985a02dcdbe4afc591e05c15dde065ad54d91ba95ff327fb8d0eac7da3ab1f68eac7da3ab1ad6b2ff80e06656b426a4e3a9027bf21ee13527212b2f68eac7da3ab1f6863166d5c759b571d66d5c759b5702f68d61f4a383aeafc009add9a814a37d17a67066dce3acfc533833614d821dd7da3ab1f68eac7da3aae3390f58e0dd0c29a85709b571b8e0dcfd6bd38eb3f14ce0cdb9c759ee241821dd7da3ab1f68eac7c0dcf18419b44a095ca79c0be4745e71ccb84d40a51c1d7587bb1fa421bf5ee1c8b7de9017ca0cf38ab3f14ce0cdb4d7ff6032d6a9b148929dbc3dd9b571d66d566ad06651aa4424f877198a3b004d6ecdb00cc18d845fe773acfc5338336e717a8f75f68eac7da3ab1f68eac7d88f543ab1f68ea15c26a0528f0f766d5623e4da0e0cdb9c759f8a66cce929dbc3dd9b571d66d5c759b4546b0f766d5c6e3837430a6ac7da3aadcab3c5ad19b738eb3f14cd99d253b787bb36ae3acdab8eb368a8d61eecdab8dc706e8614d58fb4755b95678b5a336e71d67e299b33a49d89c0cc2d016e6df257e2c8f02e0aeef032d6aa6c8dd4b0330b405aaf908c2d016e6df257e2c8f02e0ae4d8ad09ff68eac7d898b9a5c15dde065ad54d91ba960661680b736f8aebc73896f26c5ed1d58fb47563ed1d58fb47563ed1d5719b15a13fed1d58fb47563ed1d58fb47563ec5b8147563ed1d58fb47563ed1d58fb47563ec47aa0e8e9d824d080283043bafb47563ed1d58fb4750ae136ae3acdab8eb36ae3acdab8eb36ae3ac79e4d40a51e1eecdab8eb36ae3acdab8eb36abbf9b34dbe4afc591e05c15dde065ad54d91ba960661680b736f92bf1647aea6df257e2c8f02e0aeef032d17f45a97c337f6d71fa825eeddb7ad39b34c7da3ab1f68eac7da3ab1f687474ec141821dd7da3ab1f68eac7da3ab1f687474e9b8383dbaff90eebed1d58fb47563ed1d57466c5ed1d58fb47563ed1d58fb47563ed1d574667087d1ecdeb708775f68eac7da3ab1f68eac6d09ff68eac7da3ab1f68eac7da3ab1f68eac6d09cb71d6f4caba760a0c10eebed1d58fb47563e31c75922206e817057778196b553646ea581985a02dcdbe4afc591e05c15ddc1aece359b7c95f8b23c0b82bbbc0cb5aa8523752bfaf98f61b19b4b000c37991b646ea581985a029e0ea59daa1659659b7c95f8b23c0b82bbbc0cb5aa9b23752c0cc2cf28dcce348ac7e1ff6dfcc76e266054884a2b1f87fdb7f31db89981522128ac7e1ff6dfcc76e266054884a2b1f87fdb7f31d76e8d5c759b571d66d5c759b571d66d5c759b571d66d5c759b571d66d5c759b571d66d57009add9b571d66d5c759b571d66d5c759b571d66d5c759b571d66d5c759b571d66d5c6d749ee4803caba9b7aaaded0611e05c15dde065ad54d91ba960661680b736f92bf09aba09953a02dcdbe4afc591df154f7afd54d91ba960661680b736f92994d0802b45f6c2d016e6df257e063d32645c00419b4b0330b405b9b7c95f8b23c0b82bbbc0cb5aa6c5224a76f0f766d5c759b571d66d5c759b571d66d5c759b571d66d5c759b571d66d5c759b4883bafb47563ed1d58fb47563ed1d58fb47563ed1d58fb47563ed1d58fb47563ed1d576f91253b787bb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb36ae3acdab8eb1ad0ff75f68eac7da3ab1f68eac7da3ab1f68eac7da3ab1f68eac7da3ab1f68eac7da3aae3362f68eac7da3ab1f68eac7da3ab1f68eac7da3ab1f68eac7da3ab1f68eac7da3ab1f10a368eac7da3ab1f68eac7da3ab1f68eac7da3ab1f68eac7da3ab1f68eac7da3ab1f686838eac7da3ab1f68eac7da3ab1f68eac7da3ab1f68eac7da3ab1f68eac7da3ab1f68ea03c9b571d66d5c759b571d66d5c759b571d66d5c759b571d66d5c759b571d66d5c759b4546b0f766d5c759b571d66d5c759b571d66d5c759b571d66d5c759b571d66d5c759b5702f543ab1f68eac7da3ab1f68eac7da3ab1f68eac7da3ab1f68eac7da3ab1f68eac7da38359ab1f687fd6d308b6dd179c733e55c8b896de6b242de065ad54d91ba858f53cf6a78f7b28efc69c4b7f6f7ddbb6f68ebfd6ba8f48e8bce399f2ae45bd02e74bda9e3deaadc0a7f68ebfd162635661680b736f92bf164781705776ad9b8c5ad54d4d080285ab0d8000feff3e256dc81de76b45af0b6d1c9c239c729972d7fcabe64d18539d23746f6c2c2ca484d64b764daef220a08c68bef5328326d3e5bcf0427952d91791051e80e537739c2c96bfd89bf83a2e697472b4d28000000000000000029783a18829570ac5330f7d55320d3c9b8f74096a600299fe17aaa3d88eb3385e5633b0c42f36e25fa8d092df658299960640006a5ae4cb4e00000000002af83a2e696c91da8f5d9bf7631f11fa99021d4c339000000000000000000000000000001effd846fffed13cded83d9198d3c8543d4255f81c221d1f9efac654e0581ec0494600008c000037e00b4e69bd08d18c015564eacda80f263e28c14eced15f8600c46b45fec4224cf5633e7870d42830977fe222346e1b609a7fd7efd9df8dae7f8999d3967c51ad105831b4d0f8e491261f6fabd7dda99429b4be0b0a6650bee7d78dbb88b7d7e0a756d6353578314b0002a5f649ef8000000000000001d55f2a73e8776380a86a79a4c7d75c01409cafe37cb019c9941a6b0f7439b997a893697be4fc6e30c2757631724f828efcdb99d9cd5b58916949019b405ea664a81811786be36b84fa1e71827a6b1a4b92ed3e103c654765861047e20caaeed054ce9e12683605b87a5065c03d51cf360a57fd1144dd21d9fd85200423eb2d47c49f1e0c7cafd77595823be389ef39284e36cb048ad014f00dd872fe32a266832feab6b1a9abc18dbfe94d0802ef8373a89e49ef800025a012d00000858006fd7cae023e0f9cd3c1d2e5c624ea0c239d71a2f45a00483430a64fa1b57623ad0473bacb3c7044930158420eb194f089115cf06dd9d516346e3d34dad97fc8826a3b5f435497f5c6f07a505de225b7b60f64661990807430400f732ea298f8edf899850dc7c2f0ca70783c50a6080c9c000015843570face000039800013807bff8d65e1a2209d1f0b6d1c9a808668a800000000000000000f3ff13c4fb0f8b4aec8ff6675d2546366ca35e2218abe1ef06927be0a3942cc4fdcbadfb30b1a42eb774445ffa46adc67fea0c5440e6d1095ca2f7a6c2427c96cb3e9de140fc9a5dc19ca290fa98feaeadb3e16524d316c57ddb89c6c1be05ddad2f12a8140f1bd5cfa8b0298d8849b9d5c14ab60b86af10e2c0fc742603945a1204da455493f912460000000000054c2558f708af3b0d6319e0b25ab84c1efb2153448933e35748ff6fa22fcefbc6b3d09e7c7327c8fcfc22cb8d9a8e76d7bfcd3a34d0a6d91d81aa91b351dc19a5cf01720691face0f3266e9a1fa5aea8f8ee57f8187c9626094f03ebf5b9d75164c9a6ea850aae236d6ab66800000000005f0ff1d1c5bc8561867a21de021e0b23a7f1ee2cfae299c39612a732176a6d7c1b1d11493df63d199aafdb8221b2390d2ee3e9a437b70141455206371afd00b640003e540d688ea8431b664740009a62ba957f1718a635497b965aaf37de4d0802d82688cea14533600017938006a800d1e598984ca3a9ba0924b313099475374124966261328ea6e82492cc4c2651d4dd0492598984ca3a876299d55d8fff08eaf6632d6753d635368c000000000000000003de700000000000000000000000115de600fd8fc3e8dbfb7d7ac35d3b8fbeee6cbfa0f248f8baf69c50cb3552394d52a2256de8e0c3cbab8e42c636a7fd59306ecdcc8529cdeaf2d7803f9a100ee575ae72c8ca600000414e4d46ce0c0000630000840000dd0300d5020064000000414c504869000000010f30ff1111824123498a26195bf7fffe3d31334347f47f028a2d4f9e31d662d32e6bb181fffda32a86fb35600df91fffe37f30c6da60fe07a1ac0de77ffc8ffff13ffe472dd2627ab7a5c5e409ffe37ffc8fffbd8d59437b58ed29ffa35ad66cf9df4fab62cb6f360056503820440c00001011019d012ade03d6023e9d4ea34ba5a4a3a1a5dc0878b01389656ee175ee80d5028808fcc80267f96bfa6fe4ced08f33fc86fcc2f94092afbedf36e9a2fee9f68ff03fa003fea742ff301fa35facdef5ffc7bfccff72f6fff500ff3be6d3d601fc03fa87a58fed87c26f934eafc78354bfe9aa17a013b1f1d3190499efd96229d246e9cc921eea66ae1267e2c8f033dfd9522dd39080e1dfc779e9da2c6ef58dd5f7264ce9a4cd5c24cfc591df89cddd5b58ddeb1bbd6377ac6ef58ddea6f43f3191bd3b458ddeb1bbd6377ac64d0802ef0d81458ddeb1bbd6377ac6ef58dde1b08f8cdda2c6ef58ddeb1bbd6377ac6eb3b2ef58ddeb1bbd6377ac6ef58dd676b64841a22c6ef58ddeb1bbd6377ac6b6110372b6b90196878dbdd7b63aed399243dd4cd5c24cfc591e067bfb2c453a48dd39713cfc2c1fe923746dade29d246e9cc921eea66ae1267e2c8f033dfd96229d246e9cc921eea669c0ca722e52404c5221950624913faa184d50624913faa184df4c2d150c26a8312489fd50c26a8312489fd50bdc458dd6765deb1bbd6377ac6ef58ddeb1bbd6377ac6ef58ddeb1bbd6377ac6ef587a0b54ec809d9013b20276404ec809d9013b20276404ec809d9013b2027624f7054ea79e9da2c6ef58ddeb1bbd6377ac6ef58ddeb1bbd6377ac6ef58d6c65aa4809d9013b20276404ec809d9013b20276404ec809d9013b202763e591b0a1d16377ac6ef58ddeb1bbd6377ac6ef58ddeb1bbd6377ac6ef58dd676b64809d9013b20276404ec809d9013b20276404ec809d9013b2025f79a2bc3af4e5ca2affb067ba99ab8499f8b23c0cf7f6588a7491ba732487ba99ab8499f8b23c0cf7f6588a7491ba732487ba99ab8499f8b23c0cf7f6588a7491ba732487a7d8de364c381df615bec2b7d856fb0adf615bec2b7d856fb0adf615bec2b7d846db855d4f0afb0adf615bec2b7d856fb0adf615bec2b7d856fb0adf615bebbbd75236dc2b7d856fb0adf615bec2b7d856fb0adf615bec24d0802b7d856fb0adf3092378b23be4405fb29c87ba99ab8499f8b23c0cf7f6588a7491ba732487ba99ab8499f8b23c0cf7f6588a7491ba732487ba99ab8499f8b23c0cf7f6588a7491ba732487ba99ab82983b3ab366489fd50c26a8312489fd50c26a8312489fd50c26a8312489fd50c26a8312489fd50c26a8312489f758ddeb1bbd6377ac6ef58ddeb1bbd6377ac6ef58ddeb1bbd6377ac6ef58ddeb1bbd6377ac6ef58ddeb1bbd6377ac6ef58ddeb1bbd6377ac6ef58d65223029d79bb3f1647819efecb114e92374e6490f7533570933f1647819efecb114e92374e6490f7533570933f1647819efecb114e92374e6490f7533570933f1647819efecb10db73e5e5d88f6b9d05da88818a39ae74176a220628e6b9d05da88818a39ae74176a220628e6b9d05da88818a39ae74176a220628e6b9d05da88818a39ae74176a220628e6b9d05da88818a39ae74176a220628e6b9d05da88818a39ae74176a1d0e16c809d9013b20276404ec809d9013b20276404ec809d9013b20276402a7364ec809d9013b20276404ec809d9013b20276404ec809d9013b2027624e224e6809966decd77467dbbf46e03a6701d3380e99c074ce03a6701d3380e99c074ce03a6701d3380e99c074ce03a6701d3380e99c074ce03a6701d3380e99c074ce03a6701d3380e99c074ce03a6701d3372230abce04ed701b8496398af658c24b1cc57b2c61258e62bd96304d080292c7315ecb18496398af658c24b1cc57b2c61258e62bd963092c7315ecb18496398af658c24b1cc57b2c61258e62bd963092c7315ecb18496398af658c24b1cc57b2c61258e62bd963092c7315ecb18496398af658c24b1cc57b2c61258e62bd963092c72ad4574ed16377ac6ef58ddeb1bbd6377ac6ef58ddeb1bbd6377ac6ef58ddeb1bbd6377ac6ef58ddeb1bbd6377ac6ef58ddeb1bbd6377ac6ef58ddeb1aebd3a4f4cf3ddc193eb7c173ddc193eb7c173ddc193eb7c173ddc193eb7c173ddc18fe4eb461813b133b1ad6c9510dab1613e9ceade1a896020a42cd43f58ce5906d053f156e224fd7e35a21915aeaef89b3e1faa714d6f8f6cdc9f5dc9a9f0fd538a6b7c7b66e4fb532cdbd9aee8cfb77e8dc074ce03a6701d33735280ea3d3b4537a450c26a822f59af8b2344a8236875f6cb75761422aff5f8048da1d892489fd50c26a831247f1be07458d6c60ddeb118c6d065c5b359ac6c8dba0028aca4809d9013b20276404ec49eba95bebbbd752b7ce6c6c9990144a27ed67fdd0015577ac6ef58ddeb1bbd637786c0a2c6ef0d81458dd7fd72a664051289fb59ff7400555deb1bbd6377ac6ef58dde1b028b1bbc533fd6f82e3e32421ec674e5c2e268ff46ec5141a1141c3898d6795a9f999312c674e5e7c7458ddeb1bbd6377ac6ef5379e58ddea8f3788b20c51b411bb14506845c4b4e1b9f68b1bbd6377ac6ef58ddeb1bbc3604d08025163778da79127ae98ebf4fec506846a8b67fb74290d123276404ec809d9013b20261c0efb0add34de22c8d801a6f424f9ad56a74f93f2c9d9b6a0e3bcf4ed16377ac6ef58d6c60ddeb1abb960c55714d6f8501e79b119ecd7640942eec55f74004f0d2a121b7b35d9cca228499bed4cb3ae9013b20276404ec809d8f9641813b1f2c8314d379da2c6c2800a250d7ca6dd7e746287458ddeb1bbd6377ac6ef5379e58ddea6f3ca8f378c9d8fb141a11abf493a4ba731c1d16377ac6ef58ddeb1bbd635b18377ac6b632d418de768b1b0a00289435f29e0ac6c9d01b06770f9eade29d246e9cc921eea66ae1267e2c8f033dfd96229d246e9cb97785b1795bec23300f1cbcf303fe447f98a47d8a0d08d583f425084f3f3ddb272b7fe06c9d9013b20276404ec809d8f9641813b1f2c830411bcf4ec48b66b36e749b684e4ec809d9013b20276404ec8054e6c9d900a9cd93b202763ec506846d63e07458ddeb1bbd6377ac6ef58ddea6f3cb1bbd4de796377ac6ef1dd76bb6285a88b1bbd6377a982bb0adf6a659b7b35dd19f6dcee29d246e9cc921ee402afb0aba9e15f615bec25342359b58f81d161a000fef17cf71b0a4bd79bb3f64c5a366bf4b7be9b6863f9fcca190358f6bba225a7ad38e6f1ee5173863bf1bb735bce5afa851dc80e81b9975bdbb6c7a41fa9e3e3c6f9154dc11371bbf815383e0e91ac1cf2ff03ec14ef2c4883f800034d080271af493b998a4110781794c87d1c496afc5752df8a21e9b6863fa1946f13a2790c57843060d57c4ee2a34e050ad85e0c1aa71392de7eb40d528b44681aa207fde99d7a1e10dd83317f86225d599f7c7386dbf56a64d52fe67faf37b7a04f5356b924fc831e0d588e55ae4c2a94bb82f84b1149df05500000000000000000000000000022a4bdce039aafbee590c69edd3fdd02ad3e14ceeb3b0daa19c9f1e434e6b8d6adf6cb6c230dea1c638d56ac165c57576357fdfb6c4a1347f9ce4854dc7f34dba2568b6d38f28e01b7b942a46df4bb82f84b1149df0962293be12c45276ac00000000000000000457bd147feb113c1d3b43a06dfff5c6a6e4f3ca533f9bc807d6e1b9e22007fb6000001fc595f573255edb142b94a8cb2328fac600034f6fa0bbf8a49e87455aa84c18002d1a7de6a98e121c000000179794b16fb2c8ce98c437488155ce8089d2baf71295dc9e2d6dab5c9000000000006cd311e61f30dde6da8b65513e93774f7d2f668d4a38e3467ba80559d7e491c71933b8b3da24f15f6dc8f720aba842d853031fda38c7ba16b54989b96f9185dc839c223d351659167b9a54b0fd0edf9efe41d52bba4ce92845a9a080c315272d835737b49abe97dadbf5e3fddfeca9afd6fe561b2909f9e56c1bb4a530b947a8508c5337d853ea9822b9ea414f3d000100ad7266c001ee00df13f412a383cfcf615e1b87e17f57a9fc965b1593f4d0802ffab33a6289708a04acb4e182cb79a037a6896f00297113092e04d9b78b9ab1d2704db4e1afd2e14885f78275c06435cf07fb8934f1f07c99e49b66343e6664ebcab90013016cb2ad724ea70005660063ad74fc1c7ce0f6667b88a64fb53733f7bc32aad59791e49caab575b77726653bcfcbf273acc7346010c05c650d1b4e9d639fc8e297db852fde44903c99c082abd577466b1c992008a54054b9e35bfef53bbe547bfbdcf2171524a2803c045c782281c54f7e6da37d68016623563c56437bd3a49f35e974310ae710a548059b01ad7005aab5ce6141fe44090000e90047f74d1f0e9a10b740ed90c2141c29fc6a89cd5e5b58b00751017f766906d90351faba4b46b8d2bf408ba37ffb1097ffd863af12686176a0956388ca4010cbcc0328015a000000000d6fb1dff3ca2dcc232e7230b108aade36a204b0cb69ed73745faa70ffff3c56b71ae4c000000414e4d46442300000000000000002f06002f060064000002565038202c230000f0f3029d012a300630063e9d4ea34cbfa4a3a223bc9813f01389676efc7c99a682b03ffeace82c2a3f8fdfb77f6bfdbcfeebffff8c7397fe95fd63f657fad7ebcf2767683f173ef67397f1ce28ff0ffda7f2a7e67ff44feb5eddffbeff76f600fed5fcbbfe0ff4af7fde95ffa47a00fd1cffc3fdcfdefffb4fe90ff13f825e801fdaffa4ffebf68bff19fffff7cfe573fa07a807f00fee9fffff4d0802f37b45fed2fc217eeb7eef7b41fffcecd4e9f7e8cff80fe6fef5d600d54a850feb353ffd3fcbffcacff37aaedfeef8d7facbffd305e62e6ad3554a03b4d5a6aa940769ab4d5527b68db694a80dbb21194d52b025fa232b025fa234f4aeaa940769ab4d55280ed3569aaa501da6ad3554a03b4d5a6aa940769ab4d552807d38ebeaa940769a13ff64e9a478f2e2aa501da6ad3554a03b4d5a6aa940769ab4d55280ed3569aaa501da6ad3554a01f4e3afaaa501da684ffd93a6f73b9ffb7642329aaa501da6ad3554a03b4d5a6aa940769ab4d55280ed3569aaa501da684ffdbb21194d5523fdd17b7a4cbef001194d55280ed3569aaa501da6ad3554a03b4d5a6aa940769ab4d55280ed3569a6f2e2aa4f8baa9403803c237ec671d74f07b2cfd6907fe650b9ab4d55280ed3569aaa501da6ad3554a03b4d5a6aa940769ab4d55280ed354f220e118b9ab4d5528070efaee18b9ab4d55280ed3569aaa501da6ad3554a03b4d5a6aa940769ab4d55280ed3569aa59190b9aa97fa6ad3553d7d41f554a03b4d5a6aa940769ab4d55280ed3569aaa501da6ad3554a03b4d5a6aa9407692f001194c206ad35549ee80a7994d55280ed3569aaa501da6ad3554a03b4d5a6aa940769ab4d55280ed3569aaa500fa71d7d531ed3554a038c1074eb4840ba3089c75f554a03b4d5a6aa940769ab4d55280ed3569aaa501da6ad3554a03b4d5a6aa7af9ae6ac4d0802f9e9ab4d55280ed25e002329aaa501da6ad3554a03b4d5a6aa940769ab4d55280ed3569aaa501da6ad34de5c5549f1755280ed3569aa59190b9ab4d55280ed3569aaa501da6ad3554a03b4d5a6aa940769ab4d55280ed3567810e118b49681da6ad35549ee7fedd908ca6aa940769ab4d55280ed3569aa9e42baea6142d91ea12fe72f35c258817460769c7f387642329aaa501da684ffdbb20b001da6ad3554a01f4e3afaaa501da6ad3554a03b4d5a6aa9407692f0009e5c4de5c4de6070769ab4d55280ed25e002329840d5a6aa940769a13ff6ec84653554a03b4d5a6aa940769ab4d54f5f359da58d9a58d9a7318c5cd5a6aa940763b4b21193e7a6ad3554a03b4978008ca6aa940769ab4d55280ed3569aaa501c834ae834ae834ae83550a6aa940769ab4d52c8c85cd54bfd3569aaa501d8ed2c84653554a03b4d5a6aa940769ab4d552807d38e499272e0f3264da7453dd22cb63550a6aa940769ab4d52c7d5a2a7b17d8845c04349a15395280ec8a1abe6b9ab4d55280e33aecb850b646a6424940769ab4d55280e41a57d3567cf1da7318c5cd5a6aa940763b4b1bb5b243355280ed3569aa59190b9ab4d552807d38e4982e285cd5a6aa940769aa7910708c644bcf30383b4d5a6aa9407692f000a8986b792d55280ed35697d4ced0b9ab4d5523fdd172b1ddc23173569aaa501d8ed2c846543a2a5d08653554a03b4d5a69bcb89ce94d08023fe151ccb8a173569aaa4f73ff6ec846535548ff745cac77708c5cd5a5edb47b5479f3f5a4205d1850a117cb18aa3748fe034dcc36732d99ccb7629f62fb17d608facf45b23e653554a03b4d53191226d27cc2f9059ecd7850b64da916c94fdfb19f3f5a4205d1850b64da916c94fda9642329aa9e2b930cfd6907b8380b1db6908172079e18e1e1e14288097ef2036ec84a30393e6b34e85c6d7312281cf99462e6ad3554a03b4dd6a371969aaa501da6ad3554a03b4d5a6a96474beba4656f2ebfb00b0b647cca6aa940769aa8b30d69aa591b969aaa501da6ad3554a03b4d5a6aa940769ab4d55280ed35697d4cea54cea54cea54e6edd908ca6aa9407643986b4d52c8dcb4d55280ed3569aaa501da6ad3554a03b4d5a6aa940769ab4bea6752a6752a736ef04518b9a0cf1eb63f5a41e479908c9eb986b4d52c8dcb4d55280ed3569aa5158c0b64da699a1694fdfa85c96b3e7eb3c7910548b64a7efd8cf9f2574bb2694fdfa8661b8462e6acf021bd8583e566fdfb19d3e40636f663d014f329aaae7f9099da1727330d69aa591b969aaa501da6ad354b232c40c074de4382c40c4a63e64f4ecfcff2133b42e6ad2fa99da18557a53dcfc68ca6aa93dcffdbb20a8986b4d52c8dcb4d55280ed3569aa591907566f1fd348a13a0e1187567a20e1a57d3569aa59190b9aa97f8ed398c62e6ad2fa99da1727330d69aa591b969aaa501da6ad34d080254b2320eacde3fa69142741c230eacf441c34afa6ad354b232173552ff1da7318c5c94db254a790f53ed874b32c1da688661aa009853feda2163469b3f17e769ab4d55280e46cbf1718e97dae8ce7c5967b8b5217c2ff02964fe9ab378fe9a47cc9e46f238a72963e61d79bff3285cd5a69bcb8aa93de6e9ea45b247247b39e68006dd90945f1a5040434c353c9d6d17250c8c84653554a038ed1cc2340d4fca47b364e9a450f2e6aa940cc9f217fedd908ca5f533b42e49f3bf4a6927708c5cd5a6aa9407643986a7910628f69e838462e6ad354b232173569aa5ddfec9d348f994d5523fdd1b7642327810df4ebcf2f84b2fa6ad3554a03b4d59eb986a7910628f69e838462e6ad354b232173569aa5ddfec9d348f994d5523fdd1b7642327810df4ebcf2f84b2fa6ad3554a03b4d59eb986a7910628f69e838462e6ad354b232173569aa5ddfec9d348f994d5523fdd1b7642327810df4ebcf2f84b2fa6ad2f79bb19f3f5a4205d182fa57c0e48785fd09db3c87a820fd0bb678e0116c94fdfb19f3f5a0e28bfaeedd904f2e2aa501da4dbcd9e9d9d40769ab4bea67685cd5a5f533b42e4c323bad20fa19795e0f642329b0402c0e0d2e26f2e2ef34afa6ad354bbc14f329a6f2e2aa501da4dbcd9e8e085cd5a6aa7af9ae6ad3553d7cd73569aa9ebe6b9ab4d55280ec7696366963669642329aa9f22f2b4a0390695f4d5a6a9777fb274d23e4d08026535548ff746dd908c9e0438462e6acf021c23173569aaa47fba2ef044ed4ced0b9ab4c0108cfaaa47fba36ec8464f47041d59bcca6aa9403e9c75f554a01f4e3afaaa500fa71d7d55280ed354c1a6b529fbf522163a216c9b4df42cc46d3be8191fce97dd9c68851234217354f220e118b9aa605604ba5f76719f5d63ebed1b97a9f6e9d9c9459b76413cb8aa940769217de977bad20fa2265d5deea8c578e64bf59ef2c2a4c69ab4d55280ed3567810e0f17a9514d202174afa28c0ca5f533b42e6ad355490fd5647267685cd09ffb76423297d4cf03679553c573bcd5a6aa940769ab4d54f5f35cd5a6aa79125eb68e4a7994d3797155280ed3569802119b021c231725e002329aaa47fba2ef044ea358c8c5cd5a6aa940769ab4d3797155280ed24acc7d504416ca6a964642e6ad3554a0210606dcffdbb210f3ff6ec84652fa9cdbbc113a8d6323173569aaa501da681c6b8633e7eb401a9c45a4da91610d0d12533ef597d4fbb38d9fc0bb2f6f74217354d2597d737efd8cf9fad2102e8c285b26d48b64a7efd5d17789a6439f3ea4770974530ba30a16c9b522d92821c231735678120a769cc1b7f567a87efd5012f385eaa940769ab4bea6752a6781b38e5b2186a97e95a4b203b4d5a6aa940769ab4d55280ed3569aaa501da6a9e45de835504bfc769642329aaa501d8ed2c6cd2c6cd2c6e4ebbf4d54323c01194d55280ed3569aaa504d08021da6ad3554a03b4d5a6a9646e53c8bbd5923fdd1b7642329aaa47fba2ef044ed4cea82119f553e4628462e6ad3554a03b4d5a6aa940769ab4d55280ed35678120a769cc29d79e5c554a03b4d03f100770b64da6f33771cd8c9dd6d34840b92ce41b22cec02119f553e4628462e6ad35549ece34a625b253f7ec67cfd67d10e38b2c16c9b522d929fbf633e7eb483d164a2ff6d2102e8c285b26d38680249ebea0d8120a92cee7fedd908ca69bcb8aa93e2eaa5010830383b49b7ba10b9ab4d552807d38ebea97d31c0ec48a2001711bbcc35a6a964641c35503c8bbd5923fdd1b76423297d4cecdb263da6aa7c8bcad280841a6a36ec84653553d7cd73567ae63800dbb21188b30d69aa5919070d540f22ef5648ff746dd908ca5f533b36c98f69aa9f22f2b4a021069a8dbb21194d54f5f35cd59eb98e0036ec84622cc35a6a964641c35503c8bbd5923fdd1b764232979ed74bdc2d48b64a46fd8cf9f2a52cc63729ef4998dd6eb49640769ab4d55238f925e8baf383f332a082707e85dacf76da19a0769ab4d36f87b3b8c73cbb9bd7ee8165f71c62deccae7a26d3984046950c91fee8dbb21194d55280ec7696415fefd37ba076413ce8ebeaa940769a1e9a4b3b09887fc3e16aa940720d2c3ed2ca0601d1fefa7f539bb763669642329aaa501da6acf021c1e6f2602f30327a3929e653554a03b49b7923bc883232186b4d552807d38e54884d0802323208a97420f4053cca5f533b42e6ad3554a03b497800885914181c1da4dbdd085cd5a6aa940420be10bee8bdbd266376423278120a769cc247041c35503c8bbf4d53c8838462e6ad3554a0390695d59243f5f2501c95a4b203b4d5a6aa91c7c9237fea19e19f3b17974e71833a5f76728664d14498c5cd5a69aa687a071cabaf37af8668bcf9cf21e8c26b3f7aa6f3036e80a712fbaf30bf5a02f77b2e657642329aaa501da684ffdb82a5e6b50a6a977891cd55280ed3567811086a538dcc2118b9ab4d52fa23c41111e1fe5c4de606dd014e3cc6002dc0ed3569aaa501da684ffd96ea5de0a7994c01105b29aaa501da684ffd94661ad3554a03b4d545986a8b30d4f220c722ef41aa81e4269965f4d5a6aa940769aa7910657d904ebbf4d54323c01194d55280ec769637730d69aaa501da6aa2cc3545986a7910639177a0d540f22a1eeaa501da6ad3553c3835fbf633a37528a42d2102e8c285b26d3bf135ed2242327a3929e653554a03b490cc7dee7ec5f62faff061b18495280ed3569aa5eca56dcaca7ec5f62fb11d15fe2d9fa5949646e530896ea1a5f9d7eb40a7dca54b05f522bd237b7e0ed3569aaa4f7408df9099da1725e08a31726dee842e6ad3554a01d2b1a82b703b4d5a6aa940772b89014cbbc0027981b73ff7b39b97e9d2838462e6ad354b2320e1a57d354f22efd3550c8f004653554a03b1da5908ca6aa940769ab44d0802d55280e41a5741aa81e4418e4418e45dfa6ad3554a03906aa07910708c386aa14d52ef1239aaa501da6acf024166ad3554a03b4d5a6aa940720d2ba0d540f22ef41a5741aa853554a03b4d531921c4fbe4fc4ba01261f4d70a16c9b522d929f9d1fea240f3e74bed4bc69eece55d51f6ea07ea6a7df3eece55d79c1fa176cf21ea7dd9cabaf383f42ed9e260b84dec053e87c786111e447abd6d485f0c223c88f5777c70e76cf21ea7dd9cabaf383f3f670665354a2454d467eb483df0576506205d1823559205f2b16210072fdf3b6789c2b40b45fabe9ab4d55280ee8e23ced3569bd0aa1797cad3a03fe61858402c0e4f9c0f5fb00af8d24ffb63ac25df127f84820c00571202d2b9652a500fa7e035b529292432da64e545f3e653554a03b4d5a6aa940769ab3c09053f3c4b9ec1fe9436c3c721e9a13ff6ec824546d170769a13ff6ec8279713797155280ed3569aaa501da6ad3554a03b4d5a6aa7afa8365ff35b61e38f60ff4a1baa69bcc0e0ed25e08a3173427fedd904f2e26f2e2aa501da6ad3554a03b4d5a6aa940769ab4d54f5f506cbfe6b6c3c71ec1fe943754d37981c1da4bc11462e684ffdbb209e5c4de5c554a03b4d5a6aa940769ab4d55280ed3569aa9ebea0d97fcd6d878e3d83fd286ea9a700cfbb39575e707e85db3c41a107bbb6aa93dcffdbb209e5c4de5c554a03b4d5a6aa940769ab4d55280ed3569aa9ebea0d974d0802fcd6d878e3d83fd286ea9aaa501da4bc11462e684ffdbb209e5c4de5c554a03b4d5a6aa940769ab4d55280ed3569aa9ebea0d97fcd6d878e3d83fd286ea9aaa501da4bc11462e684ffdbb209e5c4de5c554a03b4d5a6aa940769ab4d55280ed3569aa9ebea0d97fcd6d878e3d83fd286ea9aaa501da4bc11462e684ffdbb209e5c4de5c554a03b4d5a6aa940769ab4d55280ed3569aa9ebea0d97fcd6d878e3d83fd286ea9aaa501da4bc11462e684ffdbb209e5c4de5c554a03b4d5a6aa940769ab4d55280ed3569aa9ebea0d97fcd6d878e3d83fd286ea9aaa501da4bc11462e684ffdbb209e5c4de5c554a03b4d5a6aa940769ab4d55280ed3569aa9ebea0d97fcd6d878e3d83fd286ea9aaa501da4bc11462e684ffdbb209e5c4de5c554a03b4d5a6aa940769ab4d55280ed3569aa9ebea0d97fcd6d878e3d83fd286ea9aaa501da4bc11462e684ffdbb209e5c4de5c554a03b4d5a6aa940769ab4d55280ed3569aa9ebea0d97fcd6d878e3d83fd286d4e35c319f3f5a4205d1850b64da916c91c72909923735548ff746dd8d9a58d9a5908ca6aa940769ab4d55280ed3569aaa501da685014e46e7fe07fa50db0f1c7b071a57d3569aa591b969aa9ebe6b9ab3c086f808708c5cd5a6aa940769ab4d55280ed3569aaa500fa7955c484af5286d878e3d83fcda5908ca6aa7afa83eaa91fee8dbb1b34b1b34b21194d55280ed3569aaa501da64d0802ad3554a03b4d0a029c8dcffc0ff4a1b61e38f60e34afa6ad354b2372d3553d7cd73567810df010e118b9ab4d55280ed3569aaa501da6ad3554a01f4f2ab89095ea50db0f1c7b07f912403faeb8614292658b8fd69081746142d922ff1184790faaa47fba36ec6cd2c6cd2c84653554a03b4d5a6aa940769ab4d55280ed34280a72373ff03fd286d878e3d83ecc3545986b4d52d8ab303297d4ced0b92f0009e5c554a03b4d5a6aa940769ab4d55280ed3569aa9ebea0d97fcd6d878e3d83fd286d8330d51661ad354b2372d3553d7cd73567810df010e118b9ab4d55280ed3569aaa501da6ad3554a01f4f2ab89095ea50db0f1c7b07fa3986a8b30d69aa591b969aa9ebe6b9ab3c086f808708c5cd5a6aa940769ab4d55280ed3569aaa500fa7955c484af5286d878e3d83fcf413237ce7b17d8bebfc17cb69faaa47fbea2aa4f73ff63528f2ba916c903dfa0918fd6907bc5f7e059b7642329aaa501da6ad3554a03b4d5a6aa940720d5410380c3836c3c71ec1fe95169aaa501d8ed398c62e4bc00279713797180d969aaa501da6ad3554a03b4d5a6aa940769ab4bea736f4440983fd286d878e3d881ab4d552807d3cad280e41a5741a5741a57d3569aaa501da6ad3554a03b4d5a6aa940769ab4bea736f4440983fd286d878e3d881ab4d552807d3cad280e41a5741a5741aa853554a03b4d5a6aa940769ab4d55280ed3569aaa47fbe9ff24d0802a6d0f1c7b07fa501beade907124dba30a16c9b522d929fbf633e7eb483e044182388969aa9e45caa7a1ebe8bccf4799e95d502f5e659facf84d677b13cca6aa940769ab4d55280ed3569aaa501da6ad354b2372a07018706d878e3d838d6d6f05c480b4c48a1ffd36f8d194d379737ea28bfc93b8462e6ad3554a03b4d5a6aa940769ab4d55280ed34280a72373ff03fd286d878d3ff6ec846535548ff7d45549ee7fec87fedd908ca6aa940769ab4d55280ed3569aaa501da6ad354b2372a07018706d878e3d838d5429aaa501da685014f329a6f2e26f2e2aa501da6ad3554a03b4d5a6aa940769ab4d55280ed354f22ef47b9e7c71ec1fe94357d41a935ba53f7ec67cfd69081746142d9230acfe24908ca5e7b56577d81fad20f5e404764d69aaa501da6ad3554a03b4d5a6aa940769ab4d55280ec769cc22ff9adb0f1c7b07f9b4e6101108a200171202d308462e6b21f13b8462e6ad3554a03b4d5a6aa940769ab4d55280ed3569aa591b950380c3836c3c71ec1c6aa07910708c5cd5a6aa940769ab4d55280ed3569aaa501da6ad3554a03b4d5a6aa940769ab3c09053f3c4b9ec1fe9436c2fbe9fd4ced0b9ab4d55280ed3569aaa501da6ad3554a03b4d5a6aa940769ab4d55280ed3569a6f3036f6095c38e3d83fd283c113b533b42e6ad3554a03b4d5a6aa940769ab4d55280ed3569aaa501da6ad3554a03b4d5a69bcc0dbd82570e34d08028f60ff4a0f044ed4ced0b9ab4d55280ed3569aaa501da6ad3554a03b4d5a6aa940769ab4d55280ed3569a6f3036f6095c38e3d83fd283c113b533b42e6ad3554a03b4d5a6aa940769ab4d55280ed3569aaa501da6ad3554a03b4d5a69bcc0dbd82570e38f60ff4a0f044ed4ced0b9ab4d55280ed3569aaa501da6ad3554a03b4d5a6aa940769ab4d55280ed3569a6f3036f6095c38e3d83fd283c113b533b42e6ad3554a03b4d5a6aa940769ab4d55280ed3569aa9e000fefefcbba83595c74a97ee0fcfed227d37ea7f583fccb0e6cfb3e58ed3c89e5b11561e619305f96db780c6e960f1756d679cbc6791e28a18c92cc1d82ec43a24a8a9d8e7f9015ded9f1e85423e35e1b60fa0dd9ac00de63753c00001736e400273e600051dab589c3bc51276e9a7251f22c3e3ff80bb42defd8194ab0822ce72c191bf6df9562784cb10f41b3a1700001585f1827360003c1680025e60002a1d256a4f8c54fa6ae76785c00003989fa700066cd0003734823e8c6013c7bb4887d11a0532bf63dcfd251a50c8bcc72711909c0d2def6c0065fc7186e718058518c028aee009f72350c8bc472711909c14e3bf104cdb2a5cfaf626862a7816106ca0b2a7743f340a9d9abf440a0047eea890406dd4230101f37715f6b9f625e6e53d8ab107f03e93e7a8a67334ea5491d95fce988b2aa32b8a11be9a1fd8d4322292e5ab573f225ce203b0097afee9db66dc4d080231810529a6ce229b0084da0c50caadf05e43f1510604913c27f9a0d83d735f253a998cb2799d333a004c3b8df5c97cbdf799a2a5e1b0ac2d832ce0f4d3d3c19d55ceba5582a5675d7a36c8b992e8e3917b0a673d12400ee0286e4e4a84a0bfac1c8d033e1289cf3b10602d1b6fcb7f866bb604658c5db51c0b905301cf9cd9f6ffe4e3ac85e6f580907a61393c3323d34f4f06755669b92054ebaa1763aea2f05d57062ad72ad1480074a55955d0b19db65e55f437e540a9d75d00f4e33b98001048b1ac08806d4d9d659f408a5d833dc7cb5996ed66e7f77a9e5a940b568be9ad6c7cbebaf5f0131d2f3514c17c746b97a9c8d6c7668a300472a2dec98ddb5369ca491caf576fd3b57ada846fc58da4265814882e6c314f7cd9a1c95fc24d23eb5787c86c1d35ebb0ce4b7739ab3a74cac228ae57abb768ae3f6b2bb313b195b3bcc1cfc9788520ace62a6092011b41462438c1a778663e3b3a6625775ade5900847f16a62a0288982227150240000127fcd801ffcf82f40aee009f4e6d4ca65937d5571e6375ff190383b59f0498db7a0792b311d6f5c11e9d729eea32531d9c2f598521afcff2512ba256a2d9cd4255559cfad2b0f671cf537b029e2a5e2bc212101fa4027fc85fae0960164f9025c8d7649542d67854b1e0ebab7fdd3e747bbe5bfa82547e154fe495d1f792c19f0065407d025b08370db985071c97c50fdd2a4d3f4d49ee0f4d0802e8527270ee715455e111ca02a76e8d928902af4a8896dcd79fa855a20ab30d6c26255dc222da978c75c2e3a008840043651001af476019477f6a96fdb3e3d0a847c6bc3750e967b2f035e721b69debb9bb8afb5d37b1050b279705f329f63547f450e31cb56b2d7715f58dfb6f45957ab54be2aa74b078bab6b3ce5bd4dbcb76bc37545e0000075ca0007ba600458659bab9bc297ba327faf607a40e681720a601ae9d335f2f33def56a1be40d94ffad70d92b26aec6091d1f20b4ea8083b9f356be39b59b17015ed2cb49412605c03b980875714bfff2b699d30fc3252aa80cfd1789e150921b158b596916dba2a4f05e34bb5d8c123b295846c90e82050363de28244cc2cc24728b7c2912fb551a573958032be9a6e390892b9a19f852124302ec7adde9a7a7833aab9b2cdfc16338f58beff48a842d5e650c5ca0551b3c5bf78665f09f2cc97a2d0e0fabcfc3c1a065a10594dda31b3b6da229899fb409fea6ee00dd8fe25786a27b86ef8f7d571c211460f6ecd9b462761e4bdb480460211582efaf626a5e2ec7403123aa7ae08b5bffe953d869648a9d39092d132f0e015255f5ecbc371dc00bd94538e4969850e6de7a680b1f2faebd7c04c74bcd4a3a46663dc863c6805bbbda8d2487183cb56ef62fc4241c57f7b48fbe0e57abb7e89c42fc523b816448ad94e0cac055b968b3d0e6e01db3b23e4de18580091616b4a59083751a3e217a4d08028322d4f41e46bf877558a802108c05698616d6862ebc61464b653813e0097aca4392b62fb7f6029cdb830bbad14eea347c41c7ca9aa79002eb342845bec56682037031ac92f6b206d1486a70ab897518d191a6dcb9fd21d831542370a2f0e200946ef280067400004d4000318dc1db729a29c70fd83262cee83e376edd9a573784c947cdcb82b5b0c2b37f0c998fd15d079aa0cfad5136d5cf41dbd5e045e9920aebb8027a64f0c3b9392a1282feab8324a6c367bff24d59cdcddf53a3a6d52a77ed9cb231977180a3202f7e624df87082a4755b6cb64abe84e6c2eff9010d7e2d785aba20e14f37b3f92b4be8432f0e013882789a50000ab9800096d3c89738869d570a42ddab17914e88f525a82929caa5afe0a8c92b9b0e3e667aeb4c1e028f5427e5947730a5300c6b14339a0f9fe51b7fd4a5c32dd37f42dbd1cf875c3a9909ed61f99d7619e7ccc3eeada6536a0026b14882b7396b419cc974bc42c7f27953df4a9d54c5845e1ba0af112ca9d39593488e6cc64150f7b1902720aab9d123193901b8a33467291c4dbe2bdb773f455a0b6468382bb03a61d81c2aff803026bd0156f3a1ea167bce0262786337e4b0ebb639206cd32a11ac3cba726f43529514e261e453899bf02aac7f3db5396f0021b551614c89cb81622313fee35641f486b8a34374c47f1756b23c2c5d39d42a40434b80ccfa399a1a2fba549a7c775cf80071dd10a2754d080295cdd85df0f1e215280a9b7ef354baff6447c5b32e342317d070a179bd733ffc5dea26fac705baee9c8540000941d66a82e60000703ef4d6b63e5f5d7af8098e979a8a5b6263f4f074bd5b9f0f84b17c83fb345e9d34f70e20ebaa03ae67cedb9266f9878606791f24e4ad6f4724cfe3602dc5f1b931fb044d63ef33c8ca912849641b925c1eb2dba0452ec210ef1822b3c663fe2bbe25fe63c42a386806657518d1912b81e8f0997b453757871d8b385c8fdf1322778ad42cb92778966f829422ba1529cdf401a0cd123057886002139810c5e217ffe0a40ec270a9e696a1fd2717300000001d90000c6601967833b4baa2188c13577e800de8bd629a8e61e727ffae465bbfb75c96702b0f1091f6abf90513dc9c95090938ede07bdc30037fb529b6ab83602bced768e90c764d15d1c027ec9f2666a33b098b705a780ea933fe3ba0dd4e75e4af1b289720bdc14b1a79b838efde2bdc737377cf69a84288849ae4d9d1bd04b07c8c74da822b75a5ae6380f55c0a77b8155811ea245fb6e7c433cbc6e1b198487665e50e5d3e7c198f6fc027ec9f26666052593e5907335735edc4b3a02ffe0f1a39f39d7680f9f30d83b5c1443b599bb7a12e7ff74bc13c931ce95c8a9e59a1ab6a4aecd9b4627ad527b83fa149c9c3c7bb4788ba32d182f3f5bbce8d6858041fce7d786e803fa07ce6558f2f607334461c587c27c6bc44b2a7512b622595b3e74d08023c5fb06c998bdca24b46e856724066d91f3800cf800000020300006fc00018e3bb6121ed4ac3500064c0000e780003340000b8000029c0000978000209000073fb4fe80a1c0c96388c9eb3ed769be332801ef19ce611b4be7bcd8c5bea1dbf7033e9aea8c7b2c4babe536158f3205faace16ae00030554863587000000c080000c400002bc8b7a2e71ef17c08a1540da05d9a0e45028e519a9ee265b734d5e3fb4b89cd19c68622d32810a0e431557b9a69f13f6ec9bd4d0de295a13b90800259d4da1d0bff98a5a8015c0000017b000054b9af97a2081ea03bc89fd0147f0ea1c42ab397ce86efce3fbe167b38a2ea2cac57eff5eccd148df820c4d752d6fc802e60311b1d73c0eec246cbc44b5e21416043600000282000090cce9b500bcd34f468ceaae74fcfc150d343c3a47d383f0720345270669b9ac7dff23454ea02ce71cff3a6d402ebc2835bef7d191dad72768647c31a36c1bac72b91ab5d43940df86ef640b78c851b9132d7479c87e52816e4e26a9ff8a4167afc6cd848e3e45cac11da0009d739607270c557763f05e1b8f2c6bacafb382119e112f91de9ad0efe366407c02030000017000006dc4f29f9d02c71dd35cb41c433fc26688a74248e82fd864496e4ae788c4f99db947f1fd47cef8046372171e77b62d362a30fbdf0fb1ae4061d0f54b61f36e25000a7f60f19091bc452e400003a6000035e0000f7800045a0001444d080200006200001cf0000000414e4d46d6110000630000000000a30400dd030064000000414c5048b6000000010f30ff1111c2911a498e5490d731ffff6bc1d81a6f778b8988fe4f407349eb9440b67b8f64c759f03ffe7770de844a4256e47ffc2f7fda33ffe37ffc8fff7deb69426dcf9bd93a006fc66bfec7ff5296894a670ebde07ffc8ffff1bf74ce9bf13a35905d41fec7ff7216d9152df81fffe37ffc8ffff13f54d684da9e37a1026f42cdf12654e04d28fec7ff3ed62cb8dc82f3bc19aff91ffffba422dbed02b2a5f91fffcb8f649bf7e5e282fff13fb3a2b9a4f50f1d5650382000110000d083019d012aa404de033e9d4e9f4c3fa4a2a225fe081bf01389656ee1768f5ba053d1272ce05668e7fc907cfbf233f7ff79379afe38fe32fcb449cfddcf9844d47f72fba4f7f1fe3fa267a19f980fd30fd5ef7a6feddfa73ee03d003fc07f55f4b7f688f400fe01fd13d27ff703e103f793d2335693c29fd87d9263fc57a4f1b23ff4ff87ff909fe8f56e467ab21dfa16df705ee0bdc17b7e9b252e5b7b2cb92e57574aeffeac8776244d0ade126f879cf8dff6cac997137ac41053bd965e53d5e9be1e9d0e60358bbca7abd37c3d103f42dbee0bdc17b82f705ee0bdc178a851842dbee0bdc17b82f705ee0bdc17b82f705e2a14610b6fb82f705ee0bdc17b82f70553117e7dc17b82f705ee0bdc17b82f705ee0bdc154c45f9f705ee0bdc14d08027b82f705ee0bdbf75a216df705ee0bdc17b82f705ee0bdc17b82f6fdd6885b7dc17b82f705ee0bdc17b82f6810ca2331b4c94f57a6b17a743980d62ef292c5b4c2e1e9cf4e2d51d990414ef659794f57a6f87a743980d62ef29eaf4df0f4e87301928900b42dbee0bdc17b82f705ee0bdc17b82f705edfbad0f513e2a5e6f8dff350dbfe6a1b7fcd436ff97d7b82f705ee0bdc17b82f705ee0bdc17b82f705ed30d2be341dfa16df705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc178a85146080fa16df705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b447241fadf75724988a0ce2d5d9a7cc5394761f1fec3e3fd87c7fb0f8ff61f1fec3e3fd87c7fb0f8ff61f1fec3e3fd87c574643bf42dbee0bdc17b82f705ee0bdc17b82f705ee0dc3a636685b7dc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bd4d0802c17b82f705ee0bdc1525937c3d3a1cc06b17794f57a6f87a743980d62ef29eaf4df0f4e87301ac0fb8af829decb2f2fca7abd37c3d3a1cc06b17794f57a6f87a743980d62ef29eaf4df0f4e87301ac5de53d5e9be1e9ce2c935abd965e5aeefd0b6fb82f705ee0bdc17b7eeb4427c7433ba5e6f8dff350dbfe6a1b7fcd436ff9a86dff350dbfe67ade800d13ee0bdc17b82f705ee0bdc178a87b2e516ea64590efd0b6fb82f705ee0bdc17b82f150a3085b7dc17b82f705ee0bdc17b82a98d3f8fadd4c8b21dfa16df705ee0bdc17b82f705e2a1ecb94293dafe43ed7f21f6bf90faa84edbd320829decb2f29eaf4df0f4e87301ac5de53d5e92c634c27db57bab92609a0d62eda6223f6f659794f57a6f87a743980d62ef29eaf4df0f4e87301ac5de53d5e9be1e9d0e6035837b0d2e95a52cbcb5ddfa169eb44b8b300e990201d320403a6372979ba79d7519d7b5fc87dafe43ed7f21f6bf90fb5fc87dafe43ed7f21f6bf90fb5fc87dafe43ed7f21f6bf90fb5fc87dafe43ed7f21f6bf8fcce280fe03bf427069685b7dc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdbf75bf936fcfb82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b4c37c8c216df705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0a6bf30c01685a7adfc9b7e7dc17b82f705ee0bdc17b82f705ee0b4d0802dc17b82f705ee0bdc17b82f1096fb82f150f65f9f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdbf75a216df6986f91842dbee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc154c45f52709a641048587466a4ae85ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdbf1e07e2b926032511fa6a96dd541a2a820a77b2cbca7abd37c3d3a1cc06b17794f57a6f87a743980d62ef29eaf4df0f4e87301ac5de53d5e9be1e9d0e60358bbca7abd37c3d3a1cc06b17794f57a6f87a74176d2fd58803177ff5643bf42dbee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f70553117e7dbf75a216df705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdbf75a216df69869685b7dc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f6986967a361d5c92ee2c5de53d5e9be1e9d0e60358bbca7abd37c3d3a1cc06b17794f57a6f87a743980d62ef29eaf4df0f4e87301ac5de53d5e9be1e9d0e60358bbca7abd37c3d3a1cc06b17794f57a6b17a74396c197eac877e85b7dc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f6fdd6885b7dc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc154c45f9f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705e29785dfb95d5c9301ac5de53d5e9be4d08021e9d0e60358bbca7abd37c3d3a1cc06b17794f57a6f87a743980d62ef29eaf4df0f4e87301ac5de53d5e9be1e9d0e60358bbca7abd37c3d3a1cc06b17794f57a6f87a3bcf705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b7e8b6fa3f15c9301ac5de53d5e9be1e9d0e60358bbca7abd37c3d3a1cc06b17794f57a6f87a743980d62ef29eaf4df0f4e87301ac5de53d5e9be1e9d0e60358bbca7abd37c3d3a1cc06b17794f57a6f87a743980c944a20a9d997153b32e2a7665c54eccb8a9d997153b32e2a7665c54eccb8a9d997153b32e2a7665c54eccb8a9d997153b32d71db4bf5643bf42dbee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b4c34b42dbee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705ee0bdc17b82f705edfb9eea3a0da5c0e6029a38782ca77b2cbca7abd37c3d3a1cc06b17794f57a6f87a743980d618967d1de60358bb821adf6d4ca77b237a9f41decb2dd956002e31ce575724c06b04f784ee0a5c9301447541f92f317720dfe4c3efaf705ee0bdc178a851805cbbffab21dfa16df705ed30d2d0b6f8fc4cd3c3edf9f1d41d88057e3bf936fcfb82f70553117e7dc17b84d08022f705ee0bdc17b7eeb7f26df9b16fa86c37c7dcbbff14a53cfb7eeb7f26df9f705ee0aa622fcfb82f705ee0bdc17b82f6fdd6fe4dbf3645294bf5643bbb81466bf3d9cbe6bf5643bf42d3d6885b7dc17b82f705ee0bdc17b4070e243eadd1fbef971424cf15eabe190cd4aa1ee5dffd501230121f36edfe34a37e8ba3f7df34a42681f8610b6fb82f0570f321ac5ddb69e7dc17b82f705ee0bdc17b82a98d43820d07fe3d4b544b42db5311710df1c7d190efd0b6f8a851842dbee0bdc17b82f705ee0bdc154c69fc5428a5c0e37eac877df1a0e2e183f66d4c8b21df9ea27bf42dbee0bdc17b82f705ee0bdc154c69fc543d8d88e76fd0b6fb4c34af8d4b303322c877e85a7ad10b6fb82f705ee0bdc17b82f705ed01c388e52846a7b0f8ff4ec3efeaf74f3bee5ec8d121ee5dffd5012479c3e7fcdbb09cb04bb1f55f0fe8800116bc5643bf42dbe2a14610b6fb82f705ee0bdc17b82f7053e7cc8637de433d590efd0b6fb510c9573e467ea5c8a237eac877e7a89efd0b6fb82f705ee0bdc17b82f7053e7cc82293cc59f705ee0bdc158303777038d6f81b2f705ee0aa622fcfb82f705ee0bdc17b82f705edf982dda22e098b3ee0bdc17b82b0606eee071adf0365ee0bdc154c45f597528cef7334c06b17794f57a6f87a743980d62ef29eaf4df0f4e87301ac5de52318cffb6e761f1fec2cecd7eac877e85a89daf0bc6f9b76ff348be77c34d0802b70c8a9389685b7da238ff5c5dfb95d5bfc6abfaaf87f55f0feabe1fd57c3faaf87f55f0feabe1fd57c3faaf87f55f0feabe1fd57c3faaf7d3cfba62e26cd0b6fb82f705ef636ff97d54c69fdc17b82f17c90201b002a7665c54eccb8a9d997153b32e2a7665ae3c9c8b21dfa16df705ee0bdc17b4c37c8c216df705ee0bdc17b82f705ee0bdc17b82f705531a7f705ee0bdc17b82f705ee0bc543d97e7dc17b82f705ee0bdbf1afcea9eaf4df0f4e87233c12214137c3d3a1cc06b17794f5402fd54f47f705ee0bdc17b82f705ee0bc543d97e7da19281c3dccd22741619800feff3e2594b6f790de89da173c2b4d823f1c537ad353a15f2882b65835eb623a0d4b2c52dda080379b4e66d753596f9190f4b35d64855f73ff0f8107ad9711d3c16c9e8133fe5ab3d95374b38346040000000000000000007076551fce7d69734ddfcaae191623e69e858aada0d838fc4c910def4332f8e236f3cb18e1ed15f12a8e791667b709b7271f0000802e4d0e180000000000c0eca9ba597766e1dbcea2e791fefd46e308e5b99a800000000000000000000000000003b7fb08dfffda279d6c2e095e9d4676f43890a47a87be789c30c13f5695189f4e0476fe17116bbfefde233d9c5737e25e1c1365248d69da1c32ec7800025300121dee1127614fcadd330760c4e558bccf77e54c7dd2fc1ddbe4b37d938ceae65be34532e183fadfe70ac87d8c03ea4d0802c0c4f2e2291bcc2eef1836d22c29903516005f588b59312c17c44787b211c75469c564b317c61859c7d26990473495a720088627b7744e479b4a74b694b9a1c300005ff561a000000000005422436b3cc13ce08e8555d9d2b590b6de00031ffb02476d40532efd2f0b801a500026b58a3763a7b3a53587c23137f60d49d18c155ad9b7f4e034e84f2dbd35f8c6bc86934a956ae72e52349a54da4d3fb14800593b0475b0b8258b73a086ff886b800351b00000000000203fd9c2baa51634eb685cf0ad08000000000000000437fb135fe60a8146617fd99d749518dc16b542ea8ac8301c9225e170005495c00000000003955da9d0fdbf4e5a79e40372cd46584940daeb69252a0038d269600000000000b2a3bbf9b39eb0fc4b84bfbaf2a17ed2cb5497d5b41d5c20ed738d9a4042a78fad58788af2f0ba3b58a37639d467b2a2ea8aa60d678cc9ef43b4ec3be015c58b2ded1cbd9a1bd5a450bb7d7a4c1475b6d61caaa4dc2f2591cc4be4f42c029350768a74d161065065589819f13e1811dcd7543d7732bedc4e4f16067620569590be89bdf86d7e5ab9f7e3f78102e79bdb6a7a8065589819f13e180d66b5d0bde14c2e93019d8815a563e397a8171e42cc5cfe7a07fdd38493e69db6e43a17b9bb20dc5101b3023b6929bea5d679501722e6e3ad734713175b795988b550c5e0000624947acf0261ab2427f8c7963c63cb1e31e58f18f2c74d08028c7963aa0312e8a9e006c357ddcb6d9d462d764e3f10f2f35c1a6a3c531cea1f1908f546af771c509f3cd7a6bdb05234617a54fa7690dc709284657096d5008588d9b849a0b9cb784d9ecaedf0171f3c13f400018be63c35941483438c8e3a6cbc34897861c361d2480001e3b0008109565e934f998e2abb4e4b534efac31f01ad8bc04224194ecf6e76bde16bf83d5a7a530fd7e5d72c95ca784a7701a041000bdfe15cb2d51c6565137446a827162a7210904e7986c023bd08c79552c74f591f7e40ea68988b542077e4d40609e2e378c2e84747d6c98441c722c3478cc34d253e2a88629037de086022b2e462407fa178332c0d71cde25219ffbd68b3e260704fbdc6b6961f4de429558a400070480ae9c0131e2b37beb18bcc7cebb33250708a81519fa21cf2568f4faa7769e8c80c5de1ec6c917e37550095592f1dba8193555a8fd888788d5662dd9e19fa94ca5455c64daf1e7f6ab3c03f62d034d08ba19a0f663589ef84da33f91cd1830045a3656360007f80b15522cbaea4b276b6b9b56aa2315f774271341f42ad94312ddd99fd41122de9ac10ffe1a4c53cd77fdfbc71f3c0e00000414e4d46680c0000840000e70000dd0300dd030064000000414c50485d000000010f30ff1111824123498ad6d9f95775f7cc0c15d1ff09c85b9e76426cb0ff29546cb8fff99ffff99ffff99ffff99ffff99ffff99fffb1459eaffff99ffff99f4d0802fff99ffff99ffff9df8d2bdafdcfffee5fd1d92386fadf5faa2d3fe9000056503820ea0b0000901a019d012ade03de033e9d4ea44d38a523a2233fc803101389676ee1772f08ffbb5df38159f79fef71f49fc84fdffda85e6ff917f92fc9bbdb3c34dfdbfdc07b8efeabd181d003cc07e92fea6fbd7fe007b80f366feabe893ec81e801fabbe931fb6bf093e911fb1dabffe16fea5ed2f9733d65cbff4ff45cb5600633f9bd28d733f9bbadccf40451ab9e8965e80420df038b8e55483fe7e764ba7046f56d3b26847fd53ff01795520ff9f9d934261ff3f3b25f8de72193775b99e808c8710a6c2005b75b99e808cbae9bbadccfdb855e416bfd9454a00b6eb733d00dfdee22b6eb733d011975d3775b99e808cbae9bbadccf40465d74ddd6d0f4b3005b75b99e808cbae9bbadccf40465d74ddd6e67a0232eba434bf59b4ec9a1205404604130ff9f9d934261ff3f3b2684c3fe7e764d0987fcfcec9a130ff9f9d934261ff3f3b2684c3fe7e764d0987fcfcec9a130ff9f9d934261ff3f3b2684c3fe7e764d08d1cdcc5d7560abc82d7fb28abd9455eca2af65157b28abd9455eca2af65157b28abd9455eca2af6515684233d011975d3775b99e808cbae9bbadccf40465d74ddd6e67a0232da4233d011975d3775b99e808cbae9bbadccf40465d74ddd6e67a0232da4233d011975d3775b99e808cbae9bbadccf40465d74ddd6e67a0232da424d080233d011975d3775b99e808cbae9bbadccf40465d74ddd6e67a0232da4233d011975d3775b99e808cbae9bbadccf40465d74ddd6e67a0232da4233d011975d3775b99e808cbae9bbadccf40465d74ddd6e67a0232da4233d011975d3775b99e808cbae9bbadccf40465d74ddd6e67a0232da4233d011975d3775b99e808cbae9bbadccf40465d74ddd6e67a0232da4233d011975d3775b99e808cbae9bbadccf40465d74ddd6e67a0232da4233d011975d3775b99e808cbae9bbadccf40465d74ddd6e67a0232da4233d011975d3775b99e808cbae9bbadccf40465d74ddd6e67a0232da4233d011975d3775b99e808cbae9bbadccf40465d74ddd6e67a0232da4233d011975d3775b99e808cbae9bbadccf40465d74ddd6e67a0232da4233d011975d3775b99e808cbae9bbadccf40465d74ddd6e67a0232da4233d011975d3775b99e808cbae9bbadccf40465d74ddd6e67a0232da4233d011975d3775b99e808cbae9bbadccf40465d74ddd6e67a0232da4233d011975d3775b99e808cbae9bbadccf40465d74ddd6e67a0232da4233d011975d3775b99e808cbae9bbadccf40465d74ddd6e67a0232da4233d011975d3775b99e808cbae9bbadccf40465d74ddd6e67a0232da4226a3c840db97e98bd2c3c851e3a06dcbf4c5e961e428f1d036e5fa62f4b0f21468b5e67a0232eba6eeb733d011975d3775b99e808cbae9bbadccf404528b5e674d0802a0232eba6eeb733d011975d3775b99e808cbae9bbadccf4045281cb147a1a5802dbadccf40465d74ddd6e67a0232eba6eeb733d011975d3750d2f4e7e764d0987fcfcec9a130ff9f9d934261ff3f3b2684c3fe7e764d0987fcfcec9a130ff9f9d934261ff3f3b2684c3fe7e764d0987fcfcec9a130ff9f9d934261ff3f3b2684c3df058da95e808cbae9bbadccf40465d74ddd6e67a0232eba6eeb733d011975193e9600b6eb733d011975d3775b99e808cbae9bbadccf40465d74ddcaa8fffd011975d3775b99e808cbae9bbadccf3f861206b20df038b8e55483fe7e764d08d1a8ebd940aa41ff3f3b26847d69619cf27e8f1d2534261ff3f3b2684c3fe7e764d0987fcfcec9a130ff9f9d934261ff3f3b2684c3fe7e764d0987fcfcec9a130ff9f9d2be77c4b3bd41fe1ffc3ff81cba25b825e67a0232eba6eeb733d011975d3775b99e808cb69064698bf3f726e4e37f081a6300b2005b75b99e808cbae9bbadccf40465d74d6ee21034c5f2e97e92a763bf4c5f02eba6eeb733d011975d3775b99e808cbae9addc420698be5d2fd254ec77e98be05d74ddd6e67a0232eba6eeb733d011975d358da8bb334c5e961a2d73ced0e181b7300b2005b75b99e808cbae9bbadccf40465d74d6ee21034c5f2e97e92a763bf4c5f02eba6eeb733d011975d3775b99e808cbae9addc420698be5d2fd254ec77e98be05d74ddd6e67a0232eba6eeb733d4d0802011975d35bb8840d317cba5fa4a9d8efd317c0bae9bbadccf40465d74ddd6e67a0232eba6b771081a62f974bf4953b1dfa62f8175d3775b99e808cbae9bbadccf40465d74d6ee21034c5f2e97e92a763bf4c5f02eba6eeb733d011975d3775b99e808cbae9addc420698be5d2fd254ec77e98be05d74ddd6e67a0232eba6eeb733d011975d35bb8840d317cba5fa4a9d8efd317c0bae9bbadccf40465d74ddd6e67a0232eba6b2b330ebcfe98bd1f726ee4e7c721036efdadccf40465d74ddd6e67a0232eba6eeb732a72fca8f1cef3d935e4dfb0f21487ffa0232eba6eeb733d011975d3775b99e806fef0c0db97cba5fa4a9d8efd317c0bae9bbadccf40465d74ddd6e67a0232eba6b771081a62f974bf4953b1dfa62f8175d3775b99e808cbae9bbadccf40465d74d6497459a908f210272845254ec77e98be05d74ddd6e67a0232eba6eeb733d011975d35bb8840d317cba5fa4a9d8efd317c0bae9bbadccf40465d74ddd6e67a0232eba6b771081a62f974bf4953b1dfa62f8175d3775b99e808cbae9bbadccf40465d74d6ee21034c5f2e97e92a763bf4c5f02eba6eeb733d011975d3775b99e808cbae9addc420119a894d0987c6c57c54e00420de04933b97428f1e514802dbadccf40465d74ddd6e67a0232eba45844c651f63dcd545b000f961e4290fff40465d74ddd6e67a0232eba6eeb733d00dfde18138e9e0a27a58790a3c79454d0802200b6eb733d011975d3775b99e808cbae9161131b8752b93772fd317a5888d14802dbadccf40465d74ddd6e67a0232eb857dff9802221c5b7d82ab4e4d0987c6c5fbcc9e4206dcbf4cf98bccf40465d74ddd6e67a0232eba6eeb732a73acefb9fbf71e98bd2c3c8565968a4016dd6e67a0232eba6eeb733d01196d2119538e9e428f1d036e5fabf33d011975d3775b99e808cbae9bbadccf3f979ecaa88740db97e98bd2c4468a4011250654d0987b6ba51b95520ff9f9d934261ff3f3b2596fd2411100a907fcfcec9a130ff9f9d934261ff3f3b268ecda4581b036e5fa62f4b0f1c000feff3c9d343b2fc41e4544f8ca743241c7bb97585d360d9ce27a8281d5437e43aca141ca541b6486386905c972f3462236aa8b5e5fac9ffff9e31c9ba6c4de0627ef079fd5ae803723ec87e38de10360095a17727c9fe8e5b3ed45248b4a559c92cc7c63eb5c09da79252b173c470000000000007ea75cead6b1ad40c8d0cfbe7f0bf325f6a0446867de2f85ca2a785f38116ef7e737972a4ff42620d7238db9aac971f2d800006a704c88996080000000013f1488a4c407b76212c526203dbb1096293101edd884b149880f6ec4258a4c407b76212c526203dbad5000000000000000000000000000000000000002d92000000000001870428fc8d41d29131f5973a3f235074a44c7d65ce85c0000000000000000000000515f9c00000001f380dc17f44d08027b694e0d7b287b61520194801e7fe4e8f86fa49fff763b2aa6be52bd92f2900802e77321cf46000000003a3b22cee611ded951a4fe47712e9dcd7d0e0101b04c1a5e99b5843ef691b984639dfc6e3048ebf07639c50dc9a90289df24725e0825ffcc61f9dcbff898ffc3973032496a4ab384979443ab23dbd850754208b7fec53ffd882edee9105a6792b94c288699a0f476f0d75ef2b44cc4abda20b7dbac1b2864900002ca02d6058c5772a3de5c396a7620a2931d7b3e963dcc7775df4ceaa8086811b025604e40a00145029427297300005a9be297c71d74bb81925ce361dac044aa61ca3580ab815602a6279c84b1eb505962fa1d65024ba4f5007a80f607eb1dae4b8d9611cdb4751a3f7c01cd7faf2b45ca11e98a75696e47a8acef8d3fa9a981ac6e847f8f647529258e4b0c76b92e36576db88f67c8bf5db6c40cbec172693b81883f7cc6160dce47e95844065511b51db40a8376800b301cbd652db47c58e5a9da5baba7828ba4d95b751e4879d02e2d5fd0e1cd24d57f6e66c7a04e62ae578717786c805a864378db03f5f802324acf595121828004040862ba0a6d3e79884fe73d1532cf856ca0df85ed5cb2eb346edd86959b5c59042d5466467aa5b36b151a9b38dbc262b22afa73ddcd4cf767c2b6506fc2f87990e4261d95cc5a447df5ec0000414e4d4628160000210000000000690500dd030064000000414c5048f10000004d0802010f30ff11110272db4892a404c6b0703d3c8b4fdd55736e6e4544ff2740aff52383411c970310c7a4ffe7ff25735415c95423fd3fffef3aee84fe9fffe7fff97ffedfbf185415fb5055c468aa8a795415fe9fff77b3a52ab2ae17d7fff3ff92356a7dac4355fd3fffef4e8cda7701aa3b85416c3f0051660dd1c91d1045fa7ffedf551755c5b4a74855fd3fffefb28cea466e74c293d4e9fff97f391ab52fd6a1aafe9fff77a5a52a4a543f0ed688a40951640dd1cb1aa24cffcfffbb0343743620966de89f30701cd5edfff97f575d8d48ae1ab91162f4568822fd3fffefaa6be106c4f29cd7effff97f39965eeb3f3300565038201615000050cb019d012a6a05de033e9d4ea04c3fa4a322257ce823f01389676efc7c99a8d53fd1ff003bc8605ff359490f5c659d3f39fc55fdffdcf8e77f8a3f941c985dabc356fbd7dd9fbdcfe9dd201fe77f9074d5f301fa45fa65ef63fcd7f437dc07fccf500feebfd47d67bfd6fb1dfa007f00fe71ffdfd623f6abe0e3f77bd993f5c3ffff676f4ebf433fb37d947d3be4bb0ddfddfac0b4d4c8ffd3fe1ffe52ffa0d5eea32c2481821de7da3e6d5ce344b7bcfb47cdab8e6fad54593bda8a321293d2d2d12deebedcbfce9a3dd5c18bfc8111515c078d61267ea685ff5dde0e5ebb246d2c4ce2ded5c24cfd4d0bfebbbc1b67cdab9c6896f79f68f9b5738d12def3ed1f34d746ae71a25bde7da3e6d54d0802ce344b7bcfb47cdab9c687b27a3e6d5ce344b7bcfb47cdab9c6896f79f68efd4da10a0a0c10ef3ed1f36ae71a25bde7da3e6d5ce3392d0a0a0a0c10ef3ed1f36ae71a25bde7da3e6d15131ab03043bcfb47cdab9c6896f79f68f9b5738d12d9ed3cdab9c6896f79f68f9b5738d12def3ed1f3483b8926f41f05d82670862d2d0fa0a3285ff5a3eaf48ebb3f5342ffaeef072f5d92369626716f6ae1267ea685ff5dde0e5ebb246d25a79b5738d12def3ed1f36ae71a25bde7da3e6d5ce2ac483043a920661a3e6d5ce344b7bcfb47cdab9c6896f79f68f9b5738d12def3ed1f36ae71a25bde7da1df42d12dad7eb49e8f9b5738d12def3ed1f36ae71a25bde7da3e6d5ce344b7bcfb47cdab9c6896f79f623f57676fc36725a1414141821de7da3e6d5ce344b7bcfb47cdab9c6896f79f68f9b5738d12def3ed1f1e8136ae71c4e5b7593bf5b716f6ae1267ea685ff5dde0e5ebb246d2c4ce2ded5c24cfd4d0bfebbbc1c0d1f36ae71a25bde7da3e6d5ce344b7bcfb47cdab9b6e660608779f85456ab9c6896f79f68f9b5738ce4b42828283043bcfb47cdab9c6896f79f68f9b5738ce4b42828283043bcfb47cdab9c6896f79f68f9b454eb2d2d12def3ed1f36ae71a25bde7da3e6d5ce344a70d1f36ae71a25bde7da3e6d5ce344b7bcfb47c7a04dab9c6896f79f68f9b5738d12def3ed1f36ae719c968505050608779f68f9b5738d12def3ed4d08021f368a9d65a5a25bde7da3e6d5ce344b7bcfb47cdab9c6894e1a3e6d5ce344b7bcfb47cdab9c6896f79f68f8f409b5738d12def3ed1f36ae71a25bde7da3e6d5ce3392d0a0a0a0c10ef3ed1f36ae71a25bde7da3e6d153acb4b44b7bcfb47cdab9c6896f79f68f9b5738d129c347cdab9c6896f79f68f9b5738d12def3ed1f1e8136ae6d26ba633d0bfebbbc1cbd7648da5899c5bdab8499fa9a17fd7778397aec91b4b1338b7b570933f3c0f70999e85fce8d5ce344b7bcf811d226716f6ae1267ea685ff5dde0e5ebb246d0ab512608c316cfd4d026dca09e6d208ed1f5bb4ca017df94699402fbf28d32805f7e502ff0d3148fe8850bb3b850506084aa9d64dab9c6896f79b06f15624208158181981ee30356546d6151b58546d6151b58546d61449cb3ab1fec10ef3ed1f36865f24068f9b5738d129c3477ed3cdab9c687b185c1de5cfef3b3fbcecfef3b3fbcecfef3b3eafd98a52c2f7f21de7da3e373e8dcbed1f36ae719c968494a3c246b117fd81cbd75575b18ffac84c125e6bc2a3062f8ff8f7fd18be3fe3fe3fe3fe3fe3fe3fe3fe3fe3fe3fe3fe3fe3fe3fe3fe3fe3fe3fe3fe3fe3fe3fe3fe3fe10e33c24f587b452f54d6c277f8fbd36c37777b570933f5342ffaeed64ceec97fd776c9362bfc2828283043bcd0d4f5559bd8cdb6e945fba8b0e6367e2ff7111016dd28c022b86996669818c518a3146292e94699402fbf28d34d08022805f7e51a6500befca00bf8499fa9a0f0a9791bef7defbdf7bef7defbdf7beedf9a9d92339e26716f6ae1267ea685ff5dde0e5ebb246d292ce2ded585db0ff1ff1fb6ead5738d12def3ed1f36ae71a25bde7da3e6d5ce344b7bcfb47cdab9c6896f79f68f9b5738d12def3ed1f36ae71a25bde7da3e6d5ce344b7bcfb47cdab9c6896f79f68f9b5738d12def3ed1f36ae71a25bde7da3e6d5ce344b7bcfb47cdab9c6894945d319e85ff5dde0e5ebb246d2c4ce2ded5c24cfd4d0bfebbbc1cbd7648da5899c5bdab8499fa9a17fd7778397aec91b4b1338b673292defe43bcfb47cdab9c6896f79f68f9b5738ce4b42d8c331c76e26606638edc4cc0cc71db8998198e3b71330331c76e26600620a0c10ef3ed1f36ae71a25bde7da3e6d5ce2ac483043bcfb47cdab9c6896f79f68f9b5738d12def35affed1f36ae71a25bde7da3e6d5ce344b7bcfb11fd4ae14141821de7da3e6d5ce344b7bcfb47cdab9c55890608779f68f9b5738d12def3ed1f36ae35f771ded5c14c1d9cfd5c55ded5c0ee23e33d0be5052dd5df133912888374bd53648da5899b2c356716f698da5349dc28283043bcd1bbfe93bb1828576732a3e1ce7d3114b13431cee14141821de7da3e6d5ce344b67b4f5b7c839837241d95180e085ab56e1033af7e51a611a96c4c3e98f9b5738d12de9204d35d25fc4ef9b5738d12def3ed1f36ae71a25b3da79a6ba346ec523704d0802c944ba8505040e71e240655d9dc282830427b4f1e8136ae71a25bde7da3e6d5ce344b7bcfb43beb5a1df42ae90f6d241c57487b8779ad8376dd1b303043bcfb47c7a04d35d75581821de7da3e6d5ce344b7bcfb47cd35d75543997d4a8ddc29b2da17c4f7f1986f33a57dd1f36ae71a25b3da78f412f2456b8b7b570933f5342ffaeef069de1dfc8779f68f9b5738abe7899c5bdab16e4aaf9ce39c731adf12be738e71ce39c738e71cc6b7c4aeafe1267ea685ff5dde0e5ebb246d2c4ce2dceb7cc09b246d2c9c83043bcfb11fabb3b85050608779f68f9b5738d12def3ed1f36ae71a25bde7da3e6d5ce344b7bcfb47cdab9c6894e1a3e6d5ce344b7bcfb47cdab9c6896f79f68f9b5738d12def3ed1f36ae71a25bde7da3e6d5ce344b67b4f36ae71a25bde7da3e6d5ce344b7bcfb47cdab9c6896f79f68f9b5738d12def3ed1f36ae71a25bd0056ced1520c1d826716f6ae1267ea685ff5dde0e5ebb246d2c4ce2ded5c24cfd4d0bfebbbc1cbd7574550b6cd64ddbb9b6ded1d95aff5aea7d2ba4748e91d23a4748e91d23a4748e91d23a4748e91d23a4748e91d23a4748e7455c672369c8499fa30e915f30505060877a66c6198e3b71330331c76e26606638edc4cc0cc71d76e8d1bb14c0694699402fbf28d32805f7e51a6500befca34ca017dcc69e6d5ce344b7bcfb47cdab9c6896f79f68f9b5738ab120987cb9c6896f79f68f9b57384d0802d12def3ed1f34d746ae71a25bde7da3e6d5ce344b7bcfb47cdab9c687b27a20e38f47cdab9c6896f79f68f9b5738d12deec66cbed1f36ae71a25bde7da3e6d5ce33890a5899c5bdab8499fa9a17fd5d5b23866091eee91cd7468fdbfe45c577b8e2a606cedf44beb4bd53648da5899c5bdab8499fa9a17fd7778397aec90f7f5e99d675342ff29a08779f68f9b5738d12def3ed1f368a9d69b6638edc4cc0cc713e4208d2d2ec7709eda9a1981821de7da3e6d5c7b4ab03043bcfb47cdab9c6896f79f68f9b55e062d2d12def3ed107197c5b1c86d5cda858824ee14141821de7da1df42d12def3ed1f36ae71a25bde7da3e6d5c7b4f9b47cdab9c687db02869da1c8282f4edb0d65a5a25bde7da3e69ae8d572605fbe8ef072f07eda6b2d5c24c9be69c4a33f5339c122168307608ecb7b570933f5342ffaeef072f5d92369621863470f5c622927b57b57b57b57b57b57b2ddc9c52830301f32c3c4b866f20f2e0d2cc25ed5ed5ed5ed5ed5eaf52c6812b85050608779f68efda79b5738d12def3ed1e185e3b8505060877630db641417c267560608779b4e57a8f9a814cfe7bf90ef3ed1f36abc0c5a5a25bde7da3e6d5800720a0c10ef3ed0eef83169687e1349d439f68f9b5600207fb4786187bbde7da3e6d5ce343d93d1f36ae71a25bde7d8b717e344b7bcfb477e9dfabb3b7d83095439f68f9b5600207fb4786187bbde7da3e6d5ce3434d0802d93d1deae97aa6c91b4b133679c87608775879c665fcf39c738e71ce39c738e71ce39c738e71ce39c7243bfee4e19af8970cddb241433a246055ce10531fd2becee1414118b58395f45dcdadb0dde8b6bd3b9b604ae57df94d5738d12def34643c4d42ffae4d97da3e6d5c7b4ab03043bcfb47cdab9c6724b7197cdcef18f9738d12deec67282e8d1e23cf59efe43bcfb47c7a04dab9c6896f79f10c368f9b5738d12def3ed1dfa8b8bedb997d4a7fda3e6d5c7b4f98861b18b4aeac0c10ef3ed1dfb4f36ae71a25bde6b5ffda3e6d5ce344b7bcfb43bc2038f853f303365f68f9b55e07326affe37340528283043bcfb11fabb3b85050608749026d5cda4d752e2bc4b8670ce19c338670ce19c338670ce19c3379d63a30a14564a332bc275d68171e545a2fa360afdfb9c734a05dd249ed1f36ae3468752fa2ee6d7e68079c0fdefbdf6fb5f52a34d283043bcfb477ed3c6bbc0bdfd1bbb18285767347512deec918397aec2ffe31f2e71a25bde7da1dde71971434e57e4a821de7da3c2faa6578030e858824ee14141821dd8cd96b5ffda3e6d5ce344a70d1e185ad0456d4115b50453cd13d10eceac0c10ef3ed1e1861ed12143d3b6c359696896f79f623f56e7d0b44b7bcfb47cd35d1a376112d4115b50456d3fb3a9f98567560608779f68f0c30f6890a1e9db61acb4b44b7bcfb11fab73e85a25bde7da3e693f6fa19c899c5b6250960094d0802f78ecfef3b3fbcece23cb9e3cb96bfd6ba8297fb170f7f21de7da3e3f5849356b15e33c4b84651027806e38418f47cdab9c6896ca4c169750933f418a82c708be9237c8b7bebd7ab44b7bcfb1327d0bfeba8b7ebd7648da5899c5bdab8499fa9a17fd751495a517a5a5a25bde7da3eb769925eda9a1981821de7da3e3e30c5c060d61fe3fe1b7b3b85050608779f68f9b5738d12de9204dab9c6896f79f68f9b5738cdf168473ed1f36ae71a25bde7d88fd5d9dc28283043bcfb47cdab9c6896cf69e6d5ce344b7bcfb47cdab9c5345d162828283043bcfb47cdaaf031684bfd59867a17ccc233ef6ae1267ea685f328e747febbbc1cbd76485c709313b85040e660608779f68f9b5738d12deeb8130f8608779f614a1b125ea9aa9aee864df4c67a17f2e800febe3cc45bf1e4dc8d838604ae2930e8f80f0521fda696759d4943fd77e3509bca686d97caa10bd4f242658fddd5d2fb642de6a96b5421c06a78df0b4867cad6d814ac0b9a8d7da9fa20016ad4000003c800004100000d1c031caed9652b4e0e5011ae3fae3946609f70d0042ddccdd4906682b5953b21f3a67fe34d3b03f5018a5d1227dfcb11cd691458000000261ea18f3f18f3f18f3f18e08005dc0000000006a80000d57eec2c9f1661a096c37fc9f25b28b526800604e00000000000000000000000000000000000000000000625c2eaefc699b4818408ca96cab40e1db1654d0802763c741004526a78e4b6962bbedd429e599812ad9d1fbd7cb838063898f3f722beea23eea23eea23ec404d361263b73c3fd8000000009a80000004e1cc1f64cb4ac6b98c792b362a0c81d5a969b672b01bf39f533bca39eecd1e705730994a218cde096fd6e60ac381a11769ee84984540538000000bf2d1a22441b59dce8ada042860fad3598b1c8740766b605490cd0057081d4ef43c53c345282cea1196bdb874226d75d4a9e8daaba514793c03919c3567a2bb78ad5e6a6b8dcb44b09a343b864832ffe2d33629a26db75427420aedf3bd7211a65131de8d6159b4d366418395f30ddfd55e02f78103921018d3c38b40b270322ec597557b03a32caaa9f4e34f1349c6c4a554f4c8fe904251600000000000000000000000000a6cb0860198e09a005f322273e99ac0f9d93a9a48c7106e62deb73c381416e00c348befe0000000000000017f7a5ac0331c13bedfba9b45489bfe8c39c0482fedfd5ecc2b33823ba252f3eb4f1f3308513671a7f93120d45d3a193c75d1d5584c8037fb85b0fd90726b9fa43961c93cccfa9b8779fd4bb50f7e90cec4669224305aed0ddee5125e4b89d471c9e5723cb04c31fe2ebbb1c993e114365ca56dac2a95b70b2bc007850f3ef5f17988f13e00be15ceae791874e22a72b1312562d075c3b73c987e3008a9cac4c0fc4516575ac17a855cf177d7ccbd7e518a8a66d4b5868b3973c380d2925dc780004d080200046c00006c81538e15d8082e552ca95cf115e3f3d944f2ccc0563ee9c9f100062bfe00000000000000000001e27b33412f88e187747ee17583f581f784252895beccf8362c5d74aea6fae9a01bfca8dc824a2d4f2ba041c8c9ef7106bfd44c8537b9880b768202800c800b47f5cf0e0000000000000000c8c9f168f68bb05bf7b96b4df39146199c4f5009f726555b396baf57c8a540b3f6c1df3478f9611c1077c9c19e582e422f983659774c8fcb1347434affe2ea55c4a32cd24cc8b0fcb76f4aceeada6213d239671909b5ed1d54f201dfc6b885c3097db6d41a14064013373c395dc154fa000006c80000276191125b1c3dfa4382373a4e4c43f528db77e48ca09dc4b20b433ed51ca82392b558fa1129c3b79078fcaa0dc3c21958cc3e6de90bf9feee38cf5b3c31111797dd59e396758cea8d9c99e2efd23f951b904945a9e57490a33b97cc5d927808dccb34b256f4f3052c54886d010a7551e658761e7c21d1b5f16aa008e0f4f9b86fd3437260c87de8e1831548e346e373dc2318dc5a77eea29c02584b7e03eb846c0580da86eda0f8d42191aa72026f5283822f4092800a704a0013267cb06fffecb6ac04170b4d0b07c42d2c2e3b7a8350dd9ba676fddff774d4acf93d7138bfc77ccf204f19d23e2d5bf4ce5e6da1394372e52a1969beed11b549ce2e697cf250cdd78110c918659def8f9af32a0314d4871eff4f7f2f1423834d080207fe10c75fe6d44a88e15df12111081553f54553e89fb9e4eb2e098a82a00c5c58be9388cc135666451480365e6d19cab207a837adfbba92730fba3a478b81e4809d95a3e45485964db98d1e1cb26bb1c1833dd1ecf910ec0cade3e86cdd01154da2c8101aa02dc7b1a43e15a0b94d7bfc73df51d99cc54c09ffca903c9090564ce6943d951015de9e86956c90d22edae615ff98476519dbf8d1e676f5d021aa0c66557e457bb8fe4bc4b8743205f36c2bcab58712805dbf8e0bca4f1aac30be380eb0ceb09741c710dc837d77b7d6e1e3a0a8c006c2396f71adc3845ac7906339234b5933d2e7a0ea42b4017d0000037006e00010705efdec837fac0efb702766fe328fe9c4b2d4dfcac72e70d0d9f3a560308534846fbd02a112f0b4100e75eec7d36286b4894cb7aca92d306d1784fc10046c01b672113c63e830a8a261d3199ac6727fde48e518a7d3865b90f8ebd8af2633903fc059b39967b7e500000d3aaac9f6e30188276dc0181e2e940553e8000838000015f239df903cd2ceb3ad528efd9c444ffff95c3a3c13805574dd5b1195962c0c3390bab78ec600f98fe11e2c1eccd108b03945c922ddffab231b4cf174224470aeea4c7485df82800000414e4d4696130000210000000000690500dd030064000000414c5048bc000000010f30ff11110262db488ea4012eb04ebd32eb73756ecc99ef1f45f47f0272ad0f154cb471cb451b4d0802a6f81fffe37ffc8ffff13ffec7fff81fff83358f57dae07ffc6f0f2c6d14763df33ffef77f80d2f23ffeb7d5a5b4311ea58d9a27daa3d7c28bfff1bf9d2da50de395cf8f87296ddcd3a3d2f23ffeb7c5d563d1e43592118bfff1bf4a4bd9f10c94b6a489765dfc8fff957d39c02a2ada75f13ffeb7bd16ed4eb957e67ffc8fff7da2bcf81fffabb4941df33ffeb77b966bfd670656503820ba12000050bf019d012a6a05de033e9d4e9e4cbfa422a2247c184bf01389676ee1769eaafe67ac17fd386f0afe9cfe91f8c9fbffb92bc63f1b7fb0fec1f241f7130cbfee1f78fef3bf8ff4807fa5fe5bd2afcc07e8b7e94fbcdff0bfd17fe1bda01fdb3fbbfa407b007a007f00fe91ffffffffb407ecdfc0ffeeffb40febd75fff4eff4cbfa9fd86fd5fe37bee5e09bbfaf91ffa7fa6c7f99bcf106e53bb0d6a4e4ee86b527277435a9393b9cdeeb55163ad7889fdc8449fdbf9b70f941197d8d04fe45cdc743055089bd4c40f1fff91737cb71737cb71737cb71737cb71737cb71737cb71737cb716121ad49c9dd0d6a4e4ee86b527277435a9393ba1acc2bf1f353bb0d6a4e4ee86b527277435a9393b9d1ea407aa5a5dd0d6a4e4ee86b527277435a9393ba1a6711e1f353bb0d6a4e4ee86b527277435a9393b67ddd6a4e4ee86b527277435a9393ba1ad49c9dd0d69fb6cb9a9dd86b527277435a9393ba1ad49c9dd0aa03527277435a9393ba1ad4d080249c9dd0d6a4e4ee86b526e8c3ad77435a9393ba1ad49c9dd0d6a4e4ee86b30afc7cd4eec35a9393ba1ad49c9dd0d6a4e4ee86b523ee73ba1ad49c9dd0d6a4e4ee86b527277435a91f739dd0d6a4e4ee86b527277435a9393ba1ad49c9dce8f5203d52d2ee86b527277435a9393ba1ad49c8aa11af003d52d2ee86b527277435a9393ba1ad49c9db3eeeb527277435a9393ba1ad49c9dd0d6a4e4ed9f775a9393ba1ad49c9dd0d6a4e4ee86b527277435a7edb2e6a7761ad49c9dd0d6a4e4ee86b52727742a80d49c9dd0d6a4e4ee86b527277435a9393ba1ad49ba30eb5dd0d6a4e4ee86b527277435a9393ba1acc2bf1f353bb0d6a4e4ee86b527277435a9393ba1ad48fb9cee86b527277435a9393ba1ad49c9dd0d6a47dce77435a9393ba1ad49c9dd0d6a4e4ee86b5272773a3d480f54b4bba1ad49c9dd0d6a4e4ee86b52722a846bc00f54b4bba1ad49c9dd0d6a4e4ee86b527276cfbbad49c9dd0d6a4e4ee86b527277435a9393b67ddd6a4e4ee86b527277435a9393ba1ad49c9dd0d69fb6cb9a9dd86b527277435a9393ba1ad49c9dd0aa03527277435a9393ba1ad49c9dd0d6a4e4ee86b526e8c3ad77435a9393ba1ad49c9dd0d6a4e4ee86b30afc7cd4eec35a9393ba1ad49c9dd0d6a4e4ee86b523ee73ba1ad49c9dd0d6a4e4ee86b527277435a91f739dd0d6a4e4ee86b527277435a9393ba1ad49c9dce5cccd3d5fc44fee4224fe4d0802e4224fee4224f93c1993cf721127f721127f721127f721127f721127f721127f721127f721127ca11ad49c9dd0d6a4e4ee86b527277435a9393ba1ad59447234fac3454728ef6cfbbad49c9dd0d6a4e4ee86b5638b4bba1ad49c9dd0d6a4e4ee86b527277435a9393ba1ad49c9db3eee99c574ac347527277435a9393ba1ad49c9dd0d6a4e4ee86b527277435a9393ba1ad49c9dd0d6a4dd1875318c09b6e1f353bb0d6a4e4ee7399fb40d73fb907817e030449fdc8449fdc8449fdc8449fdc8449fdc8449fdc8449fdc8449fdc8449fdc8449fdc8449fdc8449fdc8449fdc8449fdc8449fdc8449fdc8449fdc83d390da691b8a45f9154bfb61fd10787cd4eec35a9393ba1a5b7ec3bdcf275dd0d6a4e4ee86b527277435a9393ba1ad49c9dd0d6a4e4e2d94df2dc5bc2bbc5cdf2dc5cdf2dc5cdf2dc5cdf2dc5cdf2dc5cdf2dc5cdf2dc5cdec7b5868ea4e4ee86b527277435a9393ba1ad49c9dd0d6a4e4ee86b527277435a9393ba1ad49c9dd0d6a4e4ee86b527277435a9393ba1ad49c9dd0d6a4e4ee86b527277435a9393ba1ad49c9dd0d6a4e4ee86b527277435a9393ba1ad49c9dd0d6a4e4ee86b527277435a9393ba1ad49c9dd0d6a4e4ee852b9fc906bc00f54b4bba1ad49c9dd0d6a4e4ee86b527277435a9393ba1ad49c9dd0d6a4e4ee86b5272773a664ad49c9dd0d6a4e4ee86b527277435a9393ba1ad49c9dd0d6a4e4ee86b5274d0802277435a9393ba1ad49bd5af003d52d2ee86b527277435a9393ba1ad49c9dd0d6a4e4ee86b527277435a9393ba1ad49c9dcf043d52d2ee86b5239974a224fed91008128b5e227f721127f721127f721127f721127f721127f721127f721127f721127f721127f721127f721127f721127f6c546552e8e9fa3e8262503b793c5d1d3f98bb4ab5e227f721127f721127f721127f6c94f2dee4224ff2b6d3a6b5dce8f51dccd246f0971447234fac34547b0d46cc5029fc5f99b145868a8f61a8d318a5967dfb0bf33628b0d151ec351b313fe72f17e63c54eec35a7edb2d2b273bb0d6a4e4ee86b527277435a9393ba1ad3f6d9695994ea4e4ee86b527277435a9393ba1ad49ba30ea6318136dc3e6a7761ad49c9dd0d6a4e4ee86b523ee904a588061a3a9393ba1ad49c9dd0d6a4e4ee8699c476d986435a9393ba1ad49c9dd0d6a4e4ee86b52722a8724b986435a9393ba1ad49c9dd0d3166ed402d7889fdc8449fdc8449f8762c9fdc1a94be7d1fda55af113fb90893fb90893fb908308e9fcc5da55af113fb90893fb750c5d59fe45cddae0b9be5b8b9be5b8b789b17720e0f2e1e1127f721127f721127f721127f721127f721127f7210611d3f98bb9d7353bb0d6a4e45508ce3d9bad77435a80831ebc511c8d3eab4c9ca8a0e8fac345481cfb0d46cc5029fc5f99b145868a8f5b6e9d6bba1ad49c9dd0d6615f8b8c604db70f9a95141cd4eec4d0802359857e2f72cb9a9dd86b527277435a9393ba1accd7a03d52d2ee86b52727153a504a46f854eec34df8e4927761acc2bf17b97dc347527277435a9393ba1ad49c9c73a75aee86b527276c85ea96e2e6d590d4b53889fdb47168aa8753cb7a6dadffbe376ed6c4030d1d49b8459e6acc7ffe453cee93934eec35a9393ba1ad49c9dd0d6a4dc619eeed871d2ad783998648f7435a9393ba1a6711e1f353baebd8b52b60db70f9a9dd86b527277435a9393ba1ad49c9dd0d6a4e4ed9f775a9393ba1ad48fb9cee86b526edcda2b9a9dd86b527277435a9393ba1ad49c9dd0d6a4e4ee86b52722a846bc00f54b4bb9d1ea407aa5a5c73b6d16bba1ad49c9dd0d6a4e4ee86b527277435a9393ba1ad49c9db3eeeb5272774298775d59e2e8e5101314b4449fdb4fb2bd9ad4ad399f2ee1eb16f79e24eec35a9393ba1ad49c9dd0d6a4e4ee86b527277435a9372932092f1b2d7889f906db87cd4ee4fbba6711da9bd35925cbb8b5dd0d6a4e4ee86b527277435a9393ba1ad49c9dd0d6a4e4edb16f0d1d49c9dd0d6615f8b8c3a9321720012846b527277435a9393ba1ad49c9dd0d6a4e4ee86b527277435a92176197353bb0d6a4dd1875318752642e4002508d6a4e4ee86b527277435a9393ba1ad49c9dd0d6a4e4ee86b5242ec32e6a7761a5d509ce6956bc1201ea4ec9c4540d0b21377e0ac2fbcdd301cb1875aee85608d90dc8387f06dffbe376ee4d08022dafb275be9fd0399f2ee41c3f836ffdf1bb7716d7d93adf4fe81ccf97720e1fc1b7fef8ddbb8b6bec9d6fa7f40e67cbb9070fe0dbff7c6e2145aee86b523ee73ba1ad49c9dce8f5203d527a6c854eec35a9393ba1ad49c9dd0d6a4e4ee86b5272773b66df0a9dd86b30afc7cd4b1bc00f44428b5dd0afcbb8b5dd0d6a4e4ee86b527277435a9393ba1ad49c9dced9b7c2a7761acc2bf1f352c6f003d110a2d7742bf2ee2d77435a9393ba1ad49c9dd0d6a4e4ee86b5272773b66df0a9dd86b2c951203cd6bc44fc3c224fed9192b7ab526e8c3ad7742a3f3d3fa0733e5dc8387f06dffbe376ee2dafb275be9fd0399f2ee41c3f836ffdf1bb7716d7d93adf4fe81ccf97720e1fc1b7fef8ddbb8b6bec9d6fa7f40e67cbb907031a8d7801ea969774359857e3e555a08ac3474fdb65cd4eec35a9393ba1ad49c9dd0d6a4e4ee86b527276d8b7868ea4e4ee86b52722a846bba1910a2d7742a80d49c9dd0d6a4e4ee86b527277435a9393ba1ad49c9db62de1a3a9393ba1ad49c8aa11aee864428b5dd0aa03527277435a9393ba1ad49c9dd0d6a4e4ee86b527276d8b7868ea4e4ee86b52722a846804c75b65cd4ee45b49791172debbc1b7fef8ddbb8b6bec9d6fa7f40e67cbb9070fe0dbff7c6eddc5b5f64eb7d3fa0733e5dc8387f06dffbe376ee2dafb275be9fd0399f2ee41c3f836ffdf1bb764aaefc7e5ff91737ce6d61a3a9393ba15406a4d08024de5b9ea407aa4e92988e1594f2ca79653cb29e594f2ca79653cb29e594f2ca79653cb29e594f2ca79653cb29e594f2ca79653cb29e594f2ca79653cb29e594f2ca79653cb29e594f2ca79653cb29e594f2ca79653cb29e594f2ca79653cb29e594f2ca79653cb29e594f2ca796538b8f113fb87a901ea969774359857536d7154ce75272721dcd77435a9393ba1ad49c9dd0d6a4e4ee86b527277435a937461d6bba1ad49c9db3edf6336d6822b0d1d40d2ee2d77435a9393ba1ad49c9dd0d6a4e4ee86b527277435a7edb2e6a7761acb2d59402d781d8bf8b57c44fee421e5ee41f3899e503c97ab0cc876ecadaa0cd80918fa4e6c048c7d273602463e939b01231f49cd80918fa4e6c048c7d273602463e939b01231f49cd80918fa4e6c048c7d273602463e939b01231f49cd80918fa4e6c048c7d273602463e939b01231f49cd80918fa4e6c048c7d273602463e939b01231f49cd80918fa4e6c048c7d273602463e939b01231f49cd80918fa4e6c048c7ce8a3911dd5c5aee86b4fdb8ccc28e52d2ed82e450ae6a772fb0de3bc778ef1de3bc778ef1de3bc778ef1de3bc778ef1de3bc778ef1de3bc778ef1de3bc778ef1de3bc778ef1de3bc778ef1de3bc778ef1de3bc778ef1de3bc778ef1de3bc778ef15a1a3a9393b9d1ea3c19cea4e4550e4cb9a9dd86b527277435a9393ba1ad49c9dd0d6a4e4ee86b5272773af5d2b0d1d49c8aa14d080219c7a901ea6db7dc347527277435a9393ba1ad49c9dd0d6a4e4ee86b527277435a93918507353bb0d69fb6cb4ac9ceec2a81386bc00f54b4bba1ad49c9dd0d6a4e4ed842b37cb71737cb71737cb71737cb71442ee109fdc83bc8acd05cdf2bed68a3e227f70e0000feebd538e9cf8c166587279b6058130928003ec8f02e9f89e56422457ca09cf5d426ee714e1b3a1201e74a767d5af0e82b9d04535d1144ee21314604e4c45945897d15a7f7beca2ea7e5a53c66f11c12000e7c00002650000e3f0005368001df0000b3e0003ec80017b200086d000328c0012100006e1000287c000ebc000569800286e714e253bfd721f3f51a145896d15a600000000000000982d0bc0469c1e3c294fb1dd6c4aa12dae17be0fc42564b3815137813b399c329f4d8207549e4aa7531556caaca875e9dbeda25be5f935c96d4387dc8bd84fce997e01d3681b31ce0285a720260026521f26c04b7d099e5518f6fa133caa31edf42679542700760000000000000001060005040061ba3dad46838abb7569b59f1fb669e840015cfec00390e0380008b252f4daa5233588ba30f24724db81ad37b1750aabed03d4e60320ec78e828d39fff371baa96b10c205dfe14f730e736e197bd70cf75679cc5c337c316b1d38d04c40fc835c363aced1a3769dccd963de7503ef4c06bc399bb628c43524c3c91c936cfd8f10d5aa173946c2281a6f49eba318433b952d6b4d080205074dbe452cb732c7dc22bbf04df59f6af7cb109b0050c3e31d20ac14063a0e10b92b9a80000087cd0000e6a0005b9f87a3237c8ff08c4790ac7dd21b3fff72bedc4a71b8f15fc7e0e9f89e22c782510ca44162fde8ddfec110fba3baadf1de262ee32ac74baa7efc80fd5d8ce1604fa72d0ff833dc8fb0cdacac14095daa5f1557a1adc1a521d8007fe85906658fc0f385b65ed6419963ee0007d000007f00149ab86a4439d7206c9c1616817666ac6a7ce66f251846f05b9e9f18bd936ae4d829c5dd00b14b06f0637e9ffc36b064ef092f897c37c228b1dc20227bec5f4f7ee6519b78b67799bdc5f8ae9d097c0561202a81098b9c51780da98bb8c9df8e1aa80dcbf2156d064a73946aaf8a3d576beef65faeb5ad5315e2a2113be00000000000023f1888dc81fe8f6803df69bcd3ef8b93848e33391b37daf03bcc6f7e736fa97e4ee78fcdb54cab58779c8edcfb53aa8a7250125ad0fb1fa57fd8a22de77963623b77dbecb438994adc6423aadf3963634f9488cce3de0aca45ac8272a116706b300ec106e182bc53a794b5c52be0dd97ee7506fb492dbefe7e2e845148856460000db300010720030ceeac8c8e3413103f20d6f124eac64677ceefee8444b2935c34748e02f0ef4ca63be11e5ad199318ab2faa96b0fb4e7a4556bc02d3be20f2b54acf91483a5a08fe370b25937b4dcd066cf866c2ce26f0019eb810809c179b5d380634d080296f7a2cc02cc53efa1023dd971bafb8e05b541c85c8568b9ebf96bc1edeb1ffbbab6628dfdc86980456ee017cf93290ec001a198002e9b0008fd157199b0fbaeb959e16c9e81e813e1251e7bfc1630c8c64ed546e85086ed374a4a75922a2c5584f715e39a77c000000de290f6b8642ed00018a7200253189cb903fd1ed007bed379a7377c00f65d74ea1bb95d1864a834d00aa51955c1fa50d58aa8e05ef3df06220927375efdd807cfe569800000a67a0e918171002e76b6b3e3f7133ca5a5c131156f594a3bfe796ee4ea2e4006b54ff2b0f5d1fb7aec7858c000000000000002d2d8402b1c22c1a337bb99f0dd7dc702d12e32091a8d22deacac7834811afe25a5002560b208e2ac14000000414e4d46601300006300000000005903002f060064000000414c5048c9000000010f30ff1111c281db368e14c0ffffb1816d97e93357f6368ee8ff046c33c56f1eac5b36007b49fdefb92cffbe0273f5bf60964f0f964bfdefb18c5b71dc340aea7f612e6ee56199d4ff82535b3e06505effd3fff4bfe007ac5b7ab0d7cc83b9fa5f4c0ad63cb7e2157ce87fc12b6ed5f5bfa816b781de47602fa9ff3d9795bf4dc032590196adffe97ffadf9f396ef51d6e2ef4bf2856c7fa9f7c04cba5fe17ee6a50ffd3fff4bfb009ac34f5bfc7316ed5f53fd9a84198abffa945b0f6f53ffd4fffd3fff4bf3501005650382076120000b096019d012a5a034d080230063e9d4ea54d3fa4a422235cc82bf01389676ee1767f1dfecf3289fd68feb1fd8ff663fafffffdcabe57f915f8ddc9bbdd5f9634d1ff9bfb87f99dfd4ffb774807e9474c0f301fa29fadfef71fe47f4e7dc8fa007f7afe8dffffdacbfca7b16fa007f00ff09e911fb4ff083fba9e923ffffb3ffa71fa63fda7f16be69f19a866f2ffd3fe1ffe51ea7606777ae7cca6ba7c50b3bd73e6535d3e2859deb9f329ae9f142cef5cf994d74f8a1677ae7cca6ba7c50b3bd73e6535d3e2859deb9f329ae9f142cef5cf994d74f8a1677ae7cca6ba7c50b3bd73e6535d3e2859deb9f329ae9f142cef5cf994d74f8a1677ae7cca6ba7c50b3bd73e6535d3e2859deb9f329ae9f142cef5cf994d74f8a1677ae7cca6ba7c50b3bd73e6535d3e2859deb9f329ae9f142cef5cf994d74f8a1677ae7cca6ba7c50b3bd73e6535d3e2859deb9f329ae9f142cef5cf994d74f8a1677ae7cca6ba7c50b3bd73e61f71dbb4ebed8219250eced2773ff093b9ff849dcffc24ee51969f8d344ed467e127808a81deb9f329ae9f142cef5cf993bea02329f39894404c5e3c755f4d52eba7c50b3bd73e6535d3e28599f02e1dac4ca6ab96d876d6b1329ae9f142cef5cf994d74f8a167663b50a763f4230edad4a8b77ae7cca6ba7c50b3bd73e6535d3e28302cb516a414373f71fbe1fb8fdec5bb224205cd457d99a0cde09306e7fe127808a81deb9f329ae9f142cef54d0802cf994d74f8a15b5913880cf8073c0f8a540ef5cf994d74f8a1677ae7cca6ba7c4d20d4f8a0d080a6ba7c50b3bd73e6535d3e2859deb9f329ae9f09317994d572e99faf74ed2773ff093b9ff849dcffc24ee7fe12773ff093b9ff849dcffc24ee7fe1277268d7909a75f6ccd3afb6669d7db334ebed94353e28302cb516a414373f767aa5d74f8a1677ae7cca6ba7a3a6fe6535d3e2859deb9f329ae9f142cef5cf994d74f8a167663b50a6ba7c50b3bd73e6535d3e2859deb9f329ae9f142cef587aa27101deb9f329ae9f142cd212fd8bcfbc53b179f78a7548755476669d7db334ebed99a75f6ccd3afb6669d7db334ebed84ba29a62681deb9f329ae9f142cef5d055ddcffa84eb3bd73e6535d3e2665e59482859deb9f329ae9f142cef5cf9876d6b1329ae9f142cef260f0d626535d3e2859deb9f329ae9f1348353e2859deb9f329aae5d340ef5cf994d74f8a1677ae7cca6ab96d94d74f8a1677ae78817031ff049d16c8f994d74f8a1677ae7cca6b943aafa6a975d3e2859d9d6c0a0b7f794d72e6975d333c3e6535d3d1d37f329ae9f142cef58b8347800a0696c53d354b880d52eba7c4d20d4f8a1677ae7cca6ac65c3b6a6409c58ecba7c4daa9f142b4750173ff091771e03edc52a077ae7cca6b943bcb8666f55525a739f78a75df8cadb13b6e21ae886ba21ae886ba21ae886ba21ae895df14a819c72e25a8b437df0fdc7ef87ee4d08023f7c3f71fbe1fb8fdf0fdc7ef87ee3f7bb346fbe1f70e5521837805e523b6bbba0055865ff142ccf807101deb9f329ae9f142cef5878e8c34c9413f81348f993ce0d1deb9e3ad87ca6ba7c50b3bd73e6535d3154800e90a2df92143bca6ac65c3b589876dd20a1677ae7cca6ba7c50b3b3199186992827f026f70a15c09a478dd40dcfdc7b987ee3f7c3f71fbe1fb8fdf0fdc7ef87ee3f7c3f71fbe1f3185d621daae44335d4c0d6e62a1f5b264bae51f9bc53b179f7bd1194d74f8a1677ae7cc9df501194d74f84e62a1f5b264bae9f142cef5cf994d74f8a1677ae4d16ef5cf994d58cb87023ada077ae7cca6ba7c50b3bd73e6535d3d1d37f329ae9f09cc543eb64c975d3e2859deb9f329ae9f142ccdadbd31e3d999f7b7254703d08ca6ac65c3811d6d03bd73e6535d3e2859deb9f3298116ef5cf994d74f84e62a1f5b264bae9f142cef5cf994d74f8a1667c0b876b1329ae9f09cc543eb64c975d3e2859deb9f329ae9f142ccf8170ed626535d3e1398a87d6c992eba7c50b3bd73e6535d3e116ed65d552773fe93ddaa381e846535d3e10decde087dafcfbbdb2de1ea7b5f9f77c55377994d74f8a1677ae7cca6045bbd73e6535d3e2859deb93eb63df315194d74f8a1677ae7cca6045bbd73e6535d3e2859deb93eb63df315194d74f8a1677ae7cca6045bbd73e6535d3e2859deb93eb63df315194d726f72cce87e06f87ee3f7c3f714d0802fbe1fb8fdf0fdc7ef87ee3be7501e6e1ee739f78a762f3ef14ec5e7de2073e535c92229a57bffae78e394690e3b179f78a762f3ef14ec5f247c50b3bd73e620a0a077ac65d014c3d63f59a337994d74f85166f5181ee9fd9ae9f142cef5c9f5b203d05312de69f141bc03a80f42327b007501e846535d3e2859deb17068ef5ca0dcfb1a975cb001d407a1193d805e62652fe9117fd549dcffc24ee7fe12773ff093b9ff849dcffbc03880ec98e0f7c67b5f9f784f5cf28ed57c2f11d55cf994d7248e91125d97cfbc27ae794774f3d08df64ec4a21bc1711fd73103807101d933677ae7c78cb8301194d74f474dfcca6045bbd73e6535d3e1262f329aaa4b79a7c4d1f7d6c80f423298116ef5cf1d6876b1329ae9f1348353e2831795eb13277c6c73e535d3e26906a7c506840535d3e2859deb0f501194bee81cd88e7ddfe85bce97194aeccd064cf0e1c7f8e29503bd603d78b0fe46d973eefeb85ba7e12772c460eb4e6203bd73e65355cb6ca6b95ae76f8b1c376ff8fe5674bf4d7ae7cca6ac64065e8dba00191194d74f8a1677ae4d16ef5cf2625c21c718e003bd73e6535cb001d224c7001deb9f329ae9f1348353e2836dd02db34679942cef5cf994c299bc8001798994d74f8a1677ac11f52b78f042cef2a13697eb7ec5e7a619503eb1ca7c50b3bd73c95995f8eea2d36c73ef1228e9d2773d08ca6ba7c50b3bc9589a7c50bd07f018f4d08028a1677ae7ccb4002dc0f42329ae9f142cef260f0d626535d3e2859deb9f329ae9f142cef5cf994d74f8a15b5975deb9f329ae9f142cef5cf994d74f8a1677ae7cca6ba7c24c7442cef5cf994d74f84197e0e63dffd73ca3ba7a577b3fbf5cf994d74f8a1677ae78eb61f29ae9f142cef5cf1d68f7400b832eabe9aa5d74f8a1677ae7c78ed429ae9f142cef5cf8f1d57d3530dad626535d3e2859deb9f30edba4142cef5cf994d74c5644e2033e01c4077ae7cca6ba7c50b33e05c3b58994d74f8a1667c03880ecc755f4d52eba7c50b3bd73e3c76a14d74f8a1677ae7c79f8fb5f9f784f5cf28ee9e8d951194d74f8a1677ae7cc9df544e203bd73e6535d3e2859deb9f329ae9f142cef5cf994d572e9a077ae7cca6ba7c50b3bd73e6535d3e2859deb9f329ae98acbaef5cf994d74f8a1677ae7cca6ba7c50b3bd73e6535d3e1263a21677ae7cca6ba7c50b3bd73e6535d3e2859deb9f329ae50ed429ae9f142cef5cf994d74f8a1677ae7cca6ba7c50b3bd61ea89c4077ae7cca6ba7c50b3bd73e6535d3e2859deb9f329aae5d340ef5cf994d74f8a1677ae7cca6ba7c50b3bd73e6535d315975deb9f329ae9e85beb318d99a75f6ccd3afb6669d7db333f39dcacef5cf994d74f8a1677930786b1329ae9f141b1d55cf994d728755f4d52eba7c50b3bd73e3c76a14d74f8a1677948e7ca6ba7c4d20d4f8a1677ae7cca6ba7c4d20f0d6265354d0802d3e28363aab9f329ae50eabe9aa5d74f8a1677ae7c78ed429ae9f142cef20960cd2688d61b9ee2679ca06e7ee3f7c3f71fbc0c5b8e94f8a1677ae7cca6ba7c24c7442cef5cf994d59cc34cd30d745a6248f42329ae9f142cef5cf9876dd20a1677ae7cc9ec01d224c352eba7c50b3bd73e6535d3e28599f02e1dac4ca6ba7c28b3790002f31329ae9f142cef5cf994d74f89a41e1ac4ca6ba7c506b174caaebfb179f78700f30bb13bd73e64e9975bafb6669dd5077ae7cca6ba62b2ebbd73e6535d3e497ebf25d74f8a15b5913880ef5cf994d74f475012859deb9f329ae9f142cef5cf9876d6b1329ae9f142cef587aa27101deb9f329ae9f142cef5cf9876d6b1329ae9f142cef587aa27101deb93a3018c24dce7fe12773ff093b9ff849dcffc24ee7fe12773ff093b9623062f78b1fa6abf849e022a077ae7cca6ab974d03bd73e3df925c86f05c47f5cc4a21bc1711fb70ddcc3f718bd7a87f142cef5cf993bea89c4077ae4ed8d54d52eba7c50b3bd73e64efa808ca6ba7c50b33e05c3b58994c16c6bb7880ef5cf994d74f8a15b5913880ef5cf994c08c2e53e2859a164e76e6535d3e2859deb9f3277d4046535d3e28599f02e1dac4ca60b635dbc4077ae7cca6ba7c50adac89c4077ae7cca60461729f142cd0b273b7329ae9f142cef5cf993bea02329ae9f142ccf8170ed6265305b1aede203bd73e6535d3e2856d644e203bd73e654d080230230b94f8a16685939db994d74f8a1677ae7cc9df501194d74f8a1667c0b876b132982d8d76f101deb9f329ae9f142b6b227101deb9f32981185ca7c50b342c9cedcca6ba7c50b3bd73e64efa808ca6ba7c50b33e05c3b58994c16c6bb7880ef5cf994d74f8a15b5913880ef5cf994c08c2e53e2859a164e76e6535d3e2859deb9f3277c48000feff3e25894bc6f7e85ed687b22fa631bdd800f8043d280000000000000000000006259e5288172b9de7dbc541b75e70fb9f94f84367edd1ca3df9a44a686ec4ff34af687ade9ce3cebdfbe466077373d2208210bcc180464e00f0459d6e0522020024bd382afc4476871806ad04728de8c12b15432b3b5daffda67f6c19c64baa32bab9cb6c88b35a74775b94d7a0001d93160b968b380000019dfd846ffff689e7720ce2421078f16431f127c9d4cc15a1b09f288a1a81697d79c188831c17dbb8a7d905d9aec268016cd63be211274f428a609172e088106a9aafc2351ffb5777de993cfd3de6d3c02c3a2d10f4ac058df6805c381d770832c95b1c92f286509c31db61888f32a3d0d4eded472180cd55052d950ba0175f9831c97ec4826df1423f68f3eb0d3a50522d9d99a057ee9a6355f067fb18b4e2d8d4bdc78546bcc0d01a140cbbfab02fae8e8cc298b5b53eaa2547bd656798cff82ffd81259ac28c1e43bca71e773f384894cdc864ac25cd337219251e161792464c06850fdd1d994d0802eabf159ac4ead6aca31d3cdee4d9acce486ca756090c49cecd2d21120489488b38038dafffed84b75e5d89cafa3b7f2ae229b8aa92a03c531054e215167ce469827040000000000000f7ff1acbcbbb7eb77b5a1ec8bd431371cd07a757ee3bbf255a43728c9187b325c9dbd8b605b86ed00000000278e9c935ac363627849d59dea40e071f0df7b019a45dae31dc694c0106e0735d5406e3cdde215b9015f149315b71daa746eb600000000b0c2bea313af1ad705fe1182c9cb6ecf92e67981d84ecfc5d76f14a71fcb366e850625109c3583df6f570d1fd8eb745c57e7d9d719b32b568b8bd5e07d81b640a1d02543549d8859b3be8050694c3049ca07213f2aae120aa15205b17706eddb535802ecee4a1603b6b2f514155bb7aca2fd4c30b2c829ba700496ba0000006ec6d7ae18769e1048380250c3843a2cc8a4430bc32300cf81d7056883dc0ded543110ba5bd649a87799c40849b0afedacfdb6335fa39333cadc3799041853465a8bf987045e421c11446e5064be0b536e518a6d1d7005c1c0f76091b943c697010c1222ce016a8b3800000000006788b5ed0cf950f393ddf206f9fd77bdf071977f757e2197b0e6f7b805b6f8b11743de95987dfcbf2929503a3b0e6f81a1ddf71bb90c02d60a1b324430d1a0a4ac26e0000b001f4df3b1856fa3578e9ab0326be525b340412e6ffaabafdfb117864ea7de8a643e816080d39ee8d56ea4d080293460008794462be69a40edcc4dc3e122a9930e7a49911691e83ae3c831c82c7fbdc27c7d5372c8056967149e92d6bbdec3b32b6fd3c6d4e283dfffc5641d63871bd55db79d9c2d9f16084be53e741c9e18689b518532d04f4024bd433c831c84c2c9ef64e322fd46dbeea60a1ba6e2e1ade3953681768232e5a6748efc0bec9b498000051009aaf568bd9376ff5567881f8d270e476385e9fadd32e0dd2c0b8d55210e42f9844c68105e401ab0dfed70bffd7ed187d8b8890afccb16b57ee4680000000004ac62c9d40d31423f68f1f52fc6b46226aac46dac310248111925ccedf4b65c511fda21467ade557a5bda048159b3682a68c234bd6c18cbcc92aee0218472876da8d3919ed9e4b6c21bc30000000000000000000000000000000000003df1a6cf8c6a60f72f283402e286e4295e303950bb135c1c20ceee9b5c7681def8350db7b1b511ae0a19c7c7ecbd0050ee9ec5009ad7400000001d00f2db917a6a8fde2e6637ff8ecf31a487a6b96e32feb28fc2e2b1874042da1b6d5bb57805cf08630c1a4293a1d17e2304b186045edc7fc5523164ea036455464c1403195d5c0f07b2ace90585c7c2371e40da9d9c8b380000000024486f0d10261202a77e753f64e000000000035a392fd88d28aa14e10a23a1215bd983edcd93ca159b2f60e0641e1a7eef173002d572539edcba4e49756b8b0fdfbccdc805c92573396239ac1650afce74d0802fdc4395faad804d48557a448fd9dd642c0023c040c07600d101760288048808280f201b20000414e4d465a2000000000000000002f06002f0600640000025650382042200000b0d0029d012a300630063e9d4ea44d3fa4a3a222fc1813f01389676efc7c99c367b03fffd8890ba2b2a3f916fb37f5ffda9fdffe29fe93fa0fe3dfe537280f65bf11bb088d8749bf78febff95bf313fa97f32f6a7fe03d403fbd7f25fd52ff01f1efd2bff3bf401fa21fab3ef4ffdb7f467f807c0cf400fedbfda3d603fca7ff0f755fe6bea01fc03fabffffffb5ed0bfb3bf07bfbb5e8e9ffef5babc3ffd87dbd719081f7e8153ffd3fcbffcaaff47ab1d456023cb451feb36b038a3fd66d607147facdac0e28ff59b581c51feb36b038a3fd66d607147facdac0e28ff59b581c51feb36b038a3fd66d607147facdac0e28ff59b581c51feb36b038a3fd66d607147facdac0e28ff59b581c51feb36b038a3fd66d607147facdac0e28ff59b581c51feb36b038a3fd66d607147facdac0e28ff59b581c51feb36b038a3fd66d607147facdac0e28ff59b581c51feb36b038a3fd66d607147facdac0e28ff59b581c51feb36b038a3fd66d607147facdac0e28ff59b581c51feb36b038a3fd66d607147facdac0e28ff59b581c51feb36b038a3fd66d607147facdac0e28ff59b581c51feb36b038a3fd66d607147facdac0e28ff59b581c51feb36b038a3fd66d604d08027147facdac0e28ff59b581c51feb36b038a3fd66d607147facdac0e28ff59b581c51feb36b038a3fd66d607147facdac0e28ff59b581c51feb36b038a3fd66d607147facdac0e28ff59b581c51feb36b038a3fd66d607147facdac0e28ff59b581c51feb36b0374a2a29b8af853f274fbd67b805873b459e96915f0a7baefd4d41d2746cf4b48af841698e28ff59b581c51feb36b038a3fd66d607147facdac0e28ff59b581c51feb36499b4ddd50dc5a28f32b2afd1698a563f3966d607147facdac0e28ff59b581c51feb36b038a3fd66d607147facdac0e2222d31c5100c7fd5fa2d2c078c5af4dfffa01c51feb36b038a3fd66d607147facdac0e28ff59b581c51feb36b038a3f95b55581bc368a3f95b5524cf0d75cb6e59b581c51feb36b038a3fd66d607147fa1592e6e1f93a7d334c65b3c96e02c161cecf822ac21152149974e59b581bae5b72cd95f7581ba02782eacb48a168f213639da2cf4b3844cba72cdac0e28ff59b581c51feb36b038888b4b01d93fa6ff14605d66d607147facda9f013d00de1b451feb364373eafffd00e28ff59b581c51feb36b038a3fd66d6052b1f8442251cb6d56dc1485265d3966d606eb96dcb3657dd607147f2413fd7b51eeeb038a3fd66d607147facdac0e28ff598f73052b1f8442251cbf6b451feb36b038a3cc844cba6bc4e59b581ba52737f59b581c51feb36b038a3fd66d607147facdabe4d080203b27c6acf6fea7e4e9f517c06110426786c51feb36b038a3f95b55581bc368a3fd66c8707e58af853f273b55213960362be14f69a8c2ab038a3fd66d607147facdac0e2222d31c51feafd181759b581c51feb36a7c04f403786d147facdac0e2222d2c0764fe9bfff4038a3fd66d607147facdac0a563f3966d6052b2c4038a3fd66d60714369bfff3f9a807147facdac0a563f08844a396dcb36b038a3fd66d607147facda9f013d00e28fe56dc1485265d3966d606eb96dcb3657dd607147facda9f013b666d21f72807147facdac0e28ff57da445ff51f7acf700b0e768b38428175ff47701762362362362362362361c78d7b51eeeb038a3fd5fa2d31c5100c7fd66d607147934276d6a74fbd61c850136fbd67b6595ada9bef59ed8c3d0ad68a3fd66d607147facf7f3904b979c2ae28d9787f30f902ddd607147facdabe03b2a428cae38a3fd66d60719b28947726f814fe619b4ddd607147facdac0e28ff59b57c75a283b84172fdad147facdac0e28f321132e9af13966d607147facda9f013b666d21f72807147facdac0e28ff59b581c50eeb45077082e5fb5a28ff59b581c51e642265d35e272cdac0e28ff59b53e0276ccda43ee500e28ff59b581c50d12491f7deb3db03a1feb3242d141dc20b97ed68a3fd66d607147911d51533fa7fb03d7e1fdd9b57cd4038a3fd66d6050fa2374ba82ff5e161c783025428e1d952149974e54d08029b581c53bc40809e806ee568a0ee105cbf6b451feb36b038a3cc844a66616eeb02b5c7147facdac0e336508fe9b9838a3fd66d607147facd9266d377542ad141dc20b97ed68a3fd66d607147990894ccc2ddd607147facdac0e28ff599464fe9bfff4038a3fd66d60714799089974d68b45077082e5fb5a28ff59b581c51e642253330b77581c51feb36b038a3fd665193fa6fffd00e28ff59b58146fb08658af853de1e275228ff580968a087907f4969f85a2113805baeeeb038a3fd66d6052a91d34989783c8f7773feea6e58e427361c30f9f39eeee7fdddcffba9b69c90a4cba72126358af853e0fb6e219e807147facdac0e2900e6d7d799ae4b4505cb6d4271962cfbbac0e28ff59b53e0a043f92a4f30b59508b7f7f3904a420174315581c51fcadc0902fa6eeb038a3fd66d607147facdabe3ad14172db55b7052149974e59b581bae5b6b20bc78c066bd3bda547d0740a67facdac0a563f3966d607147facdac0e28ff59b581bb95a282e5b6ab6e0a42932e9cb36b0375cb6d64178f180cd7a77b4a8fa0e814cff59b5814ac7e72cdac0e28ff59b581c51feb36b03772b4505cb6d56dc1485265d3966d606eb96dac82f1e3019af4ef6951f41d0299feb36b02958fce59b581c51feb36b037547acf700b0e768b3d2d22be14fc9d186b7ee0161ced167a57f86b80dd7550996915f0a7e4e9f7acf700b0e768b39abb584de14fc9d1894d0802f39eeee771c790274534986c51e72db3b49dcffbbb9d3b2a428b231ca0d67b805352421f77581c51feb36b038cd9455e412f0fedbae8c04ed99b482d52d2149974e4516d3a6caf208a5a22db55b7052149974d6417931c51fe87ee500e28ff59b581c51feb36b038a3fd5fa2d2c0764fa5f1d2c51feb364f30b77581c50e9d24a6e80b77581c50e9d24f4038a3cc844cba72cdac0e28ff59b581c51feb36a7c04ed99b482d53a24e59b5815117ed68a3fd60027452b2c4038a3fd6002747147facc7b9838a3fd66d607147facdac0e28ff59b200d860b0e767a8f6a346e0161ca34fb1137e8dfd0cb224c513aaf8774bf18851feb36af8aa33cd8289607147fa11953c9093a7deb0a211522944fc26e0161c77aacca983feb36b038a3fd66d607147facdac0e222302e8be588b1c62da7464038a3fd600264e72167b6b038a3cf21ee4db5524cdab04a51feb36b038a3fd66d607147facdac0e286d3a836e81a68321e616eeb038a3fd600264e72167b6b038a3cf21ec933690fb94038a3fd66d607147facdac0e28ff59b581c50da7506dd034d0643cc6a60ffacdac0ddb6623aca2fcba72cdaa0c0b224cda43ef710a4cba72cdac0e28ff59b581c51feb36a73cc86e2be14f87535dd7085873b3e025e70908fa4877ef48efde87bdc42932e9cb36406bacbdbd9eeee6bdf762aaa8cae6b9ff6f75f5d8ee9cb322ec82fa2d2b9126a707853f26f4d0802612dc7147facdac0e28ff59b581c51feb36499b487dc9be042e65c93a0c15652d147facdac0e2864be8951196dcb36a8302c893369bba9f013d00e28ff59b581c51feb36b038a3fd0fdc9be0276ccf0c9d939bfacdac0e28ff59b581c437420e1be027a01c450508a1f728071111698e28ff59b581c51feb36b038a3fd663dcc14ac7e110b3bf7e830d8a3fd66d607147facd90dbe70b2b6aab0388a0a1143ee500e2222d31c51feb36b038a3fd66d60714793326c33ef59eda6961ad86480532a8d167a587c78a9d3395c6262e8ffabfbe4cca7b03d81ec0f607b03d81eba34e9da547c4206076c72cec98407c51d53f607b03d81ec0f607b03d6ba0287dca01c4445a638a3fd66d607147facdac0e28f321132e9cb36b0376773eafffcfe3cc2ddd5f1e635307fd66ca125bbd83fe87ef70421132e9aadaaac0e28ff59b581c51feb36b038888b4c7147facdabe1c7c312c0e22a985bbabe3cc6a60ffacd9424b8289e8052b2c37c04f40375cb6e59b581c51feb36b038a3fd66d4f809e807147facd934970513d00a9cc2ddd5f1e635307fd66ca125c144f40295961be027a01bae5b72cdac0e28ff59b581c51feb36a718238dfe4e9f7acf700b0e7679b554f60ff2f9cc0931c50c9ac6c3d3d23bf79e6ea18f9cf7773db636a905ad147fabe740d47c155f8774ac6295f48efde91ded961be027a01bae5b72cdac0e28ff59b581c51feb36b04d080238a3fd0fdee214949dcfabfff3f80ec9fd38088442eeeb038a192f8e926930d75cbf6587654851758fce59b581c51feb36b038a3fd66d607147f2b6e0a428bec9cdfd66d4f809db336918b698e28ff57ce854d743c62d7a77af7dca01c4445a638a3fd66d607147facdac0e28ff59b5814acb100e225fa0c3628fe56d54933c3288b6e59b581ba52737e56dc14210b3be8b4c714369bfff4038a3fd66d607147facdac0e28ff598f7405bbaa01d0299feb31a818e5a0fe43c9bb1a8554249fff7773db6360342a4cba72be740e3e85cffb86858e523a5abbb6396768517d1cb6d45487c3459e961c1c3d6aba72cdac0e28ff59b581c51feb36b038a3cc859edabe1c7c312c0e22a8b04a0d889f87fe807147f3392de4a0d889f8e78a1143ee4df013d00e28ff59b581c51feb36b038a3fd66d607111181759b2692e0a27a0153985ae531a983feb36b0377125bbceb98aa6dc0a1143ee4df013d00e28ff59b581c51feb36b038a3fd66d607111181759b2692e0a27a0153985ae531a983feb36b0377125c14194c5536e0508a1f726f809e807147facdac0e28ff59b581c51feb36a7196c7e4e9f567b32f3746ffb1e14fc9b951fae39d9ed8effda931c50e43553bdeecfb03d81ebf190e385f5ed47bbac0e22993696f59fe9febfc96e77790b440284505b4cd29715f0a7b8fa55afdc02c38ca6c3eeeb038a3fd66d607147facdac0e286d3e3c34d0802d9770d1641135a91e4c714ef14e6b1ffa01c51feb3dfce4114cedb3c32563f088479a7b55581c51feb36b038a3fd66d607147fa1fb937c072b0a3d8117ed68a3fd66d607147facdac0e28ff59b2798d46fdc9be0276ccda6eeb038a3fd66d607147facdac0e286d3bd7bef0c0d31853a9c5485265d3966d607147facdac0e28fe6419cc442251cb6d56d55607147facdac0e28ff59b581c51fe842a86f256b15f02bd5b3da96c7853d90640bae0508b77581c51feb36af85da64435030f7d5ba277d964490e7eb057a2230ec7147f320ce62211281cb1061dc02c391fb8045085265d3966d607147facdac0e28ff59bb4513ef4cc65b3e8302845bbac0e28ff59b57bc835865b808b4963839ee66347bbaa0c0b224cda7464128cba72cdac0e28ff59b581c51feb36b038a3fd6634f36590419cdcb36b038a3fd66c93369bba9f0217c4c6edb3c32563f3966d607147facdac0e28ff59b581c51feb36b038a3f9209feb7731a983feb36b038a3fd0fdca01c444605d66d5060591266d377581c51feb36b038a3fd66d607147facdac0e28ff57ce854d76a936947bbac0e28ff598f7307147990b3db5815117ecb0eca90a4cba72cdac0e28ff59b581c51feb36b038a3fd66c86e7d5eb1702eb36b038a3fd66d4fae272ced8e59db1cb3b63941bfa6eea8302c893369bbac0e28ff59b581c51feb36b038a3fd66d607147fabe742a6bb549b4a3ddd4d0802607147facdac0e28ff59b581bb6cf0c958fce59b581c51feb36b038a3fd66d607147facdac0e28fe4827faddcc6a60ffacdac0e28ff59b581c51feb36af8aa4d9393e952149974e59b581c51feb36b038a3fd66d607147facc69e6cb208339b966d607147facdac0e28ff59b581c450508a1f72807147facdac0e28ff59b581c51feb36b038a3fd66d5eec2f2fa5b3c3628ff59b581c51feb36b038a3fd66d5060591266d377581c51feb36b038a3fd66d607147facdac0e28ff57ce854d76a936947bbac0e28ff59b581c51feb36b0376d9e192b1f9cb36b038a3fd66d607147facdac0e28ff59b581c51fc904ff5bb98d4c1ff59b581c51feb36b038a3fd66d5f1549b2727d2a42932e9cb36b038a3fd66d607147facdac0e28ff598d3cd9641067372cdac0e28ff59b581c51feb36b0388a0a1143ee500e28ff59b581c51feb36b038a3fd66d607147facdabdd85e5f4b6786c51feb36b0374a2a29b8af853f274fbd67b805873b459c8a9285b740388a0a1143ee500e28ff59b581c51feb36b038a3fd66d607147facdabdd85e5f4b6786c51feb36b0375cb6e59b581bae5fb5a28f3c8963fa6fffd00e28ff59b581c51feb36b038a3fd66d607147facc69e6cb208339b966d607147f2b6aab038a3f95b70521494b67864ac7e72cdac0e28ff59b581c51feb36b038a3fd66d607147f2413fd6ee635307fd66d6071111698e28ff57e8c0bac4d0802daa0c0b224cda6eeb038a3fd66d607147facdac0e28ff59b581c51feaf9d0a9aed526d28f77581c51e4398b586aec8af803eb0811ced167a5a457c29f0b806eacae28ff451702cadaaac0e28ff59b581c51feb36b038a3fd66d607147facda9d14e2e5c1422ddd607147fac0530b5ca61746412b9e755606edb3c32563f3966d607147facdac0e28ff59b581c51feb36b038a3f9209feb7731a983feb36b0388aa616b94c2ddd607147facc8bb20be8b4c7147facdac0e28ff59b581c51feb36b038a3fd66d6050f7adc132259485265d3966ca330b5ca635307fd66d607114142287dca01c51feb36b038a3fd66d607147facdac0e28ff59b57bb0bcbe96cf0d8a3fd66d606eca69a5ff5f607b03d75d8ecf08498e28ff4488e29408970a7e4e56c73b459c9ad71cd5cb36b038a3fd66d607147facdac0e28ff59b581c51fe856a9d0e27538a90a4cba72cdd2d4f009cb36b037677405b3a1d952149974e59b581c51feb36b038a3fd66d607147facdac0dd2939bf320ce6e59b581c51feb36b038a3fd66c9a4b9ad2c0765485265d3966d607147facdac0e28ff59b581c51feb36b0374a4e6fcc8339b966d607147facdac0e28ff59b2692e6b4b0307974e59b581c51feb36b038a3fd66d607147facdac0e28ff57ce854d76a936947bbac0a1413d1a761e88807f915e0e85912fadd49444403fc8af0742c897d6ea4a22201fe45783a1644beb4d0802752511100ff22bc1d0bae0ce3c3e8cc1dc02c39a5b72cdac0e28ff59b581c51feb36b038a3fd66d607147facda9d14e2e5c1422ddd60714369c42d7fb28abc825e1fdb75e3988a3c538c807147facdac0e28ff59b581c51feb36b038a3fd66d607147933e84df8b81759b581c4445a638a3fd66d60714369a1d1500e28ff59b581c51feb36b038a3fd66d607147facdac0e28fe4827faddcc6a60ffacda9f013d00e28ff59b581bae574ab662638a3fd66d607147facdac0e28ff59b581c51feb36b038a192f8e9288bf6b451feb31ee60a0d13bef59ee0161ced167a5a457c29f93a7deb3db68f3191f6a4c7147facdac0e28ff59b581c51feb36b038a3fd66d607147facc69e6cb208339b966d606eb96daadac7f3904bc3fb6ebc82d7a7facdac0e28ff59b581c51feb36b038a3fd66d607147facdac0e21ba1535daa4da51eeeb02958fc221132e9cb36b038a3fd66d607147facdac0e28ff59b581c51feb36b038a3fd66d60714325f1d25117ed68a3fd663dcc14ac7e72cdac0e28ff59b581c51feb36b038a3fd66d607147facdac0e28ff59b581c51fe856a9d0e27538a90a4cba11089472db966d607147facdac0e28ff59b581c51feb36b038a3fd66d607147facdac0e28ff57ce854d76a936947bbac0a563f08844cba72cdac0e28ff59b581c51feb36b038a3fd66d607147facdac0e28ff59b581c50c97c749445fb5a28ff598f7304d080252b1f9cb36b038a3fd66d607147facdac0e28ff59b581c51feb36b038a3fd66d607147fa15aa74389d4e2a42932e8442251cb6e59b581c51feb36b038a3fd66d607147facdac0e28ff59b581c51feb36b038a3fd5f3a1535daa4da51eeeb02958fc221132e9cb36b038a3fd66d607147facdac0e28ff59b581c50c0000feff3e25a6555c7a16ebfa2007524f7a5b1b2d800002a9540d88000000000000000000000000000000000000004f6bcf365d83a2fccf3669efce6cd2cb67eaf431d2c4c00e1871f78aa7f56b38650a5b2b268d1e84298c68b9fa67e6baf89c40044aa594975b83f065400016f58000360401de3aa4e58dd4899cf5250ab2dcbbae1d107d806414f2afe5fe88f68f8beec617712240b6bc4a8a3aa4e8aee729365b6cc829855fcbfd1204d69fc417262f4c75fe910ca0a1a9c338b8ee87cd201b371288ec1ac9900bf81b3ea6a3cdeeb8ff1c34c3536ab33bdd38144406f32bb50b0ae65261388d4292f5bd23280021ab21d10290a47840aac1da0f96a840c3cc7c10374ffbb4e846ebbdb6564c9fb472a1a6c809a70a860980b621d421fc42987cc008519710a144ee7b31454b40a236cdf2db07b49a370c74c5d2d39e6f100a4fde2fe57b5dff04794cd334ccc8d3b65d61c07c504d8a7f6628a9681446d9d2012158128d1f750b9bb9f42c02f7ebfce4dbb111e92d8abc13fffe2b7e10c64fd601917cfb3c109c9943704d0802ce38e92cafbda9a946aac514976183f25c1a4fe3f744bebc91604fc073be568c4525d0a809c3a9c50d7f0037a3f00f2ac0a2f4efda64f0f0be85a5c422087247e6e9f4eda4b25bb0ecdba332ee1cbb175e47eae0c39bc6634628c8a1dc7b5d5dac3c6efa4936f108f3bd658dad28fa0a0847e74f97fa9f300d0efb1be012e44bbf1f671621d0b9969a8b1b96b50d8053b0cc0019918781101623dd8a3ceaee729312d728f8a665a4fac6dce45751eef22afed5e1264bc19f77e67bff630e9685a7a8b78c7a910da7c9824a215fcff258e538e9f99ced763b788fedb0e26cb0e8c02c60fdaeeae17f7c72dff482d6f246b72f95a6fd5773949b28e80f1cd70e8c03404e30553bf10b9395784ca742f327cf3569a5f920571a092536b3a103f282b23c5c92e5ec2478b8fd75817886c5e1fcdd63cea1f41f0ea8c1e956399d3e0ba79fd2dac6ce035ca4bc83d0d24f5399c0129c90e78a9048b7475bc647f4fe9274b787d839757de0c5db63e8ea54901bc5ea715c5500ce3825e24b1283c82b6879a22f6012a03f6c5aeea0ebb10a774dec02a90f84268b8061683e6dad15aa5aff9015de7eafad4bca236012a3f8919e65a69d89c2c0c8483b7606f06ccc772fddaebd3b85507a3f452ffbefc2292f2da254c8f0da3bcfc5c94cf9fbb4b134535cebd9deea71f49d488fc206af68b1f1043145d502049c8476418667196c025b136dec01dfaa47404d080216951ca00e013db02bad89d7bdf824212dce2803576be3f9fb35fe3911e6df06d943989ef78e764549500d4e2fc55b827e71ed8427944449fc085c052cfa751feb39f121d634466943ab7de104684d7370ba74cd76957d5ed736fbcf0654ad46c6af78e04e6197ebe50dc1af5967a7160e0d7acb3d3e724e4714ab4eecce443b337db77a073f6a3a1300fc343ff4bdd3e209937549f36e7e37f8435da3209b87b554400f1d1fc3774399c68d1d21787eb68fb7b681781ec027b158e674f711ff5a12b4434d08fea9f00119ee76d5da8f601b96012aa573987d300dcb00a0a8da71804a85881010001d727a0101ca3370611e7993f3863af0725acf00ef881f8c018f22bbb4916a5c687ccecbb0745894476c96c660b12b0b354e755d44b7f9ac0d292500c521ff54f2a4296b272da4b8305f4272da264b5472ccd92140ef8aef82158f104528410af9bc0775c39551e7279bddd5ba6ecd801c30e3ef154fead6754f8c4e67f0ea65695db7f0048754b99e6ce0df9cbbc8884e8d9df56d71127fe7add4016d6e02435564a4b7511cb00951c7bb6013769c000f9b58002c13103173000be8cd49e2e4996234a7f3726c900572b03f5d984ba254088b1f9e4ca33051394e2a155ba7eda02f11b59847b7d9ae60624f12f1c10d7393e801893be623a03319d1ab529e2b41f780d3cf80b286d61b3f0be7071d59a6002ddc7f9bcafefdb3abe2a8d3546f4d08021ce747f531a363bdb125de5d0caed2417c9a9a94ee57f080f2ec8b89c400dabdb5345d89caa85004daa44000547a68002b707683e5de204261b80fed1ca860536d9565569199e88b4ec0acd5df542a0e7e38e0dbb8cce81a57b76f697b9a79536034febd3958b9751084ecaf4e455ceb7bc1f59344064de0a83b044799b2130f3ed07c8d908c00962a24d59c8526a4822f30095695b8002220434801a790b003e4dc2424015943a1632c56282a07cbbde49a567ac7c969976979e54d1f0898ae8c3709f6ceb5cbb34ed69e82d6810bea30415f7ae1e22fb623c9b1dcafdda29363dab7c005edced83dff33b184e38e4e2733a808cf47fdcb2ff151815541a85a2207064cfb0a27698798a7bb46fb4d28d052d089b9497c4c7da1678b25f19d08d8bc0edd0e671a347485e1fada415e6156399d3ddae53f2a54be12992142e279a850d350e065dbbc21d5bc89f9291cb230891830d2202b88b7472c00f3817c5a002baf10816baf6ee0c23cefa788fabe90f1fccbdeaad81d593cf9f943409acfa2d36c4753e3fa4c5fd91fe3d284e1724ee2a4553d6ded6d06962321f156666e66e03c39c45df95fb9375894873394b3f210790a237ebdb027fffa447447a051488d5a6aa6889d1b67da93d1ce8b89caaa284a68626013804bb87487b31ae3efd094df9971c167023e7f1797edc18d76f9d10f8ed264d19ed5febc9e12d9d802816000000fe400014d0802101dd07de0cfb9fca5af8e4a494d8c99620eca6f1cec4003c4d6a64cbc06f88d822c0a3094a6e5c3a1ec0208d5baf1b2e9ca8f9ce5dcc07bc39f669ad3edd8f54095bc1d0d87c92f28a8a0ee83601cfce9f956b8d4582d8f1f9e4ca2ad3acf98e1a09f4d8e579eb222428bb84a49ab379f64a4ba0c24572116808009050000045080006c480009150000eb6800139d0001ef90002d640003f460005e340008144000bfbe4a44518339d4b48ab0730d0162859816b3a333c38d6239cc39a5626c649329d6165c7ab38a20fe63fc42a6e51b2c819f8f7040043d1a517e0000117d00010f10001a162fc8ea84242b407061e0cd405444a3a8bb6bb246c2ce05f762a82db3a1c47023dfdc804d2a760123540b81f8f597849008fd2994f7b54fb10017f447300950024540001b120002459b83c50081cd7040148892603d8b8ee2bc689c27ff1eb9ffff82a97c5c9399abd6336949005aeb26b4038ba0bd292dc79496c00d4a8000326e000377f01a9a24f16c755d3b23c4f23eae58e3491ab6e3e789a618ec21920cc0333d1fda1ce35fa938c1d681b9a48776b8e69f74e52bd2d8d68f219c008160c919648d020a98900e2bc285e9d0ac36154054e0319a16a0093822e00e858b9efd4c363c8eeb0496f9b1ca6f74f6a1421394034a4dd39b9e2815abba74ba7560567a300030565a0ab38a8e970894800fb1800043c400068480008be8000e2e80014d08022c50001e87000000414e4d46d2100000000000420000e50400170300e8030000414c5048ae000000010f30ff1111c271db469254996dfe51f9d3aeeaeeb9af5544ff2740bbf6775dc57af788e9f91fffbb96e49d7c49dec98f937f8b49f23ffef7d5d3b15fc77103e5f91ffffb5659e317a43affe37f7705752cc9d7645f26a23d43b49710ddf91fffbbbad678412abfa54c3822dafc8fff7da4c4f4899857be345096fff1bf3fef3695fff1bffb892cffe37fdf2ef24e75fec7ff2840c744acb8cafc8fff7dd5f8d71abf0dd1e67ffcefe2d9b5d71556503820041000005046019d012ae60418033e9d4ea04cbfa4a322247c3823f01389676efc7c99a8a27ac1f27da1ef3588dfc83f003f0aff7ecad89cfe67fc83f63bf7ff6f6ba1ff43fd61fcaae48feeae1997ef7f9b7a20fe6ffc03a803fc9ff16fd52f7ffce03f43bf577dee3f987e8b76007f64fe29ffffda1ffa47ffff739f400fe01fe4bff9fffff699fd7df81bfed1fedfd243ffff67674cff413fb4fb29f5a7ccd3c82a7ffa7f6c87faed582a31c1bd103a0740e81d03a0740e81d03a0740e81cfd1225693066579f8d5b024f27e4fc4070a0a169c6f9332a35465231d0594948df2b503cfc6aeec1400b7d57046a20740e81d03a0740e81d03a0740e81d0396731bb3766ecdd9bb3766ecdd9bb3764cbaf00b01602c0580b01602c0580b01602c0580b0157406a8c226e4dc9b9374d080226e4dc9b93726a87e1d03a0740e81d03a0740e81d03a0740e81cfcb814e426bbb04f8d27dffee08c65b9ec229844a465023a2e09d410821042083a69d41082104208410821042084075eda6de919ae32927cc6bfa3422266ef2bc117aac9b95afae08d42a3f7c6aeeafddac9a337795e7e357760a0064d95c6525237cad3e33c9f93f27e4fc9f93f27e4fc987c8af94012d49d36a6d4da9b536a6d4da60349f66ecdd9bb3766ecdd9bb3766ecdd9bb3766ecd84371569cc6c219e4fc9f93f27e4fc9f93e080d510821042084108210420841082104208410820e9a8488fad3b4c0649a30c618c318630c613b9bff7046a20740e81d03a0740e81d03a0740e81d0321ab9a5b011d807c6aeeae0b18db8235103a0740e81d039505467f94a0064d95c6525232ce6536652523339116f8235103a0740e81d03a0740e81cb39bff704e186ec618c318630c618c318630c618c27731b0db2a0740e81d03a0740e81d03a0740c86ae6a6d4da9b536a6d4da9b536a6d4da9b536a5a93a5a93a6d4da9b536a6d4da9b536a6d4da60349f66ece29046a20740e81d03a0740e81d039fac063cd3a821042084108210420839c9c457c6aeec1400c9b2b2529d7317b6c2fbecdecdecd94a3a31487c6bc61ed8208d440e81d03a0740e81d039582c93633c6f95a691b218759de7e355c4b45d04fc9f93f27e4fc9f040144cf9d432b42f16d4410764ce580b016024d0802c0580b01602c057df2828019360dbf1af1af15af93f27e4fc9f93f27c101aa21042083b20c82350bc9889b93726e4dc9b93726e4dc9b93726e4dc9b93726e4dc9b93723eb6b80580b01583b6a2083b2672c0580b01602c0580b01602c0580b01602c0580b01602c0580ab06d3056cec9d20c2378b6ccc11a85e2da8820ec133d7adf66f66f65231039b01602c0580b01602c058074b4ccb7201602c0580b01602c0580b00efc8b024f13c3208d42f1b0d7c90c44dc9b93726e4dc9b93726e4dc9b93726e4dc9b93726e4dc9b91f5b5c02c03f03208d440e7f0b39602c0580b01602c0580b01602c0580b01602c0580b01602c055d01aa2103faada8821042048b39602c0580b01602c0580b01602c0580b01602c0580b01601c6948ca0064d0c4f98b54f9e08d35e190024900225237c8b604ad87a81a6708042d53777ab2182b01602c038d2bf26ecdd9bb248cd761eb5088809dd8bd90d3d9bb3766ecdd9bb3610dc6287934618690c80469dc8636cb158bf1133153706dfd0e9b93726e029ac6a20740e5b0f4da9b536a6d4da9b536a5a945510821041d90640234d9c4a7110420841082103f619e4fc9f93f113113726e4dc9b93726e4dc8fadae01602c0560eda7d8678898f8dd9bb3766ecdd9b086793f27e4fc44c44dc9b93726e4dc9b7e5844a46f92c66b4ad39e483bcfbd5f5fee01601f7fdbc6bc6bc09d24106e23af7c0519fa6cf9e4d080208d440c4e3473d1f7ab74fe8bd17a2f366d14d91f4193657193019268ba237188a7ea079f8d8a09f93f27e4fc989add82801881301b546e884108210214b9c5f866fe874dc9b936fecd49d0a029476233922dcf86706e0ca9b83706df8cad80471a881d03a0740e81d0396737fee08d440e81d03a0740e81cb398dd9bb3766ecdd9bb27b10dd9bb3766ecdd9bb3766ecdd2f35781d03a0740e81d03a0740e7eb019268c318630c618c2842ea08410821042084108210420381fc31a8821042084108210420840fd86793f27e4fc9f93f27bdd0b1cfe8df2b50458c0580b01602c0580b0156a8c3ce4fe35acf9bbcaf3f1abbb0500326cae329291be56a079f8d5dd82801936571949253dfcc561fbcfc6aeec1400c9b2b8ca4a46f95a81e7e357760a004d154f6d077cea071a881d03a0740e81d03a0740e81d03a0740e81d03a0740e81d03a0740e81d03a0740e81d03a0740e81d03a0740e81d03a0740e81d03a0740e81d03a0740e81d03a0740e81d03a0740e81d03a0740e81d03a0740e81d03a0740e81d03a0740e81d03a0740e81d03a073f2e3e35b536a6d4da9b536a6d4da9b536a6d4da9b536a6d4da9b536a6d4da9b536a6d4da9b535e34c1a5237d7f66ecdd9bb3766ecdd9bb3766ecdd9bb3766ecdd9bb3766ecdd9bb3766ecdd9bb3610cf27e4fc9f93f27e4fc9f93f27e4fc9f93f27e4fc9f93f27e4fc9f93f27e4fc9f93f27e224d08026226e4dc9b93726e4dbf462286cae319722488e6c1a7163de80d2c3d4b421ef2b8ca4a46f95a81e7e357760a0064d95c6525237cad40f3f1abbb0500326cae329291be56a079f8d5dd8280193657194948df2998b9672ce59cb391d38310c41864d2735122e8859947fc09ceca58125d4b4345702f5cd84f5f6c3f6c3f6c3f6c3f6c3f6c3f6c3f6c3f6c3f6c3f6c3f6c3f6c100116049e4fc9f93e08026d4d83181278980674934618c318630c618c318630c618c318630c614217504208410821020baf00afcbf2ce588c35781d03a0740e81d03a0740e81d03a0740e81d039742ea0841082104204175e015f97e59cb1186af03a0740e81d03a0740e81d03a0740e81d03a072e85d410821042084072654481def75e577a00f8a891be5699a942549a2ed83dc2f27cb7616b7d5ba7f45e8bd17a2f45e8bd17a2f45e8bd17a2f45e8bd17a2f45e8bd17a2f45e8bd17a2f45e8bd17a2f45e8bd17a2f45e8bcdb02580bb716e2da5c84108210420841082103f61b8acab26d4da9b536a6d4da9b536a6d4da9b536a6d4da60326e4dc9b93726e4dc9b93726e029da5834534618c318630c618c318630c618c318630c6185e0324d18630c618c318630c618bb1ab9a9faa0841082104208410821042084108210420840fd86793f27e4fc9f93f27e4fc9f92f15437cbabd02b57c02165d0d93b0b5beadd3fa2f45e8bd17a2f45e8bd17a2f45e8bd17a4d08022f45e8bd17a2f45e8bd17a2f45e8bd17a2f45e8bd17a2f45e8bd17a2f45e7c678790dad31be56a08b180b01602c0580b00f1ad2828014f508d1ddaf9395b0167460e198981898189818981898189818981898189818981774a7005d79b05b01602c0580b01602c055d004b707c86a881d03a0740e81d03a0740e81d03a0740e59cc6c219e4fc9f93f27e4fc9f93f27e4c3e454ac853cf046a20740e81d03a0740e81d03a0740e8190d36712c33726e4dc9b93726e4dc8ecaee214f2bcded0f590a79e08d3529f50304f1af1af13b9f71b528235103a0740ca28658befb37b2f6f9e1554ce55b97b05002f69da606e8600000fef8fa3038843b422a932c558dd40f11101df70e8c62384e1641c761334be889cfb8b18d14c453d11e9bf7fd37722438642fb6a232c678d951b3491c2389ab25a72adeefdf9127e1bdf2ff67fd126273a451920dee6d879fa75c6956392f7317a75cccae4cfc22fc0000e17800000030f40001365f302adef1c26b2299710e11d63dd3486a642fc1c11c5743b1d9b182716acf9dbd70c7e889256214a401990effed277e43005cbf9c13dd90f2a6632ff00132200a8f652ebe8479e0a6c527c1a3d0bca73147341bd4f8c8e1c6dbcb74cf32dcc2707728dc225a7246cf5a736ab7ac548abae74d99d0e50785b924ac6d58de0c24cc2b1ebc77b1cd7355de47fee1667563d7c185630035ad9ada8c88000000000000004d080200004a03814485e0512a43bca96b67e166c6cf7c8da1a1f1e23b17bbbbeb26a58f8e5847a17b8c4e252712669ca7a8fe3937ab53c3f82272996e5ebc898e476d239147bc7e73cc7b2049548503e8d8d75de805480000134000b517263ad4502559cff67fedb1960876a940bc8b49d1179750f135600baebd1ecbe3e3cfa74fbc76447ef7c9ae9e48871dbd4cb6aeb0673e7f2bf88b850556378372e2b6eddaeeee45f3d1070facf78f15f2c542dbeebcb5c4d92c16eb75d6a4c562ffe00879831126ea90701d5b38f9c56d34105f5413a3a26ac77807f96dfb07fefb078f742105c4000005f000002908000b9bc1f94282725a391e870a46e823d22e285cc48a9c446607bb61b3bd6ef2196b22995b2da06ee6fb5ab5ddfb00075e0a50c001be995e004aa3cf05362946911a1794e628e6834a1881f5462870d9c8fe2d8e06f36ca5d55b224c1ff82716e85433a262b75d383f1db2a6ff0d12ffb88cc52fc1315e9039eaea9caceee3d5af4200c2d2c0fc0df756c05bd9f7a1f8690126b130942a3f2aff743747bc8cf6d7c156e6602cb5cb022e2ed4d6b806b7aef5d1530573c34fe3f4c15de99a3796c548e42b216c0214684a91f00b1dcd08d6b46c5dd5e3fdd0dd1d58c04b9ace328c883d942e9fce3408da65430146599317add2f30c57f63bd6aa6c4e14015b7159a388d61311a00063a8006f40153a31a14d0b0c6c1496629aed72b3e60d4d0802fc3ea6237a19d6d9511bff922544a1dafc133167ab3b20552e5145f793e78627abf52581c317f9ba35d852db6ad3f43b3d1c91fde7503ac815b81662bb4bf1bba863b07d015c96b8dbcc25c6a422f9f2d7fcb2a43d824b6fdc6a41d33e8dc7a02164010643803292160054ac30e09d800d6aefd380129442c7ba3c597e535a813500519d92b4cc083000006a492802df387d7f43df4fee807a6a03f96639e0f83d813e9fca9e4d38c9000a52e02829e7e0ddd3e7a1df77d72560fe9faaf59a39e2e5a022d70985bd8c964a6d125e10c1112000000000000000077831a57dec6381c1c11c24000111f2990193d9c1ac14644000003614644000000e4557342e107658cf77bcb7750c760f9fde89cf7198e135914cb7fe65d51bd40029396c47c482e80f8d0f7d667d0add838b6bf53bec000a3ba0e76fd0c613f2002e5fce09eec8f86d4ad4868167bb7996eb903d83c23c592475b06a60624a717fc8b5047dae2b010b960ea3615ab0aec5c468bdd78f3a9a354d1aa68d5346a9a354d1aa674140a32270106c8dc00000000000000eec1a0002ccfa00069f0f8584563f4889996c2dc8d8292d2b1e6630d10f61484cd7fe1aa07c5bc530f7a4bc64b901d7f614d04fa0408ac1eab3966f42f6a38432b00568b4c992694c6e678e2f8e27479de3d223da8bf8463da3413fb89bc6ed2d4d3cf946005c8005628cb12714b004a053802684b97613ef1e4ca29aebcad03ec9af6725880f8d0f7d425dd054fa15bb0721c4b537fcc8587ba12a50dd19063ed003d63769447d813ee7442bd988ad8b7effcc7d4ab99b404ee85ce39360be4633c000edb078c03aa00002340006084e7f3e492fdc7cb4c1dfad6dbcb3fba5283e19344a322577b5f39b33f596464cb6a51a12f2ddc1f8b7d45da48b24a52b59ebbaf5c1464889ac377bc11cfe4a4f9b57f639676c6897d9b5e08000006821c04b295f7286658781d63ff8444369ad9fc4aa20018637f9e5d433dd729f721f8200000000").unwrap(); + parse_transaction(&tx_bytes).unwrap(); + } + #[test] fn test_parse_transaction_extended_format() { let raw_tx = sample_extended_transaction(); @@ -843,6 +853,55 @@ pub(crate) mod tests { assert_eq!(&extr_address, &address); } + #[test] + fn test_parse_coinbase() { + // txid 10f08c702616a2d06b4931160fc4d38dd37a286b8e3899bd90e4193ec01b1ca8 + let raw_tx = "010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff6403208d0a2cfabe6d6dc722441fc57e62b1d14f65f8fbd605070a8562f865794a4f9ced4b052c53f63310000000f09f909f082f4632506f6f6c2f0000000000000000000000000000000000000000000000000000000000000000000000000500221740604a4d010005dbb44325000000001976a914c825a1ecf2a6830c4401620c3a16f1995057c2ab88ac0000000000000000266a24aa21a9ed1d2944b658ac778d4b6f63975f7f8af1c44aeb70815925c9265a920bccb3c7cb0000000000000000366a34486174684105da4443c8ccdd9bd42ce67916625f2265cab02961b28bcfcb85d354b1938d003687f638c24b5ca63bab804cf5496a00000000000000002c6a4c2952534b424c4f434b3a0bde938d409648c985a31b79d335172cc03b2dce726b9d9284309d2800347b770000000000000000266a24b9e11b6d0c42f3297e5d45f4e3b36795ba9295af6da3897ed7b0116282084f596a4bd793012000000000000000000000000000000000000000000000000000000000000000003845d148"; + let tx_bytes = hex::decode(&raw_tx).unwrap(); + let transaction = parse_transaction(&tx_bytes).unwrap(); + let inputs = transaction.inputs.clone(); + assert_eq!(transaction.version, 1); + assert_eq!(inputs.len(), 1); + assert!(matches!(inputs[0].source, TransactionInputSource::Coinbase(_))); + + let mut bytes = BoundedWriter::new(tx_bytes.len() as u32); + transaction.try_format(&mut bytes).unwrap(); + let reconstructed_raw_tx = bytes.result(); + + let reconstructed_hex_tx = hex::encode(reconstructed_raw_tx); + assert_eq!(reconstructed_hex_tx, raw_tx); + } + + #[test] + fn test_reconstruct_txid() { + // txid eb3db053cd139147f2fd676cf59a491fd5aebc54bddfde829704585b659126fc + let raw_tx = "0100000000010120e6fb8f0e2cfb8667a140a92d045d5db7c1b56635790bc907c3e71d43720a150e00000017160014641e441c2ba32dd7cf05afde7922144dd106b09bffffffff019dbd54000000000017a914bd847a4912984cf6152547feca51c1b9c2bcbe2787024830450221008f00033064c26cfca4dc98e5dba800b18729c3441dca37b49358ae0df9be7fad02202a81085318466ea66ef390d5dab6737e44a05f7f2e747932ebba917e0098f37d012102c109fc47335c3a2e206d462ad52590b1842aa9d6e0eb9c683c896fa8723590b400000000"; + let tx_bytes = hex::decode(&raw_tx).unwrap(); + let transaction = parse_transaction(&tx_bytes).unwrap(); + + let reconstructed_txid = transaction.tx_id_bounded(tx_bytes.len() as u32).unwrap().to_hex_be(); + + assert_eq!( + reconstructed_txid, + "eb3db053cd139147f2fd676cf59a491fd5aebc54bddfde829704585b659126fc" + ); + } + + #[test] + fn test_reconstruct_coinbase_2() { + // txid 6f807e134d60f7b4a5fb4818ee28971cb4a20f7063e9f34be81ba2a2fde0c024 + let raw_tx = "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff5803478d0a0486b8f360fabe6d6d905e48adc5d0180883bd0b5f0f145e02acb4378eaa8625bc140039035d256c02020000004204cb9a62696e616e63652f6f7235303279643853413924451238e5ae6e244a28000000000000ffffffff04a8f94025000000001600143156afc4249915008020f932783319f3e610b97d0000000000000000266a24aa21a9ede777d528c5e1df7c82399c2a06d5a7e78c4a815c59c33f0ec79749ee863571890000000000000000266a24b9e11b6dfc71482357d4353614be3a63bb3391d078a622a837e3c16a62d23a51cf6a09aa00000000000000002b6a2952534b424c4f434b3a71b49ca583059694eab2c27471a0b82aa9f6af91778544f63a1af01d0035cd0a0120000000000000000000000000000000000000000000000000000000000000000000000000"; + let tx_bytes = hex::decode(&raw_tx).unwrap(); + let transaction = parse_transaction(&tx_bytes).unwrap(); + + let reconstructed_txid = transaction.tx_id_bounded(tx_bytes.len() as u32).unwrap().to_hex_be(); + + assert_eq!( + reconstructed_txid, + "6f807e134d60f7b4a5fb4818ee28971cb4a20f7063e9f34be81ba2a2fde0c024" + ); + } + /* #[test] fn test_extract_address_invalid_p2pkh_fails() { diff --git a/crates/bitcoin/src/script.rs b/crates/bitcoin/src/script.rs index 477f1812e6..d59e104894 100644 --- a/crates/bitcoin/src/script.rs +++ b/crates/bitcoin/src/script.rs @@ -25,7 +25,8 @@ impl Script { pub(crate) fn height(height: u32) -> Script { let mut script = Script::new(); - script.append(OpCode::Op3); + let len: u8 = 0x03; // Note: NOT OP3. See https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki + script.append(len); let bytes = height.to_le_bytes(); script.append(&bytes[0..=2]); script diff --git a/crates/bitcoin/src/types.rs b/crates/bitcoin/src/types.rs index bf99f3dc4c..10d3115737 100644 --- a/crates/bitcoin/src/types.rs +++ b/crates/bitcoin/src/types.rs @@ -6,7 +6,7 @@ use mocktopus::macros::mockable; pub use crate::merkle::MerkleProof; use crate::{ formatter::{BoundedWriter, TryFormat, Writer}, - merkle::MerkleTree, + merkle::{MerkleTree, PartialTransactionProof}, parser::{extract_address_hash_scriptsig, extract_address_hash_witness, parse_block_header}, utils::{log2, reverse_endianness, sha256d_le}, Address, Error, PublicKey, Script, @@ -24,6 +24,15 @@ use serde::{Deserialize, Serialize}; pub(crate) const SERIALIZE_TRANSACTION_NO_WITNESS: i32 = 0x4000_0000; +/// We also check the coinbase proof in order to defend against the 'leaf-node weakness'. +/// See https://bitslog.com/2018/06/09/leaf-node-weakness-in-bitcoin-merkle-tree-design/ . +#[derive(Encode, Decode, Clone, TypeInfo, PartialEq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct FullTransactionProof { + pub user_tx_proof: PartialTransactionProof, + pub coinbase_proof: PartialTransactionProof, +} + /// Bitcoin Script OpCodes /// #[derive(Copy, Clone)] @@ -368,6 +377,17 @@ impl Transaction { // check if any of the inputs has a witness self.inputs.iter().any(|v| !v.witness.is_empty()) } + + pub fn is_coinbase(&self) -> bool { + self.inputs.len() == 1 + && matches!( + self.inputs.get(0), + Some(&TransactionInput { + source: TransactionInputSource::Coinbase(_), + .. + }) + ) + } } // https://en.bitcoin.it/wiki/NLockTime @@ -1083,8 +1103,8 @@ mod tests { .unwrap(); assert_eq!(block.header.version, 4); assert_eq!(block.header.merkle_root, block.transactions[0].tx_id()); - // should be 3, might change if block is changed - assert_eq!(block.header.nonce, 3); + // should be 2, might change if block is changed (last change was due to coinbase txid calculation fix) + assert_eq!(block.header.nonce, 2); assert!(block.header.nonce > 0); } diff --git a/crates/btc-relay/src/lib.rs b/crates/btc-relay/src/lib.rs index 23334a6297..6162d7441f 100644 --- a/crates/btc-relay/src/lib.rs +++ b/crates/btc-relay/src/lib.rs @@ -54,7 +54,7 @@ use mocktopus::macros::mockable; #[cfg(feature = "runtime-benchmarks")] use bitcoin::types::{BlockBuilder, TransactionBuilder, TransactionOutput}; use bitcoin::{ - merkle::{MerkleProof, ProofResult}, + merkle::ProofResult, types::{BlockChain, BlockHeader, H256Le, Transaction, Value}, Error as BitcoinError, SetCompact, }; @@ -72,7 +72,10 @@ use sp_std::{ prelude::*, }; -pub use bitcoin::{self, Address as BtcAddress, PublicKey as BtcPublicKey}; +pub use bitcoin::{ + self, merkle::PartialTransactionProof, types::FullTransactionProof, Address as BtcAddress, + PublicKey as BtcPublicKey, +}; pub use pallet::*; pub use types::{OpReturnPaymentData, RichBlockHeader}; @@ -551,17 +554,11 @@ impl Pallet { /// interface to the issue pallet; verifies inclusion and returns the payment amount pub fn get_and_verify_issue_payment>( - merkle_proof: MerkleProof, - transaction: Transaction, - length_bound: u32, + unchecked_transaction: FullTransactionProof, recipient_btc_address: BtcAddress, ) -> Result { - let tx_id = transaction - .tx_id_bounded(length_bound) - .map_err(|err| Error::::from(err))?; - // Verify that the transaction is indeed included in the main chain - Self::_verify_transaction_inclusion(tx_id, merkle_proof, None)?; + let transaction = Self::_verify_transaction_inclusion(unchecked_transaction, None)?; Self::get_issue_payment(transaction, recipient_btc_address) } @@ -588,19 +585,13 @@ impl Pallet { /// interface to redeem,replace,refund to check that the payment is included and is valid pub fn verify_and_validate_op_return_transaction>( - merkle_proof: MerkleProof, - transaction: Transaction, - length_bound: u32, + unchecked_transaction: FullTransactionProof, recipient_btc_address: BtcAddress, expected_btc: V, op_return_id: H256, ) -> Result<(), DispatchError> { - let tx_id = transaction - .tx_id_bounded(length_bound) - .map_err(|err| Error::::from(err))?; - // Verify that the transaction is indeed included in the main chain - Self::_verify_transaction_inclusion(tx_id, merkle_proof, None)?; + let transaction = Self::_verify_transaction_inclusion(unchecked_transaction, None)?; // Check that the transaction matches the given parameters Self::validate_op_return_transaction(transaction, recipient_btc_address, expected_btc, op_return_id)?; @@ -608,28 +599,51 @@ impl Pallet { } pub fn _verify_transaction_inclusion( - tx_id: H256Le, - merkle_proof: MerkleProof, + unchecked_transaction: FullTransactionProof, confirmations: Option, - ) -> Result<(), DispatchError> { + ) -> Result { if Self::disable_inclusion_check() { - return Ok(()); + return Ok(unchecked_transaction.user_tx_proof.transaction); } - let proof_result = Self::verify_merkle_proof(&merkle_proof)?; + let user_proof_result = Self::verify_merkle_proof(unchecked_transaction.user_tx_proof)?; + let coinbase_proof_result = Self::verify_merkle_proof(unchecked_transaction.coinbase_proof)?; + + // Make sure the coinbase tx is for the same block as the user tx + ensure!( + user_proof_result.extracted_root == coinbase_proof_result.extracted_root, + Error::::InvalidMerkleProof + ); + ensure!( + user_proof_result.block_hash == coinbase_proof_result.block_hash, + Error::::InvalidMerkleProof + ); - let block_hash = merkle_proof.block_header.hash; - let stored_block_header = Self::verify_block_header_inclusion(block_hash, confirmations)?; + // ensure that the tx count for the coinbase tx matches the user's + ensure!( + user_proof_result.tx_count == coinbase_proof_result.tx_count, + Error::::InvalidMerkleProof + ); + + // Ensure that it's actually the coinbase tx + ensure!( + coinbase_proof_result.transaction.is_coinbase(), + Error::::InvalidMerkleProof + ); - // fail if the transaction hash is invalid - ensure!(proof_result.transaction_hash == tx_id, Error::::InvalidTxid); + let stored_block_header = Self::verify_block_header_inclusion(user_proof_result.block_hash, confirmations)?; // fail if the merkle root is invalid ensure!( - proof_result.extracted_root == stored_block_header.merkle_root, + Self::block_matches_merkle_root(&stored_block_header, &user_proof_result), Error::::InvalidMerkleProof ); - Ok(()) + Ok(user_proof_result.transaction) + } + + // util function extracted for mocking purposes + fn block_matches_merkle_root(block_header: &BlockHeader, proof_result: &ProofResult) -> bool { + proof_result.extracted_root == block_header.merkle_root } pub fn verify_block_header_inclusion( @@ -908,8 +922,10 @@ impl Pallet { // ********************************* // Wrapper functions around bitcoin lib for testing purposes - fn verify_merkle_proof(merkle_proof: &MerkleProof) -> Result { - merkle_proof.verify_proof().map_err(|err| Error::::from(err).into()) + fn verify_merkle_proof(unchecked_transaction: PartialTransactionProof) -> Result { + unchecked_transaction + .verify_proof() + .map_err(|err| Error::::from(err).into()) } /// Verifies a Bitcoin block header. @@ -1346,7 +1362,7 @@ impl Pallet { vin: u32, vout: Vec, max_tx_size: usize, - ) -> (Transaction, MerkleProof) { + ) -> FullTransactionProof { let init_block = BlockBuilder::new() .with_version(4) .with_coinbase(&BtcAddress::default(), 50, 3) @@ -1375,7 +1391,21 @@ impl Pallet { ext::security::active_block_number::() + Self::parachain_confirmations() + 1u32.into(), ); - (transaction, merkle_proof) + let coinbase_tx = block.transactions[0].clone(); + let coinbase_merkle_proof = block.merkle_proof(&[coinbase_tx.tx_id()]).unwrap(); + + FullTransactionProof { + coinbase_proof: PartialTransactionProof { + tx_encoded_len: coinbase_tx.size_no_witness() as u32, + transaction: coinbase_tx, + merkle_proof: coinbase_merkle_proof, + }, + user_tx_proof: PartialTransactionProof { + tx_encoded_len: transaction.size_no_witness() as u32, + transaction: transaction, + merkle_proof, + }, + } } #[cfg(feature = "runtime-benchmarks")] @@ -1419,6 +1449,7 @@ impl From for Error { BitcoinError::ArithmeticUnderflow => Self::ArithmeticUnderflow, BitcoinError::InvalidCompact => Self::InvalidCompact, BitcoinError::BoundExceeded => Self::BoundExceeded, + BitcoinError::InvalidTxid => Self::InvalidTxid, } } } diff --git a/crates/btc-relay/src/tests.rs b/crates/btc-relay/src/tests.rs index 3dd5cfb321..ac65cf9526 100644 --- a/crates/btc-relay/src/tests.rs +++ b/crates/btc-relay/src/tests.rs @@ -1164,9 +1164,6 @@ fn test_verify_transaction_inclusion_succeeds() { let confirmations = None; let rich_block_header = sample_rich_tx_block_header(chain_id, main_chain_height); - let merkle_proof = sample_merkle_proof(); - let proof_result = sample_valid_proof_result(); - let main = get_empty_block_chain_from_chain_id_and_height(chain_id, start, main_chain_height); let fork = get_empty_block_chain_from_chain_id_and_height(fork_ref, start, fork_chain_height); @@ -1182,7 +1179,7 @@ fn test_verify_transaction_inclusion_succeeds() { BTCRelay::get_best_block_height.mock_safe(move || MockResult::Return(main_chain_height)); - BTCRelay::verify_merkle_proof.mock_safe(move |_| MockResult::Return(Ok(proof_result))); + BTCRelay::block_matches_merkle_root.mock_safe(move |_, _| MockResult::Return(true)); BTCRelay::get_block_header_from_hash.mock_safe(move |_| MockResult::Return(Ok(rich_block_header))); @@ -1191,8 +1188,7 @@ fn test_verify_transaction_inclusion_succeeds() { BTCRelay::check_parachain_confirmations.mock_safe(|_| MockResult::Return(Ok(()))); assert_ok!(BTCRelay::_verify_transaction_inclusion( - proof_result.transaction_hash, - merkle_proof, + sample_unchecked_transaction(), confirmations )); }); @@ -1207,9 +1203,6 @@ fn test_verify_transaction_inclusion_empty_fork_succeeds() { let confirmations = None; let rich_block_header = sample_rich_tx_block_header(chain_id, main_chain_height); - let merkle_proof = sample_merkle_proof(); - let proof_result = sample_valid_proof_result(); - let main = get_empty_block_chain_from_chain_id_and_height(chain_id, start, main_chain_height); BTCRelay::get_block_chain_from_id.mock_safe(move |id| { @@ -1222,17 +1215,16 @@ fn test_verify_transaction_inclusion_empty_fork_succeeds() { BTCRelay::get_best_block_height.mock_safe(move || MockResult::Return(main_chain_height)); - BTCRelay::verify_merkle_proof.mock_safe(move |_| MockResult::Return(Ok(proof_result))); - BTCRelay::get_block_header_from_hash.mock_safe(move |_| MockResult::Return(Ok(rich_block_header))); BTCRelay::check_bitcoin_confirmations.mock_safe(|_, _, _| MockResult::Return(Ok(()))); BTCRelay::check_parachain_confirmations.mock_safe(|_| MockResult::Return(Ok(()))); + BTCRelay::block_matches_merkle_root.mock_safe(move |_, _| MockResult::Return(true)); + assert_ok!(BTCRelay::_verify_transaction_inclusion( - proof_result.transaction_hash, - merkle_proof, + sample_unchecked_transaction(), confirmations, )); }); @@ -1249,14 +1241,6 @@ fn test_verify_transaction_inclusion_invalid_tx_id_fails() { let confirmations = None; let rich_block_header = sample_rich_tx_block_header(chain_id, main_chain_height); - // Mismatching TXID - let invalid_tx_id = H256Le::from_bytes_le( - &hex::decode("0000000000000000000000000000000000000000000000000000000000000000".to_owned()).unwrap(), - ); - - let merkle_proof = sample_merkle_proof(); - let proof_result = sample_valid_proof_result(); - let main = get_empty_block_chain_from_chain_id_and_height(chain_id, start, main_chain_height); let fork = get_empty_block_chain_from_chain_id_and_height(fork_ref, start, fork_chain_height); @@ -1272,16 +1256,21 @@ fn test_verify_transaction_inclusion_invalid_tx_id_fails() { BTCRelay::get_best_block_height.mock_safe(move || MockResult::Return(main_chain_height)); - BTCRelay::verify_merkle_proof.mock_safe(move |_| MockResult::Return(Ok(proof_result))); - BTCRelay::get_block_header_from_hash.mock_safe(move |_| MockResult::Return(Ok(rich_block_header))); BTCRelay::check_bitcoin_confirmations.mock_safe(|_, _, _| MockResult::Return(Ok(()))); BTCRelay::check_parachain_confirmations.mock_safe(|_| MockResult::Return(Ok(()))); + let mut tx = sample_unchecked_transaction(); + + // Mismatching TXID + tx.coinbase_proof.merkle_proof.hashes[0] = H256Le::from_bytes_le( + &hex::decode("0000000000000000000000000000000000000000000000000000000000000000".to_owned()).unwrap(), + ); + assert_err!( - BTCRelay::_verify_transaction_inclusion(invalid_tx_id, merkle_proof, confirmations,), + BTCRelay::_verify_transaction_inclusion(tx, confirmations,), TestError::InvalidTxid ); }); @@ -1304,9 +1293,6 @@ fn test_verify_transaction_inclusion_invalid_merkle_root_fails() { ); rich_block_header.block_header.merkle_root = invalid_merkle_root; - let merkle_proof = sample_merkle_proof(); - let proof_result = sample_valid_proof_result(); - let main = get_empty_block_chain_from_chain_id_and_height(chain_id, start, main_chain_height); let fork = get_empty_block_chain_from_chain_id_and_height(fork_ref, start, fork_chain_height); @@ -1328,8 +1314,11 @@ fn test_verify_transaction_inclusion_invalid_merkle_root_fails() { BTCRelay::check_parachain_confirmations.mock_safe(|_| MockResult::Return(Ok(()))); + // merkle root does not match block + BTCRelay::block_matches_merkle_root.mock_safe(move |_, _| MockResult::Return(false)); + assert_err!( - BTCRelay::_verify_transaction_inclusion(proof_result.transaction_hash, merkle_proof, confirmations,), + BTCRelay::_verify_transaction_inclusion(sample_unchecked_transaction(), confirmations,), TestError::InvalidMerkleProof ); }); @@ -1340,14 +1329,11 @@ fn test_verify_transaction_inclusion_fails_with_ongoing_fork() { run_test(|| { BTCRelay::get_chain_id_from_position.mock_safe(|_| MockResult::Return(Ok(1))); BTCRelay::get_block_chain_from_id.mock_safe(|_| MockResult::Return(Ok(BlockChain::default()))); - BTCRelay::verify_merkle_proof.mock_safe(|_| MockResult::Return(Ok(sample_valid_proof_result()))); - let tx_id = sample_valid_proof_result().transaction_hash; - let merkle_proof = sample_merkle_proof(); let confirmations = None; assert_err!( - BTCRelay::_verify_transaction_inclusion(tx_id, merkle_proof, confirmations,), + BTCRelay::_verify_transaction_inclusion(sample_unchecked_transaction(), confirmations,), TestError::OngoingFork ); }); @@ -1356,13 +1342,13 @@ fn test_verify_transaction_inclusion_fails_with_ongoing_fork() { #[test] fn test_get_and_verify_issue_payment_with_tx_containing_taproot() { run_test(|| { - let raw_tx = "010000000001013413e41f47eecad702082578c35a2925217056fd0a837b22f1a205fe178a010d0500000000ffffffff19771000000000000017a91415f691c1905082c300362d48540846c30855162d877a1000000000000022512038234fa3e3ca718dfadfb540c320180e68798e67e0a9d4f10d98ea33d37caf047a100000000000001976a914d73838271ee26471aa3640915ed7274b49435b6688acee2000000000000016001470eab26ae0074a58802acc7c38cd9941619c408d14250000000000001976a91479ef95650e8284c3be439d888cf2ee2d1d8ef63088ac3129000000000000160014a558dd2db8167e069f580da2482a9b73dc4f5960217f0000000000001976a91409f3607112083fb1ffe3718214a8e5d5eb0da46188ac04a50000000000001600149215c14609d581aacaa54f629e823cc8abd17ee6c7cd00000000000017a9146da59c9a54a5465402884712bbbe140bc68a4f218728f700000000000017a914ed99cbd06b43b4e3741d1457f7af7b24c2e8d12487ae380100000000001976a91448296f6f29c497f59193ab4e7def5f2e03ef2f9988ac654901000000000017a914ba997376b5daaa3707aefdf30cc09745b579df2187a6a301000000000017a914ffed3c6e71adc2b73939d6951f4655ed1432909b87ec9202000000000017a9147759a1bffe2acca168afdb5b106250b02a703b2887d63603000000000016001439fef3095e8a3bce11ce471aa602bf3e3609d8ddae3703000000000017a914ea0d18bbd804d17a1f2f07ed9aa1670721777d2287cd370300000000001976a914bcc6bcffe584761176d8f510896e882f838208d988ac1d3803000000000016001470eb59ad925fdec71ca0ec50cf7c6b9bbe8dc7592f380300000000001976a91447eb6c94d7b2ac0c11eb3957c0844d333e21d02e88ac724803000000000016001470eb59ad925fdec71ca0ec50cf7c6b9bbe8dc759692e050000000000160014ae26178c1a9b4adb6f24f047fa119e034205900c381b10000000000017a914c9e20b0d7e46d07a878585955ca377db833d181587d32b20000000000017a914bbfcd0b601046e1656ba9b74a98ee8d362d5b63687402f200000000000160014ca146a720a30ca404e979df59d3ddca039e8fd58f22fea0000000000220020935f3eb059cd94bd307e6378bd590724f361f0316fd0964eb5952f274dfb7b4f0400483045022100c9fc44a423e31fc792f5d255ae09ffdc0b224cb70fcebacd52183ce2813ba11d022046c8530230f644be4a05f25bd6a2264b99afc7e3e38531d4bde12d477d03f18001473044022027f50b14154123b173286db76e189a32973a13b0b4ca425329533229cf7f8d9a02202cea81a657ee654c63ab4a01a741931378abae036435a1d695622216596d9e27016952210257bf4070df9735de32305f3bc25320d331edb10c662423e06cd1e50bc58d8fa7210246454540c4e36ba6a481347d0194ffe476640289aecfd2d3f3db1328415b9a5c210248e0a3385d6f744ae81779e10f8ccafbbed7d44debf08a2b0d5250e2f0a0e84853aef0210b00"; - let tx_bytes = hex::decode(&raw_tx).unwrap(); - let transaction = parse_transaction(&tx_bytes).unwrap(); + BTCRelay::_verify_transaction_inclusion.mock_safe(|_, _| { + let raw_tx = "010000000001013413e41f47eecad702082578c35a2925217056fd0a837b22f1a205fe178a010d0500000000ffffffff19771000000000000017a91415f691c1905082c300362d48540846c30855162d877a1000000000000022512038234fa3e3ca718dfadfb540c320180e68798e67e0a9d4f10d98ea33d37caf047a100000000000001976a914d73838271ee26471aa3640915ed7274b49435b6688acee2000000000000016001470eab26ae0074a58802acc7c38cd9941619c408d14250000000000001976a91479ef95650e8284c3be439d888cf2ee2d1d8ef63088ac3129000000000000160014a558dd2db8167e069f580da2482a9b73dc4f5960217f0000000000001976a91409f3607112083fb1ffe3718214a8e5d5eb0da46188ac04a50000000000001600149215c14609d581aacaa54f629e823cc8abd17ee6c7cd00000000000017a9146da59c9a54a5465402884712bbbe140bc68a4f218728f700000000000017a914ed99cbd06b43b4e3741d1457f7af7b24c2e8d12487ae380100000000001976a91448296f6f29c497f59193ab4e7def5f2e03ef2f9988ac654901000000000017a914ba997376b5daaa3707aefdf30cc09745b579df2187a6a301000000000017a914ffed3c6e71adc2b73939d6951f4655ed1432909b87ec9202000000000017a9147759a1bffe2acca168afdb5b106250b02a703b2887d63603000000000016001439fef3095e8a3bce11ce471aa602bf3e3609d8ddae3703000000000017a914ea0d18bbd804d17a1f2f07ed9aa1670721777d2287cd370300000000001976a914bcc6bcffe584761176d8f510896e882f838208d988ac1d3803000000000016001470eb59ad925fdec71ca0ec50cf7c6b9bbe8dc7592f380300000000001976a91447eb6c94d7b2ac0c11eb3957c0844d333e21d02e88ac724803000000000016001470eb59ad925fdec71ca0ec50cf7c6b9bbe8dc759692e050000000000160014ae26178c1a9b4adb6f24f047fa119e034205900c381b10000000000017a914c9e20b0d7e46d07a878585955ca377db833d181587d32b20000000000017a914bbfcd0b601046e1656ba9b74a98ee8d362d5b63687402f200000000000160014ca146a720a30ca404e979df59d3ddca039e8fd58f22fea0000000000220020935f3eb059cd94bd307e6378bd590724f361f0316fd0964eb5952f274dfb7b4f0400483045022100c9fc44a423e31fc792f5d255ae09ffdc0b224cb70fcebacd52183ce2813ba11d022046c8530230f644be4a05f25bd6a2264b99afc7e3e38531d4bde12d477d03f18001473044022027f50b14154123b173286db76e189a32973a13b0b4ca425329533229cf7f8d9a02202cea81a657ee654c63ab4a01a741931378abae036435a1d695622216596d9e27016952210257bf4070df9735de32305f3bc25320d331edb10c662423e06cd1e50bc58d8fa7210246454540c4e36ba6a481347d0194ffe476640289aecfd2d3f3db1328415b9a5c210248e0a3385d6f744ae81779e10f8ccafbbed7d44debf08a2b0d5250e2f0a0e84853aef0210b00"; + let tx_bytes = hex::decode(&raw_tx).unwrap(); + let transaction = parse_transaction(&tx_bytes).unwrap(); - let raw_proof = "0000402007abe6919ca547e5a9ffe0a11936feef61cb59e7b1f703000000000000000000a53fd3336aa8a18ea00d4127ddb4f4c8d602eee44e271191ee957c257d6b28fc104c4362c0400a17783168d59f0a00000d20a74fa5c909996400a1eaf8ccb08a1fe93f125d260bdbe85f6c46a8ceb0135825c9767aea6bd8d7534a4dcb550332a174a3532aca52665e621c23504d020547dbaa46c7c8fd72b89d3b7dbe1f4f7ca977f3227ad9ce47fc725eb166324662ffdf6b1c856c7a4ec042017fd6c4b10b7b7405d35d2334389b1ea7455f3d94153b3ecfcbaea201e40fdfde250f6a810857bf3ce25af03521a417f44f038e48d7443c46d8574331f1e393ac0c47700544235de90786fca8b6f6d6ce4dce28eabfe82afdf11f4b31ae5209384e56cfc2e103a28f62cd5a269323966a3e29210276fd3fad24c0a2a832ba276dd036b0f50d1d24b12ad239812ffd64cc318f6c28a1a98295da3e28cc22959235808a432225dc101b3c5d545067c8c6553ea89675c7652d914146f9851d78c52802e5dffcbb77ae2f90478f507811c2cece1c2e7b08e5978b8384e6bdf73573b0033a6c2da1494abb5e0b760a00583c106cfdb9b658cecd0d11f35385dbbeb5546bda1144978c674a589e1991e8610aa5ef7480abff3e82bd5bcb91174fd1e896bab2746c9f5fed3faad55d043c1822e7a98f8b8862a104d7ae0500"; - let proof_bytes = hex::decode(&raw_proof).unwrap(); - let merkle_proof = MerkleProof::parse(&proof_bytes).unwrap(); + MockResult::Return(Ok(transaction)) + }); // check the last output address let raw_address = "935f3eb059cd94bd307e6378bd590724f361f0316fd0964eb5952f274dfb7b4f"; @@ -1370,10 +1356,8 @@ fn test_get_and_verify_issue_payment_with_tx_containing_taproot() { let address_hash = H256::from_slice(&address_bytes); let recipient_btc_address = BtcAddress::P2WSHv0(address_hash); - BTCRelay::_verify_transaction_inclusion.mock_safe(|_, _, _| MockResult::Return(Ok(()))); - assert_ok!( - BTCRelay::get_and_verify_issue_payment::(merkle_proof, transaction, u32::MAX, recipient_btc_address), + BTCRelay::get_and_verify_issue_payment::(sample_unchecked_transaction(), recipient_btc_address), 15347698 ); }) @@ -1923,19 +1907,8 @@ pub fn test_has_request_expired() { /// # Util functions -const SAMPLE_TX_ID: &str = "c8589f304d3b9df1d4d8b3d15eb6edaaa2af9d796e9d9ace12b31f293705c5e9"; - const SAMPLE_MERKLE_ROOT: &str = "1EE1FB90996CA1D5DCD12866BA9066458BF768641215933D7D8B3A10EF79D090"; -fn sample_merkle_proof() -> MerkleProof { - MerkleProof { - block_header: sample_block_header(), - transactions_count: 1, - hashes: vec![H256Le::from_hex_le(SAMPLE_TX_ID)], - flag_bits: vec![true], - } -} - fn sample_block_header() -> BlockHeader { let mut ret = BlockHeader { merkle_root: H256Le::from_hex_le(SAMPLE_MERKLE_ROOT), @@ -1950,14 +1923,35 @@ fn sample_block_header() -> BlockHeader { ret } -fn sample_valid_proof_result() -> ProofResult { - let tx_id = H256Le::from_hex_le(SAMPLE_TX_ID); - let merkle_root = H256Le::from_hex_le(SAMPLE_MERKLE_ROOT); - - ProofResult { - extracted_root: merkle_root, - transaction_hash: tx_id, - transaction_position: 0, +fn sample_unchecked_transaction() -> FullTransactionProof { + let coinbase_tx_hex = "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff08044b6d0b1a020b02ffffffff0100f2052a01000000434104e8e37f1556b53b557405fc7924c861e640c8f99ebb3feb09ae69a84bea1f125940309beec02fb815ea5e68782c32da123b4585bc2f23731f1f1c62c9727dba9dac00000000"; + let raw_coinbase_tx = hex::decode(coinbase_tx_hex).unwrap(); + let coinbase_tx = parse_transaction(&raw_coinbase_tx).unwrap(); + + let coinbase_proof_hex = "010000006fd2c5a8fac33dbe89bb2a2947a73eed2afc3b1d4f886942df08000000000000b152eca4364850f3424c7ac2b337d606c5ca0a3f96f1554f8db33d2f6f130bbed325a04e4b6d0b1a85790e6b0a00000005e1af205960ae338a37174b407ee71067c3cd7f04d48a5cec7e13f6eccb61dcbca314970cd7c647d1cc0a477e1a2122b98205b6924b73001b8dab20ee81c2f4f740213c81f059806fb8c1b91d0a7397a57156cfc3a17b71d095c244aafc1eb1158be15fc2ab11ef3e079568d43b2b09ed5a5690fb13ecb1032f7aab99238a1847e827331b1fe7a2689fbc23d14cd21317c699596cbca222182a489322ece1fa74021f00"; + let coinbase_raw_proof = hex::decode(coinbase_proof_hex).unwrap(); + let coinbase_proof = MerkleProof::parse(&coinbase_raw_proof).unwrap(); + + // txid 8d30eb0f3e65b8d8a9f26f6f73fc5aafa5c0372f9bb38aa38dd4c9dd1933e090 + let user_tx_hex = "010000000168a59c95a89ed5e9af00e90a7823156b02b7811000c63170bb2440d8db6a1869000000008a473044022050c32cf6cd888178268701a636b189dc3f026ee3ebd230fd77018e54044aac77022055aa7fa73c524dd4f0be02694683a21eb03d5d2f2c519d7dc7110b742c417517014104aa5c77986a87b93b03d949013e629601b6dbdbd5fc09f3bef9263b64b3c38d79d443fafa2fbf422a203fe433adf6e071f3172a53747739ce72c640fe7e514981ffffffff0140420f00000000001976a91449cf380abdb86449efc694988bf0f447739f73cd88ac00000000"; + let raw_user_tx = hex::decode(user_tx_hex).unwrap(); + let user_tx = parse_transaction(&raw_user_tx).unwrap(); + + let user_proof_hex = "010000006fd2c5a8fac33dbe89bb2a2947a73eed2afc3b1d4f886942df08000000000000b152eca4364850f3424c7ac2b337d606c5ca0a3f96f1554f8db33d2f6f130bbed325a04e4b6d0b1a85790e6b0a000000038d9d737b484e96eed701c4b3728aea80aa7f2a7f57125790ed9998f9050a1bef90e03319ddc9d48da38ab39b2f37c0a5af5afc736f6ff2a9d8b8653e0feb308d84251842a4c0f0e188e1c2bf643ec37a1402dd86a25a9ab5004633467d16e313013d"; + let user_raw_proof = hex::decode(user_proof_hex).unwrap(); + let user_proof = MerkleProof::parse(&user_raw_proof).unwrap(); + + FullTransactionProof { + coinbase_proof: PartialTransactionProof { + transaction: coinbase_tx, + tx_encoded_len: raw_coinbase_tx.len() as u32, + merkle_proof: coinbase_proof, + }, + user_tx_proof: PartialTransactionProof { + transaction: user_tx, + tx_encoded_len: raw_user_tx.len() as u32, + merkle_proof: user_proof, + }, } } diff --git a/crates/issue/src/benchmarking.rs b/crates/issue/src/benchmarking.rs index 4b39898687..d9cf85c747 100644 --- a/crates/issue/src/benchmarking.rs +++ b/crates/issue/src/benchmarking.rs @@ -104,10 +104,8 @@ enum PaymentType { struct ChainState { issue_id: H256, - merkle_proof: MerkleProof, - transaction: Transaction, + transaction: FullTransactionProof, issue_request: DefaultIssueRequest, - length_bound: u32, } fn setup_issue( @@ -158,9 +156,8 @@ fn setup_issue( &vault_btc_address, )); - let (transaction, merkle_proof) = + let transaction = BtcRelay::::initialize_and_store_max(relayer_id.clone(), hashes, vin, outputs, tx_size as usize); - let length_bound = transaction.size_no_witness() as u32; register_vault::(vault_id.clone()); @@ -170,10 +167,8 @@ fn setup_issue( ChainState { issue_id, - merkle_proof, transaction, issue_request, - length_bound, } } @@ -224,13 +219,7 @@ pub mod benchmarks { let issue_data = setup_issue::(PaymentType::Exact, h, i, o, b); #[extrinsic_call] - execute_issue( - RawOrigin::Signed(origin), - issue_data.issue_id, - issue_data.merkle_proof, - issue_data.transaction, - issue_data.length_bound, - ); + execute_issue(RawOrigin::Signed(origin), issue_data.issue_id, issue_data.transaction); } #[benchmark] @@ -239,13 +228,7 @@ pub mod benchmarks { let issue_data = setup_issue::(PaymentType::Overpayment, h, i, o, b); #[extrinsic_call] - execute_issue( - RawOrigin::Signed(origin), - issue_data.issue_id, - issue_data.merkle_proof, - issue_data.transaction, - issue_data.length_bound, - ); + execute_issue(RawOrigin::Signed(origin), issue_data.issue_id, issue_data.transaction); } #[benchmark] @@ -254,13 +237,7 @@ pub mod benchmarks { let issue_data = setup_issue::(PaymentType::Underpayment, h, i, o, b); #[extrinsic_call] - execute_issue( - RawOrigin::Signed(origin), - issue_data.issue_id, - issue_data.merkle_proof, - issue_data.transaction, - issue_data.length_bound, - ); + execute_issue(RawOrigin::Signed(origin), issue_data.issue_id, issue_data.transaction); } #[benchmark] @@ -270,13 +247,7 @@ pub mod benchmarks { expire_issue::(&issue_data); #[extrinsic_call] - execute_issue( - RawOrigin::Signed(origin), - issue_data.issue_id, - issue_data.merkle_proof, - issue_data.transaction, - issue_data.length_bound, - ); + execute_issue(RawOrigin::Signed(origin), issue_data.issue_id, issue_data.transaction); } #[benchmark] @@ -286,13 +257,7 @@ pub mod benchmarks { expire_issue::(&issue_data); #[extrinsic_call] - execute_issue( - RawOrigin::Signed(origin), - issue_data.issue_id, - issue_data.merkle_proof, - issue_data.transaction, - issue_data.length_bound, - ); + execute_issue(RawOrigin::Signed(origin), issue_data.issue_id, issue_data.transaction); } #[benchmark] @@ -302,13 +267,7 @@ pub mod benchmarks { expire_issue::(&issue_data); #[extrinsic_call] - execute_issue( - RawOrigin::Signed(origin), - issue_data.issue_id, - issue_data.merkle_proof, - issue_data.transaction, - issue_data.length_bound, - ); + execute_issue(RawOrigin::Signed(origin), issue_data.issue_id, issue_data.transaction); } #[benchmark] diff --git a/crates/issue/src/ext.rs b/crates/issue/src/ext.rs index 659e851591..69ed75a1a5 100644 --- a/crates/issue/src/ext.rs +++ b/crates/issue/src/ext.rs @@ -3,23 +3,16 @@ use mocktopus::macros::mockable; #[cfg_attr(test, mockable)] pub(crate) mod btc_relay { - use bitcoin::types::{MerkleProof, Transaction, Value}; + use bitcoin::types::{FullTransactionProof, Value}; use btc_relay::BtcAddress; use frame_support::dispatch::DispatchError; use sp_std::convert::TryFrom; pub fn get_and_verify_issue_payment>( - merkle_proof: MerkleProof, - transaction: Transaction, - length_bound: u32, + unchecked_transaction: FullTransactionProof, recipient_btc_address: BtcAddress, ) -> Result { - >::get_and_verify_issue_payment( - merkle_proof, - transaction, - length_bound, - recipient_btc_address, - ) + >::get_and_verify_issue_payment(unchecked_transaction, recipient_btc_address) } pub fn get_best_block_height() -> u32 { diff --git a/crates/issue/src/lib.rs b/crates/issue/src/lib.rs index 4ac852d942..509422f5ca 100644 --- a/crates/issue/src/lib.rs +++ b/crates/issue/src/lib.rs @@ -32,10 +32,10 @@ pub mod types; pub use crate::types::{DefaultIssueRequest, IssueRequest, IssueRequestStatus}; use crate::types::{BalanceOf, DefaultVaultId, Version}; -use bitcoin::types::{MerkleProof, Transaction}; +use bitcoin::{merkle::PartialTransactionProof, types::FullTransactionProof}; use btc_relay::{BtcAddress, BtcPublicKey}; use currency::Amount; -use frame_support::{dispatch::DispatchError, ensure, traits::Get, transactional, PalletId}; +use frame_support::{dispatch::DispatchError, ensure, pallet_prelude::Weight, traits::Get, transactional, PalletId}; use frame_system::{ensure_root, ensure_signed}; pub use pallet::*; use sp_core::H256; @@ -44,6 +44,32 @@ use sp_std::vec::Vec; use types::IssueRequestExt; use vault_registry::{types::CurrencyId, CurrencySource, VaultStatus}; +/// Complexity: +/// - `O(H + I + O + B)` where: +/// - `H` is the number of hashes in the merkle tree +/// - `I` is the number of transaction inputs +/// - `O` is the number of transaction outputs +/// - `B` is `transaction` size in bytes (length-fee-bounded) +fn weight_for_execute_issue(proof: &FullTransactionProof) -> Weight { + let partial_weight = |partial_proof: &PartialTransactionProof| { + let h = partial_proof.merkle_proof.hashes.len() as u32; + let i = partial_proof.transaction.inputs.len() as u32; + let o = partial_proof.transaction.outputs.len() as u32; + let b = partial_proof.tx_encoded_len; + + ::WeightInfo::execute_issue_underpayment(h, i, o, b) + .max(::WeightInfo::execute_issue_exact(h, i, o, b)) + .max(::WeightInfo::execute_issue_overpayment(h, i, o, b)) + .max(::WeightInfo::execute_expired_issue_underpayment( + h, i, o, b, + )) + .max(::WeightInfo::execute_expired_issue_exact(h, i, o, b)) + .max(::WeightInfo::execute_expired_issue_overpayment(h, i, o, b)) + }; + + partial_weight(&proof.coinbase_proof).saturating_add(partial_weight(&proof.user_tx_proof)) +} + #[frame_support::pallet] pub mod pallet { use super::*; @@ -220,36 +246,16 @@ pub mod pallet { /// * `tx_block_height` - block number of collateral chain /// * `merkle_proof` - raw bytes /// * `raw_tx` - raw bytes - /// - /// ## Complexity: - /// - `O(H + I + O + B)` where: - /// - `H` is the number of hashes in the merkle tree - /// - `I` is the number of transaction inputs - /// - `O` is the number of transaction outputs - /// - `B` is `transaction` size in bytes (length-fee-bounded) #[pallet::call_index(1)] - #[pallet::weight({ - let h = merkle_proof.hashes.len() as u32; - let i = transaction.inputs.len() as u32; - let o = transaction.outputs.len() as u32; - let b = *length_bound; - ::WeightInfo::execute_issue_underpayment(h, i, o, b) - .max(::WeightInfo::execute_issue_exact(h, i, o, b)) - .max(::WeightInfo::execute_issue_overpayment(h, i, o, b)) - .max(::WeightInfo::execute_expired_issue_underpayment(h, i, o, b)) - .max(::WeightInfo::execute_expired_issue_exact(h, i, o, b)) - .max(::WeightInfo::execute_expired_issue_overpayment(h, i, o, b)) - })] + #[pallet::weight(weight_for_execute_issue::(unchecked_transaction))] #[transactional] pub fn execute_issue( origin: OriginFor, issue_id: H256, - merkle_proof: MerkleProof, - transaction: Transaction, - #[pallet::compact] length_bound: u32, + unchecked_transaction: FullTransactionProof, ) -> DispatchResultWithPostInfo { let executor = ensure_signed(origin)?; - Self::_execute_issue(executor, issue_id, merkle_proof, transaction, length_bound)?; + Self::_execute_issue(executor, issue_id, unchecked_transaction)?; Ok(().into()) } @@ -375,20 +381,14 @@ impl Pallet { fn _execute_issue( executor: T::AccountId, issue_id: H256, - merkle_proof: MerkleProof, - transaction: Transaction, - length_bound: u32, + unchecked_transaction: FullTransactionProof, ) -> Result<(), DispatchError> { let mut issue = Self::get_issue_request_from_id(&issue_id)?; // allow anyone to complete issue request let requester = issue.requester.clone(); - let amount_transferred = ext::btc_relay::get_and_verify_issue_payment::>( - merkle_proof, - transaction, - length_bound, - issue.btc_address, - )?; + let amount_transferred = + ext::btc_relay::get_and_verify_issue_payment::>(unchecked_transaction, issue.btc_address)?; let amount_transferred = Amount::new(amount_transferred, issue.vault.wrapped_currency()); let expected_total_amount = issue.amount().checked_add(&issue.fee())?; diff --git a/crates/issue/src/tests.rs b/crates/issue/src/tests.rs index 4e20ae5b2c..4f8cccd3b3 100644 --- a/crates/issue/src/tests.rs +++ b/crates/issue/src/tests.rs @@ -1,5 +1,6 @@ use crate::{ext, mock::*, Event, IssueRequest}; +use bitcoin::{merkle::PartialTransactionProof, types::FullTransactionProof}; use btc_relay::{BtcAddress, BtcPublicKey}; use currency::Amount; use frame_support::{assert_noop, assert_ok, dispatch::DispatchError}; @@ -54,7 +55,20 @@ fn request_issue_ok_with_address( } fn execute_issue(origin: AccountId, issue_id: &H256) -> Result<(), DispatchError> { - Issue::_execute_issue(origin, *issue_id, Default::default(), Default::default(), u32::MAX) + let unchecked_transaction = FullTransactionProof { + user_tx_proof: PartialTransactionProof { + transaction: Default::default(), + tx_encoded_len: u32::MAX, + merkle_proof: Default::default(), + }, + coinbase_proof: PartialTransactionProof { + transaction: Default::default(), + tx_encoded_len: u32::MAX, + merkle_proof: Default::default(), + }, + }; + + Issue::_execute_issue(origin, *issue_id, unchecked_transaction) } fn cancel_issue(origin: AccountId, issue_id: &H256) -> Result<(), DispatchError> { @@ -158,7 +172,7 @@ fn setup_execute( >::set_active_block_number(5); ext::btc_relay::get_and_verify_issue_payment:: - .mock_safe(move |_, _, _, _| MockResult::Return(Ok(btc_transferred))); + .mock_safe(move |_, _| MockResult::Return(Ok(btc_transferred))); issue_id } diff --git a/crates/redeem/src/benchmarking.rs b/crates/redeem/src/benchmarking.rs index b44207ebd1..afcc7ae50a 100644 --- a/crates/redeem/src/benchmarking.rs +++ b/crates/redeem/src/benchmarking.rs @@ -234,9 +234,7 @@ pub mod benchmarks { )); } - let (transaction, merkle_proof) = - BtcRelay::::initialize_and_store_max(relayer_id.clone(), h, i, outputs, b as usize); - let length_bound = transaction.size_no_witness() as u32; + let transaction = BtcRelay::::initialize_and_store_max(relayer_id.clone(), h, i, outputs, b as usize); assert_ok!(Oracle::::_set_exchange_rate( get_collateral_currency_id::(), @@ -244,13 +242,7 @@ pub mod benchmarks { )); #[extrinsic_call] - _( - RawOrigin::Signed(vault_id.account_id.clone()), - redeem_id, - merkle_proof, - transaction, - length_bound, - ); + _(RawOrigin::Signed(vault_id.account_id.clone()), redeem_id, transaction); } #[benchmark] diff --git a/crates/redeem/src/ext.rs b/crates/redeem/src/ext.rs index d0bd65e316..a1156eb7e9 100644 --- a/crates/redeem/src/ext.rs +++ b/crates/redeem/src/ext.rs @@ -3,24 +3,20 @@ use mocktopus::macros::mockable; #[cfg_attr(test, mockable)] pub(crate) mod btc_relay { - use bitcoin::types::{MerkleProof, Transaction, Value}; + use bitcoin::types::{FullTransactionProof, Value}; use btc_relay::BtcAddress; use frame_support::dispatch::DispatchError; use sp_core::H256; use sp_std::convert::TryInto; pub fn verify_and_validate_op_return_transaction>( - merkle_proof: MerkleProof, - transaction: Transaction, - length_bound: u32, + unchecked_transaction: FullTransactionProof, recipient_btc_address: BtcAddress, expected_btc: V, op_return_id: H256, ) -> Result<(), DispatchError> { >::verify_and_validate_op_return_transaction( - merkle_proof, - transaction, - length_bound, + unchecked_transaction, recipient_btc_address, expected_btc, op_return_id, diff --git a/crates/redeem/src/lib.rs b/crates/redeem/src/lib.rs index 5c49433651..ffd3acc3f3 100644 --- a/crates/redeem/src/lib.rs +++ b/crates/redeem/src/lib.rs @@ -30,12 +30,14 @@ pub mod types; pub use crate::types::{DefaultRedeemRequest, RedeemRequest, RedeemRequestStatus}; use crate::types::{BalanceOf, RedeemRequestExt, Version}; -use bitcoin::types::{MerkleProof, Transaction}; +use bitcoin::types::FullTransactionProof; use btc_relay::BtcAddress; use currency::Amount; use frame_support::{ dispatch::{DispatchError, DispatchResult}, - ensure, transactional, + ensure, + pallet_prelude::Weight, + transactional, }; use frame_system::{ensure_root, ensure_signed}; use oracle::OracleKey; @@ -50,6 +52,27 @@ use vault_registry::{ pub use pallet::*; +/// Complexity: +/// - `O(H + I + O + B)` where: +/// - `H` is the number of hashes in the merkle tree +/// - `I` is the number of transaction inputs +/// - `O` is the number of transaction outputs +/// - `B` is `transaction` size in bytes (length-fee-bounded) +fn weight_for_execute_redeem(proof: &FullTransactionProof) -> Weight { + ::WeightInfo::execute_redeem( + proof.user_tx_proof.merkle_proof.hashes.len() as u32, // H + proof.user_tx_proof.transaction.inputs.len() as u32, // I + proof.user_tx_proof.transaction.outputs.len() as u32, // O + proof.user_tx_proof.tx_encoded_len, + ) + .saturating_add(::WeightInfo::execute_redeem( + proof.coinbase_proof.merkle_proof.hashes.len() as u32, // H + proof.coinbase_proof.transaction.inputs.len() as u32, // I + proof.coinbase_proof.transaction.outputs.len() as u32, // O + proof.coinbase_proof.tx_encoded_len, + )) +} + #[frame_support::pallet] pub mod pallet { use super::*; @@ -266,30 +289,17 @@ pub mod pallet { /// * `tx_id` - transaction hash /// * `merkle_proof` - membership proof /// * `transaction` - tx containing payment - /// - /// ## Complexity: - /// - `O(H + I + O + B)` where: - /// - `H` is the number of hashes in the merkle tree - /// - `I` is the number of transaction inputs - /// - `O` is the number of transaction outputs - /// - `B` is `transaction` size in bytes (length-fee-bounded) #[pallet::call_index(2)] - #[pallet::weight(::WeightInfo::execute_redeem( - merkle_proof.hashes.len() as u32, // H - transaction.inputs.len() as u32, // I - transaction.outputs.len() as u32, // O - *length_bound, - ))] + #[pallet::weight(weight_for_execute_redeem::(unchecked_transaction))] #[transactional] pub fn execute_redeem( origin: OriginFor, redeem_id: H256, - merkle_proof: MerkleProof, - transaction: Transaction, - #[pallet::compact] length_bound: u32, + unchecked_transaction: FullTransactionProof, ) -> DispatchResultWithPostInfo { let _ = ensure_signed(origin)?; - Self::_execute_redeem(redeem_id, merkle_proof, transaction, length_bound)?; + + Self::_execute_redeem(redeem_id, unchecked_transaction)?; // Don't take tx fees on success. If the vault had to pay for this function, it would // have been vulnerable to a griefing attack where users would redeem amounts just @@ -577,19 +587,12 @@ impl Pallet { Ok(()) } - fn _execute_redeem( - redeem_id: H256, - merkle_proof: MerkleProof, - transaction: Transaction, - length_bound: u32, - ) -> Result<(), DispatchError> { + fn _execute_redeem(redeem_id: H256, unchecked_transaction: FullTransactionProof) -> Result<(), DispatchError> { let redeem = Self::get_open_redeem_request_from_id(&redeem_id)?; // check the transaction inclusion and validity ext::btc_relay::verify_and_validate_op_return_transaction::( - merkle_proof, - transaction, - length_bound, // need to check this first to avoid excess work + unchecked_transaction, redeem.btc_address, redeem.amount_btc, redeem_id, diff --git a/crates/redeem/src/tests.rs b/crates/redeem/src/tests.rs index dc75e3e650..c31a0afd15 100644 --- a/crates/redeem/src/tests.rs +++ b/crates/redeem/src/tests.rs @@ -1,6 +1,7 @@ use crate::{ext, mock::*}; use crate::types::{RedeemRequest, RedeemRequestStatus}; +use bitcoin::{merkle::PartialTransactionProof, types::FullTransactionProof}; use btc_relay::BtcAddress; use currency::Amount; use frame_support::{assert_err, assert_noop, assert_ok, dispatch::DispatchError}; @@ -358,18 +359,28 @@ fn test_liquidation_redeem_succeeds() { }) } +fn get_some_unchecked_transaction() -> FullTransactionProof { + FullTransactionProof { + user_tx_proof: PartialTransactionProof { + transaction: Default::default(), + tx_encoded_len: u32::MAX, + merkle_proof: Default::default(), + }, + coinbase_proof: PartialTransactionProof { + transaction: Default::default(), + tx_encoded_len: u32::MAX, + merkle_proof: Default::default(), + }, + } +} + #[test] fn test_execute_redeem_fails_with_redeem_id_not_found() { run_test(|| { convert_to.mock_safe(|_, x| MockResult::Return(Ok(x))); + assert_err!( - Redeem::execute_redeem( - RuntimeOrigin::signed(VAULT.account_id), - H256([0u8; 32]), - Default::default(), - Default::default(), - u32::MAX, - ), + Redeem::_execute_redeem(H256([0u8; 32]), get_some_unchecked_transaction()), TestError::RedeemIdNotFound ); }) @@ -397,7 +408,7 @@ fn test_execute_redeem_succeeds_with_another_account() { }, ); ext::btc_relay::verify_and_validate_op_return_transaction:: - .mock_safe(|_, _, _, _, _, _| MockResult::Return(Ok(()))); + .mock_safe(|_, _, _, _| MockResult::Return(Ok(()))); let btc_fee = Redeem::get_current_inclusion_fee(DEFAULT_WRAPPED_CURRENCY).unwrap(); @@ -433,12 +444,9 @@ fn test_execute_redeem_succeeds_with_another_account() { MockResult::Return(Ok(())) }); - assert_ok!(Redeem::execute_redeem( - RuntimeOrigin::signed(USER), + assert_ok!(Redeem::_execute_redeem( H256([0u8; 32]), - Default::default(), - Default::default(), - u32::MAX, + get_some_unchecked_transaction() )); assert_emitted!(Event::ExecuteRedeem { redeem_id: H256([0; 32]), @@ -477,7 +485,7 @@ fn test_execute_redeem_succeeds() { }, ); ext::btc_relay::verify_and_validate_op_return_transaction:: - .mock_safe(|_, _, _, _, _, _| MockResult::Return(Ok(()))); + .mock_safe(|_, _, _, _| MockResult::Return(Ok(()))); let btc_fee = Redeem::get_current_inclusion_fee(DEFAULT_WRAPPED_CURRENCY).unwrap(); @@ -513,12 +521,9 @@ fn test_execute_redeem_succeeds() { MockResult::Return(Ok(())) }); - assert_ok!(Redeem::execute_redeem( - RuntimeOrigin::signed(VAULT.account_id), + assert_ok!(Redeem::_execute_redeem( H256([0u8; 32]), - Default::default(), - Default::default(), - u32::MAX, + get_some_unchecked_transaction() )); assert_emitted!(Event::ExecuteRedeem { redeem_id: H256([0; 32]), @@ -837,7 +842,7 @@ mod spec_based_tests { }, ); ext::btc_relay::verify_and_validate_op_return_transaction:: - .mock_safe(|_, _, _, _, _, _| MockResult::Return(Ok(()))); + .mock_safe(|_, _, _, _| MockResult::Return(Ok(()))); let btc_fee = Redeem::get_current_inclusion_fee(DEFAULT_WRAPPED_CURRENCY).unwrap(); let redeem_request = RedeemRequest { @@ -869,12 +874,9 @@ mod spec_based_tests { MockResult::Return(Ok(())) }); - assert_ok!(Redeem::execute_redeem( - RuntimeOrigin::signed(USER), + assert_ok!(Redeem::_execute_redeem( H256([0u8; 32]), - Default::default(), - Default::default(), - u32::MAX, + get_some_unchecked_transaction() )); assert_emitted!(Event::ExecuteRedeem { redeem_id: H256([0; 32]), diff --git a/crates/replace/src/benchmarking.rs b/crates/replace/src/benchmarking.rs index 8e9125627e..9d523d72a6 100644 --- a/crates/replace/src/benchmarking.rs +++ b/crates/replace/src/benchmarking.rs @@ -137,7 +137,7 @@ fn setup_replace( vin: u32, vout: u32, tx_size: u32, -) -> (H256, MerkleProof, Transaction) +) -> (H256, FullTransactionProof) where <::Balance as TryInto>::Error: Debug, { @@ -186,7 +186,7 @@ where )); } - let (transaction, merkle_proof) = + let transaction = BtcRelay::::initialize_and_store_max(relayer_id.clone(), hashes, vin, outputs, tx_size as usize); let period = Replace::::replace_period().max(replace_request.period); @@ -197,7 +197,7 @@ where Security::::active_block_number() + Replace::::replace_period() + 100u32.into(), ); - (replace_id, merkle_proof, transaction) + (replace_id, transaction) } #[benchmarks( @@ -278,18 +278,10 @@ pub mod benchmarks { to_be_replaced, .. } = setup_chain::(); - let (replace_id, merkle_proof, transaction) = - setup_replace::(&old_vault_id, &new_vault_id, to_be_replaced, h, i, o, b); - let length_bound = transaction.size_no_witness() as u32; + let (replace_id, transaction) = setup_replace::(&old_vault_id, &new_vault_id, to_be_replaced, h, i, o, b); #[extrinsic_call] - execute_replace( - RawOrigin::Signed(old_vault_id.account_id), - replace_id, - merkle_proof, - transaction, - length_bound, - ); + execute_replace(RawOrigin::Signed(old_vault_id.account_id), replace_id, transaction); } #[benchmark] @@ -300,9 +292,7 @@ pub mod benchmarks { to_be_replaced, .. } = setup_chain::(); - let (replace_id, merkle_proof, transaction) = - setup_replace::(&old_vault_id, &new_vault_id, to_be_replaced, h, i, o, b); - let length_bound = transaction.size_no_witness() as u32; + let (replace_id, transaction) = setup_replace::(&old_vault_id, &new_vault_id, to_be_replaced, h, i, o, b); assert_ok!(Pallet::::cancel_replace( RawOrigin::Signed(new_vault_id.account_id).into(), @@ -310,13 +300,7 @@ pub mod benchmarks { )); #[extrinsic_call] - execute_replace( - RawOrigin::Signed(old_vault_id.account_id), - replace_id, - merkle_proof, - transaction, - length_bound, - ); + execute_replace(RawOrigin::Signed(old_vault_id.account_id), replace_id, transaction); } #[benchmark] @@ -328,7 +312,7 @@ pub mod benchmarks { .. } = setup_chain::(); - let (replace_id, _, _) = setup_replace::(&old_vault_id, &new_vault_id, to_be_replaced, 2, 2, 2, 541); + let (replace_id, _) = setup_replace::(&old_vault_id, &new_vault_id, to_be_replaced, 2, 2, 2, 541); #[extrinsic_call] cancel_replace(RawOrigin::Signed(new_vault_id.account_id), replace_id); diff --git a/crates/replace/src/ext.rs b/crates/replace/src/ext.rs index b1a2037436..bfbfdf204b 100644 --- a/crates/replace/src/ext.rs +++ b/crates/replace/src/ext.rs @@ -3,24 +3,20 @@ use mocktopus::macros::mockable; #[cfg_attr(test, mockable)] pub(crate) mod btc_relay { - use bitcoin::types::{MerkleProof, Transaction, Value}; + use bitcoin::types::{FullTransactionProof, Value}; use btc_relay::BtcAddress; use frame_support::dispatch::DispatchError; use sp_core::H256; use sp_std::convert::TryInto; pub fn verify_and_validate_op_return_transaction>( - merkle_proof: MerkleProof, - transaction: Transaction, - length_bound: u32, + unchecked_transaction: FullTransactionProof, recipient_btc_address: BtcAddress, expected_btc: V, op_return_id: H256, ) -> Result<(), DispatchError> { >::verify_and_validate_op_return_transaction( - merkle_proof, - transaction, - length_bound, + unchecked_transaction, recipient_btc_address, expected_btc, op_return_id, diff --git a/crates/replace/src/lib.rs b/crates/replace/src/lib.rs index ad5c4b82b4..ba741b8beb 100644 --- a/crates/replace/src/lib.rs +++ b/crates/replace/src/lib.rs @@ -27,13 +27,14 @@ use mocktopus::macros::mockable; use crate::types::{BalanceOf, ReplaceRequestExt, Version}; pub use crate::types::{DefaultReplaceRequest, ReplaceRequest, ReplaceRequestStatus}; -use bitcoin::types::{MerkleProof, Transaction}; +use bitcoin::types::FullTransactionProof; use btc_relay::BtcAddress; use currency::Amount; pub use default_weights::WeightInfo; use frame_support::{ dispatch::{DispatchError, DispatchResult}, ensure, + pallet_prelude::Weight, traits::Get, transactional, }; @@ -45,6 +46,31 @@ use vault_registry::{types::CurrencyId, CurrencySource}; pub use pallet::*; +/// Complexity: +/// - `O(H + I + O + B)` where: +/// - `H` is the number of hashes in the merkle tree +/// - `I` is the number of transaction inputs +/// - `O` is the number of transaction outputs +/// - `B` is `transaction` size in bytes (length-fee-bounded) +fn weight_for_execute_replace(proof: &FullTransactionProof) -> Weight { + { + let h = proof.user_tx_proof.merkle_proof.hashes.len() as u32; + let i = proof.user_tx_proof.transaction.inputs.len() as u32; + let o = proof.user_tx_proof.transaction.outputs.len() as u32; + let b = proof.user_tx_proof.tx_encoded_len; + ::WeightInfo::execute_pending_replace(h, i, o, b) + .max(::WeightInfo::execute_cancelled_replace(h, i, o, b)) + } + .saturating_add({ + let h = proof.coinbase_proof.merkle_proof.hashes.len() as u32; + let i = proof.coinbase_proof.transaction.inputs.len() as u32; + let o = proof.coinbase_proof.transaction.outputs.len() as u32; + let b = proof.coinbase_proof.tx_encoded_len; + ::WeightInfo::execute_pending_replace(h, i, o, b) + .max(::WeightInfo::execute_cancelled_replace(h, i, o, b)) + }) +} + #[frame_support::pallet] pub mod pallet { use super::*; @@ -266,32 +292,17 @@ pub mod pallet { /// * `replace_id` - the ID of the replacement request /// * 'merkle_proof' - the merkle root of the block /// * `raw_tx` - the transaction id in bytes - /// - /// ## Complexity: - /// - `O(H + I + O + B)` where: - /// - `H` is the number of hashes in the merkle tree - /// - `I` is the number of transaction inputs - /// - `O` is the number of transaction outputs - /// - `B` is `transaction` size in bytes (length-fee-bounded) #[pallet::call_index(3)] - #[pallet::weight({ - let h = merkle_proof.hashes.len() as u32; - let i = transaction.inputs.len() as u32; - let o = transaction.outputs.len() as u32; - let b = *length_bound; - ::WeightInfo::execute_pending_replace(h, i, o, b) - .max(::WeightInfo::execute_cancelled_replace(h, i, o, b)) - })] + #[pallet::weight(weight_for_execute_replace::(unchecked_transaction))] #[transactional] pub fn execute_replace( origin: OriginFor, replace_id: H256, - merkle_proof: MerkleProof, - transaction: Transaction, - #[pallet::compact] length_bound: u32, + unchecked_transaction: FullTransactionProof, ) -> DispatchResultWithPostInfo { let _ = ensure_signed(origin)?; - Self::_execute_replace(replace_id, merkle_proof, transaction, length_bound)?; + + Self::_execute_replace(replace_id, unchecked_transaction)?; Ok(().into()) } @@ -491,12 +502,7 @@ impl Pallet { Ok(()) } - fn _execute_replace( - replace_id: H256, - merkle_proof: MerkleProof, - transaction: Transaction, - length_bound: u32, - ) -> DispatchResult { + fn _execute_replace(replace_id: H256, unchecked_transaction: FullTransactionProof) -> DispatchResult { // retrieve the replace request using the id parameter // we can still execute cancelled requests let replace = Self::get_open_or_cancelled_replace_request(&replace_id)?; @@ -511,9 +517,7 @@ impl Pallet { // check the transaction inclusion and validity ext::btc_relay::verify_and_validate_op_return_transaction::( - merkle_proof, - transaction, - length_bound, + unchecked_transaction, replace.btc_address, replace.amount, replace_id, diff --git a/crates/replace/src/tests.rs b/crates/replace/src/tests.rs index e88e3a9afb..51cf8fa241 100644 --- a/crates/replace/src/tests.rs +++ b/crates/replace/src/tests.rs @@ -3,6 +3,7 @@ use crate::{ *, }; +use bitcoin::merkle::PartialTransactionProof; use btc_relay::BtcAddress; use currency::Amount; use frame_support::{assert_err, assert_ok}; @@ -44,6 +45,21 @@ fn wrapped(amount: u128) -> Amount { Amount::new(amount, DEFAULT_WRAPPED_CURRENCY) } +fn get_some_unchecked_transaction() -> FullTransactionProof { + FullTransactionProof { + user_tx_proof: PartialTransactionProof { + transaction: Default::default(), + tx_encoded_len: u32::MAX, + merkle_proof: Default::default(), + }, + coinbase_proof: PartialTransactionProof { + transaction: Default::default(), + tx_encoded_len: u32::MAX, + merkle_proof: Default::default(), + }, + } +} + mod request_replace_tests { use super::*; @@ -189,7 +205,7 @@ mod execute_replace_test { Replace::replace_period.mock_safe(|| MockResult::Return(20)); ext::btc_relay::has_request_expired::.mock_safe(|_, _, _| MockResult::Return(Ok(false))); ext::btc_relay::verify_and_validate_op_return_transaction:: - .mock_safe(|_, _, _, _, _, _| MockResult::Return(Ok(()))); + .mock_safe(|_, _, _, _| MockResult::Return(Ok(()))); ext::vault_registry::replace_tokens::.mock_safe(|_, _, _, _| MockResult::Return(Ok(()))); Amount::::unlock_on.mock_safe(|_, _| MockResult::Return(Ok(()))); ext::vault_registry::transfer_funds::.mock_safe(|_, _, _| MockResult::Return(Ok(()))); @@ -204,9 +220,7 @@ mod execute_replace_test { setup_mocks(); assert_ok!(Replace::_execute_replace( H256::zero(), - Default::default(), - Default::default(), - u32::MAX, + get_some_unchecked_transaction() )); assert_event_matches!(Event::ExecuteReplace { replace_id: _, @@ -231,9 +245,7 @@ mod execute_replace_test { assert_ok!(Replace::_execute_replace( H256::zero(), - Default::default(), - Default::default(), - u32::MAX, + get_some_unchecked_transaction() )); assert_event_matches!(Event::ExecuteReplace { replace_id: _, diff --git a/data/bitcoin-testdata.gzip b/data/bitcoin-testdata.gzip index d9d12418ed..b151e448d8 100644 Binary files a/data/bitcoin-testdata.gzip and b/data/bitcoin-testdata.gzip differ diff --git a/parachain/runtime/runtime-tests/src/bitcoin_data.rs b/parachain/runtime/runtime-tests/src/bitcoin_data.rs index 57a7a42a5f..e97a115127 100644 --- a/parachain/runtime/runtime-tests/src/bitcoin_data.rs +++ b/parachain/runtime/runtime-tests/src/bitcoin_data.rs @@ -1,4 +1,7 @@ -use bitcoin::types::{BlockHeader, H256Le, MerkleProof}; +use bitcoin::{ + parser::parse_transaction, + types::{BlockHeader, H256Le, MerkleProof}, +}; use flate2::read::GzDecoder; use serde::Deserialize; use std::{ @@ -43,6 +46,7 @@ impl Block { #[derive(Clone, Debug, Deserialize)] pub struct Transaction { txid: String, + pub raw_tx: String, raw_merkle_proof: String, } @@ -54,6 +58,15 @@ impl Transaction { pub fn get_merkle_proof(&self) -> MerkleProof { MerkleProof::parse(&hex::decode(&self.raw_merkle_proof).expect(ERR_INVALID_PROOF)).expect(ERR_INVALID_PROOF) } + + pub fn get_tx(&self) -> bitcoin::types::Transaction { + let raw_tx_bytes = &hex::decode(&self.raw_tx).expect(ERR_INVALID_PROOF); + parse_transaction(&raw_tx_bytes).unwrap() + } + + pub fn get_tx_len(&self) -> u32 { + self.raw_tx.len() as u32 + } } fn read_data(data: &str) -> String { diff --git a/parachain/runtime/runtime-tests/src/parachain/btc_relay.rs b/parachain/runtime/runtime-tests/src/parachain/btc_relay.rs index d0dfb05892..6e45432129 100644 --- a/parachain/runtime/runtime-tests/src/parachain/btc_relay.rs +++ b/parachain/runtime/runtime-tests/src/parachain/btc_relay.rs @@ -2,8 +2,36 @@ use crate::{ bitcoin_data::{get_bitcoin_testdata, get_fork_testdata}, setup::{assert_eq, *}, }; +use bitcoin::{formatter::TryFormat, merkle::PartialTransactionProof}; use btc_relay::DIFFICULTY_ADJUSTMENT_INTERVAL; +#[test] +#[cfg_attr(feature = "skip-slow-tests", ignore)] +fn integration_test_transaction_formatting_and_txid_calculation() { + // reduce number of blocks to reduce testing time, but higher than 2016 blocks for difficulty adjustment + const BLOCKS_TO_TEST: usize = 2 * 2016 + 1; + + // load blocks with transactions + let test_data = get_bitcoin_testdata(); + + assert!(test_data.len() > BLOCKS_TO_TEST); + + // verify all transactions + for block in test_data.iter().take(BLOCKS_TO_TEST) { + assert!(block.test_txs[0].get_tx().is_coinbase()); + for tx in block.test_txs.iter() { + let mut reconstructed_raw_tx = vec![]; + tx.get_tx().try_format(&mut reconstructed_raw_tx).unwrap(); + let reconstructed_hex_tx = hex::encode(reconstructed_raw_tx); + assert_eq!(reconstructed_hex_tx, tx.raw_tx); + + let reconstructed_txid = tx.get_tx().tx_id_bounded(tx.get_tx_len()).unwrap().to_hex_be(); + + assert_eq!(reconstructed_txid, tx.get_txid().to_hex_be()); + } + } +} + #[test] #[cfg_attr(feature = "skip-slow-tests", ignore)] fn integration_test_submit_block_headers_and_verify_transaction_inclusion() { @@ -13,7 +41,7 @@ fn integration_test_submit_block_headers_and_verify_transaction_inclusion() { assert!(!BTCRelayPallet::disable_difficulty_check()); // reduce number of blocks to reduce testing time, but higher than 2016 blocks for difficulty adjustment - const BLOCKS_TO_TEST: usize = 5_000; + const BLOCKS_TO_TEST: usize = 2 * 2016 + 1; // load blocks with transactions let test_data = get_bitcoin_testdata(); @@ -39,6 +67,9 @@ fn integration_test_submit_block_headers_and_verify_transaction_inclusion() { }) .dispatch(origin_of(account_of(ALICE)))); + assert_eq!(skip_blocks, 0); + assert!(test_data.len() > skip_blocks + BLOCKS_TO_TEST); + for block in test_data.iter().skip(skip_blocks + 1).take(BLOCKS_TO_TEST) { let block_header = block.get_block_header(); let prev_header_hash = block_header.hash_prev_block; @@ -64,15 +95,29 @@ fn integration_test_submit_block_headers_and_verify_transaction_inclusion() { // verify all transactions let current_height = btc_relay::Pallet::::get_best_block_height(); for block in test_data.iter().skip(skip_blocks).take(BLOCKS_TO_TEST) { - for tx in &block.test_txs { - let txid = tx.get_txid(); - let merkle_proof = tx.get_merkle_proof(); + let coinbase = block.test_txs[0].clone(); + let coinbase_proof = PartialTransactionProof { + merkle_proof: coinbase.get_merkle_proof(), + transaction: coinbase.get_tx(), + tx_encoded_len: coinbase.get_tx_len(), + }; + + for tx in block.test_txs.iter().skip(1) { + let user_tx_proof = PartialTransactionProof { + merkle_proof: tx.get_merkle_proof(), + transaction: tx.get_tx(), + tx_encoded_len: tx.get_tx_len(), + }; + let full_proof = FullTransactionProof { + coinbase_proof: coinbase_proof.clone(), + user_tx_proof, + }; if block.height <= current_height - CONFIRMATIONS + 1 { - assert_ok!(BTCRelayPallet::_verify_transaction_inclusion(txid, merkle_proof, None)); + assert_ok!(BTCRelayPallet::_verify_transaction_inclusion(full_proof, None)); } else { // expect to fail due to insufficient confirmations assert_noop!( - BTCRelayPallet::_verify_transaction_inclusion(txid, merkle_proof, None), + BTCRelayPallet::_verify_transaction_inclusion(full_proof, None), BTCRelayError::BitcoinConfirmations ); } diff --git a/parachain/runtime/runtime-tests/src/parachain/issue.rs b/parachain/runtime/runtime-tests/src/parachain/issue.rs index cd66caae71..420539a826 100644 --- a/parachain/runtime/runtime-tests/src/parachain/issue.rs +++ b/parachain/runtime/runtime-tests/src/parachain/issue.rs @@ -489,7 +489,7 @@ fn integration_test_issue_wrapped_execute_succeeds() { let total_amount_btc = amount_btc + fee_amount_btc; // send the btc from the user to the vault - let (_tx_id, _height, merkle_proof, transaction) = generate_transaction_and_mine( + let (_tx_id, _height, transaction) = generate_transaction_and_mine( Default::default(), vec![], vec![(vault_btc_address, total_amount_btc)], @@ -501,9 +501,7 @@ fn integration_test_issue_wrapped_execute_succeeds() { // alice executes the issue by confirming the btc transaction assert_ok!(RuntimeCall::Issue(IssueCall::execute_issue { issue_id: issue_id, - merkle_proof, - transaction, - length_bound: u32::MAX, + unchecked_transaction: transaction }) .dispatch(origin_of(account_of(vault_proof_submitter)))); }); @@ -614,7 +612,7 @@ mod execute_pending_issue_tests { fn integration_test_issue_execute_precond_rawtx_valid() { test_with_initialized_vault(|vault_id| { let (issue_id, issue) = request_issue(&vault_id, vault_id.wrapped(1000)); - let (_tx_id, _height, merkle_proof, mut transaction) = TransactionGenerator::new() + let (_tx_id, _height, mut unchecked_transaction) = TransactionGenerator::new() .with_outputs(vec![(issue.btc_address, wrapped(1000))]) .mine(); @@ -622,13 +620,12 @@ mod execute_pending_issue_tests { // send to wrong address let bogus_address = BtcAddress::P2WPKHv0(H160::random()); - transaction.outputs[0] = TransactionOutput::payment(1000, &bogus_address); + unchecked_transaction.user_tx_proof.transaction.outputs[0] = + TransactionOutput::payment(1000, &bogus_address); assert_noop!( RuntimeCall::Issue(IssueCall::execute_issue { issue_id: issue_id, - merkle_proof, - transaction, - length_bound: u32::MAX, + unchecked_transaction }) .dispatch(origin_of(account_of(CAROL))), BTCRelayError::InvalidTxid @@ -641,20 +638,19 @@ mod execute_pending_issue_tests { fn integration_test_issue_execute_precond_proof_valid() { test_with_initialized_vault(|vault_id| { let (issue_id, issue) = request_issue(&vault_id, vault_id.wrapped(1000)); - let (_tx_id, _height, mut merkle_proof, transaction) = TransactionGenerator::new() + let (_tx_id, _height, mut transaction) = TransactionGenerator::new() .with_outputs(vec![(issue.btc_address, wrapped(1))]) .mine(); SecurityPallet::set_active_block_number(SecurityPallet::active_block_number() + CONFIRMATIONS); // mangle block header in merkle proof - merkle_proof.block_header = Default::default(); + transaction.user_tx_proof.merkle_proof.block_header = Default::default(); + transaction.coinbase_proof.merkle_proof.block_header = Default::default(); assert_noop!( RuntimeCall::Issue(IssueCall::execute_issue { issue_id: issue_id, - merkle_proof, - transaction, - length_bound: u32::MAX, + unchecked_transaction: transaction }) .dispatch(origin_of(account_of(CAROL))), BTCRelayError::BlockNotFound diff --git a/parachain/runtime/runtime-tests/src/parachain/redeem.rs b/parachain/runtime/runtime-tests/src/parachain/redeem.rs index 54619a6ba2..d6527cf1dc 100644 --- a/parachain/runtime/runtime-tests/src/parachain/redeem.rs +++ b/parachain/runtime/runtime-tests/src/parachain/redeem.rs @@ -406,9 +406,7 @@ mod spec_based_tests { assert_noop!( RuntimeCall::Redeem(RedeemCall::execute_redeem { redeem_id: H256::random(), - merkle_proof: Default::default(), - transaction: Default::default(), - length_bound: u32::MAX, + unchecked_transaction: dummy_tx() }) .dispatch(origin_of(account_of(VAULT))), RedeemError::RedeemIdNotFound @@ -468,22 +466,22 @@ mod spec_based_tests { ); // The `merkleProof` MUST contain a valid proof of of `rawTX` - let (_tx_id, _tx_block_height, _merkle_proof, transaction) = generate_transaction_and_mine( + let (_tx_id, _tx_block_height, mut transaction) = generate_transaction_and_mine( Default::default(), vec![], vec![(user_btc_address, redeem.amount_btc())], vec![redeem_id], ); let invalid_merkle_proof = hex::decode("00000020b0b3d77b97015b519553423c96642b33ca534c50ecefd133640000000000000029a0a725684aeca24af83e3ba0a3e3ee56adfdf032d19e5acba6d0a262e1580ca354915fd4c8001ac42a7b3a1000000005df41db041b26536b5b7fd7aeea4ea6bdb64f7039e4a566b1fa138a07ed2d3705932955c94ee4755abec003054128b10e0fbcf8dedbbc6236e23286843f1f82a018dc7f5f6fba31aa618fab4acad7df5a5046b6383595798758d30d68c731a14043a50d7cb8560d771fad70c5e52f6d7df26df13ca457655afca2cbab2e3b135c0383525b28fca31296c809641205962eb353fb88a9f3602e98a93b1e9ffd469b023d00").unwrap(); + transaction.user_tx_proof.merkle_proof = MerkleProof::parse(&invalid_merkle_proof).unwrap(); + transaction.user_tx_proof.merkle_proof = MerkleProof::parse(&invalid_merkle_proof).unwrap(); assert_noop!( RuntimeCall::Redeem(RedeemCall::execute_redeem { redeem_id: redeem_id, - merkle_proof: MerkleProof::parse(&invalid_merkle_proof).unwrap(), - transaction, - length_bound: u32::MAX, + unchecked_transaction: transaction }) .dispatch(origin_of(account_of(VAULT))), - BTCRelayError::BlockNotFound + BTCRelayError::InvalidTxid ); let parachain_state_before_execution = ParachainState::get(&vault_id); execute_redeem(redeem_id); @@ -1221,7 +1219,7 @@ fn integration_test_premium_redeem_wrapped_execute() { let redeem = RedeemPallet::get_open_redeem_request_from_id(&redeem_id).unwrap(); // send the btc from the vault to the user - let (_tx_id, _tx_block_height, merkle_proof, transaction) = generate_transaction_and_mine( + let (_tx_id, _tx_block_height, transaction) = generate_transaction_and_mine( Default::default(), vec![], vec![(user_btc_address, redeem.amount_btc())], @@ -1232,9 +1230,7 @@ fn integration_test_premium_redeem_wrapped_execute() { assert_ok!(RuntimeCall::Redeem(RedeemCall::execute_redeem { redeem_id, - merkle_proof, - transaction, - length_bound: u32::MAX, + unchecked_transaction: transaction }) .dispatch(origin_of(account_of(VAULT)))); @@ -1290,7 +1286,7 @@ fn integration_test_multiple_redeems_multiple_op_returns() { let redeem_2 = RedeemPallet::get_open_redeem_request_from_id(&redeem_2_id).unwrap(); // try to fulfill both redeem requests in a single transaction - let (_tx_id, _tx_block_height, merkle_proof, transaction) = generate_transaction_and_mine( + let (_tx_id, _tx_block_height, transaction) = generate_transaction_and_mine( Default::default(), vec![], vec![ @@ -1305,9 +1301,7 @@ fn integration_test_multiple_redeems_multiple_op_returns() { assert_err!( RuntimeCall::Redeem(RedeemCall::execute_redeem { redeem_id: redeem_1_id, - merkle_proof: merkle_proof.clone(), - transaction: transaction.clone(), - length_bound: u32::MAX, + unchecked_transaction: transaction.clone() }) .dispatch(origin_of(account_of(VAULT))), BTCRelayError::InvalidOpReturnTransaction @@ -1316,9 +1310,7 @@ fn integration_test_multiple_redeems_multiple_op_returns() { assert_err!( RuntimeCall::Redeem(RedeemCall::execute_redeem { redeem_id: redeem_2_id, - merkle_proof: merkle_proof.clone(), - transaction: transaction.clone(), - length_bound: u32::MAX, + unchecked_transaction: transaction }) .dispatch(origin_of(account_of(VAULT))), BTCRelayError::InvalidOpReturnTransaction @@ -1343,7 +1335,7 @@ fn integration_test_single_redeem_multiple_op_returns() { let redeem_id = assert_redeem_request_event(); let redeem = RedeemPallet::get_open_redeem_request_from_id(&redeem_id).unwrap(); - let (_tx_id, _tx_block_height, merkle_proof, transaction) = generate_transaction_and_mine( + let (_tx_id, _tx_block_height, transaction) = generate_transaction_and_mine( Default::default(), vec![], vec![(user_btc_address, redeem.amount_btc())], @@ -1358,9 +1350,7 @@ fn integration_test_single_redeem_multiple_op_returns() { assert_err!( RuntimeCall::Redeem(RedeemCall::execute_redeem { redeem_id, - merkle_proof, - transaction, - length_bound: u32::MAX, + unchecked_transaction: transaction }) .dispatch(origin_of(account_of(VAULT))), BTCRelayError::InvalidOpReturnTransaction diff --git a/parachain/runtime/runtime-tests/src/parachain/replace.rs b/parachain/runtime/runtime-tests/src/parachain/replace.rs index b00ef86299..1ef849c1d0 100644 --- a/parachain/runtime/runtime-tests/src/parachain/replace.rs +++ b/parachain/runtime/runtime-tests/src/parachain/replace.rs @@ -702,7 +702,7 @@ fn execute_replace_with_amount(replace_id: H256, amount: Amount) -> Dis let replace = ReplacePallet::get_open_or_cancelled_replace_request(&replace_id).unwrap(); // send the btc from the old_vault to the new_vault - let (_tx_id, _tx_block_height, merkle_proof, transaction) = generate_transaction_and_mine( + let (_tx_id, _tx_block_height, transaction) = generate_transaction_and_mine( Default::default(), vec![], vec![(replace.btc_address, amount)], @@ -713,9 +713,7 @@ fn execute_replace_with_amount(replace_id: H256, amount: Amount) -> Dis RuntimeCall::Replace(ReplaceCall::execute_replace { replace_id, - merkle_proof, - transaction, - length_bound: u32::MAX, + unchecked_transaction: transaction, }) .dispatch(origin_of(account_of(OLD_VAULT))) } diff --git a/parachain/runtime/runtime-tests/src/setup.rs b/parachain/runtime/runtime-tests/src/setup.rs index 05528f5847..018643e590 100644 --- a/parachain/runtime/runtime-tests/src/setup.rs +++ b/parachain/runtime/runtime-tests/src/setup.rs @@ -1,4 +1,5 @@ pub use crate::utils::*; +use bitcoin::merkle::PartialTransactionProof; pub use codec::Encode; use frame_support::traits::GenesisBuild; pub use frame_support::{assert_noop, assert_ok, traits::Currency, BoundedVec}; @@ -41,6 +42,21 @@ mod interlay_imports { pub const DEFAULT_GRIEFING_CURRENCY: CurrencyId = DEFAULT_NATIVE_CURRENCY; } +pub fn dummy_tx() -> FullTransactionProof { + FullTransactionProof { + coinbase_proof: PartialTransactionProof { + merkle_proof: Default::default(), + transaction: Default::default(), + tx_encoded_len: u32::MAX, + }, + user_tx_proof: PartialTransactionProof { + merkle_proof: Default::default(), + transaction: Default::default(), + tx_encoded_len: u32::MAX, + }, + } +} + pub struct ExtBuilder { test_externalities: sp_io::TestExternalities, } diff --git a/parachain/runtime/runtime-tests/src/utils.rs b/parachain/runtime/runtime-tests/src/utils.rs index 71c194b250..be988d1f99 100644 --- a/parachain/runtime/runtime-tests/src/utils.rs +++ b/parachain/runtime/runtime-tests/src/utils.rs @@ -1,5 +1,6 @@ use crate::setup::{assert_eq, *}; +use bitcoin::merkle::PartialTransactionProof; pub use bitcoin::types::{Block, TransactionInputSource, *}; pub use btc_relay::{BtcAddress, BtcPublicKey}; use currency::Amount; @@ -1209,7 +1210,7 @@ impl TransactionGenerator { self.relayer = relayer; self } - pub fn mine(&self) -> (H256Le, u32, MerkleProof, Transaction) { + pub fn mine(&self) -> (H256Le, u32, FullTransactionProof) { let mut height = BTCRelayPallet::get_best_block_height() + 1; let extra_confirmations = self.confirmations - 1; @@ -1287,6 +1288,9 @@ impl TransactionGenerator { let tx_block_height = height; let merkle_proof = block.merkle_proof(&[tx_id]).unwrap(); + let coinbase_tx = block.transactions[0].clone(); + let coinbase_merkle_proof = block.merkle_proof(&[coinbase_tx.tx_id()]).unwrap(); + self.relay(height, &block, block.header); // Mine six new blocks to get over required confirmations @@ -1308,7 +1312,20 @@ impl TransactionGenerator { prev_block_hash = conf_block.header.hash; } - (tx_id, tx_block_height, merkle_proof, transaction) + let unchecked_transaction = FullTransactionProof { + coinbase_proof: PartialTransactionProof { + tx_encoded_len: coinbase_tx.size_no_witness() as u32, + transaction: coinbase_tx, + merkle_proof: coinbase_merkle_proof, + }, + user_tx_proof: PartialTransactionProof { + tx_encoded_len: transaction.size_no_witness() as u32, + transaction: transaction, + merkle_proof, + }, + }; + + (tx_id, tx_block_height, unchecked_transaction) } fn relay(&self, height: u32, block: &Block, block_header: BlockHeader) { @@ -1331,7 +1348,7 @@ pub fn generate_transaction_and_mine( inputs: Vec<(Transaction, u32, Option)>, outputs: Vec<(BtcAddress, Amount)>, return_data: Vec, -) -> (H256Le, u32, MerkleProof, Transaction) { +) -> (H256Le, u32, FullTransactionProof) { TransactionGenerator::new() .with_script(signer.to_p2pkh_script_sig(vec![1; 32]).as_bytes()) .with_inputs(inputs) diff --git a/parachain/runtime/runtime-tests/src/utils/issue_utils.rs b/parachain/runtime/runtime-tests/src/utils/issue_utils.rs index 4358d5b891..a4149769b2 100644 --- a/parachain/runtime/runtime-tests/src/utils/issue_utils.rs +++ b/parachain/runtime/runtime-tests/src/utils/issue_utils.rs @@ -78,7 +78,7 @@ pub struct ExecuteIssueBuilder { submitter: AccountId, register_vault_with_currency_id: Option, relayer: Option<[u8; 32]>, - execution_tx: Option<(MerkleProof, Transaction)>, + execution_tx: Option, } impl ExecuteIssueBuilder { @@ -128,13 +128,11 @@ impl ExecuteIssueBuilder { pub fn execute_prepared(&self) -> DispatchResultWithPostInfo { VaultRegistryPallet::collateral_integrity_check(); - if let Some((merkle_proof, transaction)) = &self.execution_tx { + if let Some(transaction) = &self.execution_tx { // alice executes the issuerequest by confirming the btc transaction let ret = RuntimeCall::Issue(IssueCall::execute_issue { issue_id: self.issue_id, - merkle_proof: merkle_proof.clone(), - transaction: transaction.clone(), - length_bound: u32::MAX, + unchecked_transaction: transaction.clone(), }) .dispatch(origin_of(self.submitter.clone())); VaultRegistryPallet::collateral_integrity_check(); @@ -146,7 +144,7 @@ impl ExecuteIssueBuilder { pub fn prepare_for_execution(&mut self) -> &mut Self { // send the btc from the user to the vault - let (_tx_id, _height, merkle_proof, transaction) = TransactionGenerator::new() + let (_tx_id, _height, unchecked_transaction) = TransactionGenerator::new() .with_outputs(vec![(self.issue.btc_address, self.amount)]) .with_relayer(self.relayer) .mine(); @@ -160,7 +158,7 @@ impl ExecuteIssueBuilder { ); } - self.execution_tx = Some((merkle_proof, transaction)); + self.execution_tx = Some(unchecked_transaction); self } diff --git a/parachain/runtime/runtime-tests/src/utils/redeem_utils.rs b/parachain/runtime/runtime-tests/src/utils/redeem_utils.rs index 32e6974db4..fbb266c8fb 100644 --- a/parachain/runtime/runtime-tests/src/utils/redeem_utils.rs +++ b/parachain/runtime/runtime-tests/src/utils/redeem_utils.rs @@ -56,7 +56,7 @@ impl ExecuteRedeemBuilder { #[transactional] pub fn execute(&self) -> DispatchResultWithPostInfo { // send the btc from the user to the vault - let (_tx_id, _height, merkle_proof, transaction) = TransactionGenerator::new() + let (_tx_id, _height, transaction) = TransactionGenerator::new() .with_outputs(vec![(self.redeem.btc_address, self.amount)]) .with_op_return(vec![self.redeem_id]) .mine(); @@ -68,9 +68,7 @@ impl ExecuteRedeemBuilder { // alice executes the redeemrequest by confirming the btc transaction let ret = RuntimeCall::Redeem(RedeemCall::execute_redeem { redeem_id: self.redeem_id, - merkle_proof, - transaction, - length_bound: u32::MAX, + unchecked_transaction: transaction, }) .dispatch(origin_of(self.submitter.clone())); VaultRegistryPallet::collateral_integrity_check(); @@ -199,7 +197,7 @@ pub fn assert_redeem_error( error: BTCRelayError, ) -> u32 { // send the btc from the vault to the user - let (_tx_id, _tx_block_height, merkle_proof, transaction) = generate_transaction_and_mine( + let (_tx_id, _tx_block_height, unchecked_transaction) = generate_transaction_and_mine( Default::default(), vec![], vec![(user_btc_address, amount)], @@ -211,9 +209,7 @@ pub fn assert_redeem_error( assert_noop!( RuntimeCall::Redeem(RedeemCall::execute_redeem { redeem_id: redeem_id, - merkle_proof, - transaction, - length_bound: u32::MAX, + unchecked_transaction }) .dispatch(origin_of(account_of(VAULT))), error diff --git a/scripts/fetch_bitcoin_data.py b/scripts/fetch_bitcoin_data.py index 044b6b91ab..a47a611f15 100644 --- a/scripts/fetch_bitcoin_data.py +++ b/scripts/fetch_bitcoin_data.py @@ -10,8 +10,8 @@ TESTDATA_FILE = os.path.join(TESTDATA_DIR, "bitcoin-testdata.json") TESTDATA_ZIPPED = os.path.join(TESTDATA_DIR, "bitcoin-testdata.gzip") BASE_URL = "https://blockstream.info/api" -MAX_BITCOIN_BLOCKS = 10_000 -MAX_TXS_PER_BITCOIN_BLOCK = 20 +MAX_BITCOIN_BLOCKS = 100 # recommended to run this script in a loop +MAX_TXS_PER_BITCOIN_BLOCK = 2 ####################### # Blockstream queries # @@ -71,10 +71,15 @@ async def get_raw_merkle_proof(txid): uri = "/tx/{}/merkleblock-proof".format(txid) return await query_binary(uri) +async def get_raw_tx(txid): + uri = "/tx/{}/hex".format(txid) + return await query_binary(uri) + async def get_txid_with_proof(txid): try: return { "txid": txid, + "raw_tx": await get_raw_tx(txid), "raw_merkle_proof": await get_raw_merkle_proof(txid) } except: @@ -143,8 +148,9 @@ async def get_and_store_block(height): get_block_txids(blockhash) ) # select txids randomly for testing - max_to_sample = min(len(txids), MAX_TXS_PER_BITCOIN_BLOCK) + max_to_sample = min(len(txids), MAX_TXS_PER_BITCOIN_BLOCK - 1) test_txids = random.sample(txids, max_to_sample) + test_txids.insert(0, txids[0]) # get the tx merkle proof test_txs = [] test_txs = await asyncio.gather( @@ -160,9 +166,9 @@ async def get_and_store_block(height): store_block(block) -async def get_testdata(number, tip_height): +async def get_testdata(start, end): # query number of blocks - for i in range(tip_height - number, tip_height): + for i in range(start, end): await get_and_store_block(i) async def main(): @@ -174,19 +180,20 @@ async def main(): tip_height = await get_tip_height() print("Current height {}".format(tip_height)) blocks = read_testdata() + last_block_in_db = blocks[-1]['height'] if blocks: - if blocks[-1]['height'] == tip_height: + if last_block_in_db == tip_height: print("Latest blocks already downloaded") number_blocks = 0 return else: # determine how many block to download - delta = tip_height - blocks[-1]["height"] - 1 - number_blocks = delta if delta <= max_num_blocks else max_num_blocks + remaining_blocks = tip_height - last_block_in_db + number_blocks = remaining_blocks if remaining_blocks <= max_num_blocks else max_num_blocks # download new blocks and store them print("Getting {} blocks".format(number_blocks)) - await get_testdata(number_blocks, tip_height) + await get_testdata(last_block_in_db + 1, last_block_in_db + number_blocks + 1) except KeyboardInterrupt: break except Exception as e: