From c441a6c66b61857e7764469e82df416cfabcaee4 Mon Sep 17 00:00:00 2001 From: Ermal Kaleci Date: Thu, 11 Jan 2024 12:53:02 +0100 Subject: [PATCH] refactor --- executor/src/light_client.rs | 478 ++++++++---------- packages/core/src/blockchain/index.ts | 38 +- packages/core/src/blockchain/storage-layer.ts | 2 +- .../__snapshots__/executor.test.ts.snap | 16 +- .../wasm-executor/browser-wasm-executor.js | 11 +- .../core/src/wasm-executor/executor.test.ts | 7 +- packages/core/src/wasm-executor/index.ts | 31 +- .../core/src/wasm-executor/light-client.ts | 80 +-- .../src/wasm-executor/node-wasm-executor.js | 11 +- 9 files changed, 300 insertions(+), 374 deletions(-) diff --git a/executor/src/light_client.rs b/executor/src/light_client.rs index 6056ccae..2e57fe88 100644 --- a/executor/src/light_client.rs +++ b/executor/src/light_client.rs @@ -30,39 +30,42 @@ export interface JsLightClientCallback { connectionStreamOpen: (connectionId: number) => void connectionStreamReset: (connectionId: number, streamId: number) => void streamSend: (connectionId: number, data: Uint8Array) => void - storageResponse: (response: StorageResponse) => void - blockResponse: (response: BlocksResponse) => void + queryResponse: (requestId: number, response: Response) => void } -export type StorageRequest = { - id: number - blockHash: HexString - keys: HexString[] - retries: number -} - -export type BlockRequest = { - id: number - blockNumber: number | null - blockHash: HexString | null - retries: number -} - -export type StorageResponse = { - id: number - items: [HexString, HexString][] - errorReason?: string -} +export type Request = + { + storage: { + hash: HexString + keys: HexString[] + } + } + | + { + block: { + number: number | null + hash: HexString | null + header: boolean + body: boolean + } + } -export type BlocksResponse = { - id: number - blocks: { - hash: HexString, - header: HexString, - body: HexString[], - }[] - errorReason?: string -} +export type Response = + { + Storage: [HexString, HexString][] + } + | + { + Block: { + hash: HexString + header: HexString + body: HexString[] + } + } + | + { + Error: string + } "#; #[wasm_bindgen] @@ -93,11 +96,8 @@ extern "C" { #[wasm_bindgen(structural, method, js_name = "resetConnection")] pub fn reset_connection(this: &JsLightClientCallback, conn_id: u32); - #[wasm_bindgen(structural, method, js_name = "storageResponse")] - pub fn storage_response(this: &JsLightClientCallback, response: JsValue); - - #[wasm_bindgen(structural, method, js_name = "blockResponse")] - pub fn block_response(this: &JsLightClientCallback, response: JsValue); + #[wasm_bindgen(structural, method, js_name = "queryResponse")] + pub fn query_response(this: &JsLightClientCallback, request_id: usize, response: JsValue); } unsafe impl Sync for JsLightClientCallback {} @@ -159,28 +159,24 @@ struct NetworkServiceConfig { #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -struct StorageRequest { - id: usize, - block_hash: HashHexString, - keys: Vec, - retries: usize, -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -struct StorageResponse { - id: usize, - items: Vec<(HexString, HexString)>, - error_reason: Option, +enum Request { + Storage { + hash: HashHexString, + keys: Vec, + }, + Block { + hash: Option, + number: Option, + header: bool, + body: bool, + }, } #[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -struct BlockRequest { - id: usize, - block_hash: Option, - block_number: Option, - retries: usize, +enum Response { + Storage(Vec<(HexString, HexString)>), + Block(Block), + Error(String), } #[derive(Debug, Serialize, Deserialize)] @@ -191,14 +187,6 @@ struct Block { body: Vec, } -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -struct BlocksResponse { - id: usize, - blocks: Vec, - error_reason: Option, -} - #[wasm_bindgen] pub async fn start_network_service( config: JsValue, @@ -296,39 +284,11 @@ pub async fn start_network_service( } #[wasm_bindgen] -pub fn stream_message(connection_id: u32, stream_id: u32, data: Vec) { - crate::platform::stream_message(connection_id, stream_id, data); -} - -#[wasm_bindgen] -pub fn stream_writable_bytes(connection_id: u32, stream_id: u32, num_bytes: u32) { - crate::platform::stream_writable_bytes(connection_id, stream_id, num_bytes); -} - -#[wasm_bindgen] -pub fn connection_reset(connection_id: u32, data: Vec) { - crate::platform::connection_reset(connection_id, data); -} - -#[wasm_bindgen] -pub fn stream_reset(connection_id: u32, stream_id: u32, data: Vec) { - crate::platform::stream_reset(connection_id, stream_id, data); -} - -#[wasm_bindgen] -pub fn timer_finished(callback: JsLightClientCallback) { - crate::timers::timer_finished(Arc::new(callback)); -} - -#[wasm_bindgen] -pub fn connection_stream_opened(connection_id: u32, stream_id: u32, outbound: u32) { - crate::platform::connection_stream_opened(connection_id, stream_id, outbound); -} - -#[wasm_bindgen] -pub async fn storage_request( +pub fn query_chain( chain_id: usize, + request_id: usize, request: JsValue, + mut retries: usize, callback: JsLightClientCallback, ) -> Result<(), JsValue> { setup_console(None); @@ -337,96 +297,146 @@ pub async fn storage_request( let chain = chains.get(&chain_id).cloned().ok_or("chain not found")?; drop(chains); - let StorageRequest { - id, - block_hash, - keys, - mut retries, - } = serde_wasm_bindgen::from_value::(request)?; - - if !chain.is_connected() { - return Err("no peers".into()); - } - let peers = chain.peers_list(); - let mut index = id % peers.len(); - let mut peer_id = peers.get(index).cloned().expect("index out of range"); + let request = serde_wasm_bindgen::from_value::(request)?; wasm_bindgen_futures::spawn_local(async move { - let config = network::codec::StorageProofRequestConfig { - block_hash: block_hash.0, - keys: keys.clone().into_iter().map(|x| x.0), - }; - loop { - let proof = chain - .network_service - .clone() - .storage_proof_request(peer_id.clone(), config.clone(), Duration::from_secs(30)) - .await; - - match proof { - Ok(proof) => { - let result = inner_decode_proof( - smoldot::trie::proof_decode::Config { - proof: proof.decode().to_vec(), - }, - None, - ); - - match result { - Ok(items) => { - let response = StorageResponse { - id, - items, - error_reason: None, - }; - callback - .storage_response(serde_wasm_bindgen::to_value(&response).unwrap()); + if !chain.is_connected() { + let response = Response::Error("no peers".to_string()); + callback + .query_response(request_id, serde_wasm_bindgen::to_value(&response).unwrap()); + break; + } + let peers = chain.peers_list(); + let index = request_id.saturating_add(retries) % peers.len(); + let peer_id = peers.get(index).cloned().expect("index out of range"); + retries = retries.saturating_sub(1); + + match &request { + Request::Storage { hash, keys } => { + let proof = chain + .network_service + .clone() + .storage_proof_request( + peer_id, + network::codec::StorageProofRequestConfig { + block_hash: hash.0, + keys: keys.clone().into_iter().map(|x| x.0), + }, + Duration::from_secs(30), + ) + .await; + + match proof { + Ok(proof) => { + let result = inner_decode_proof( + smoldot::trie::proof_decode::Config { + proof: proof.decode().to_vec(), + }, + None, + ); + + match result { + Ok(result) => { + let response = Response::Storage(result); + callback.query_response( + request_id, + serde_wasm_bindgen::to_value(&response).unwrap(), + ); + break; + } + Err(reason) => { + log::debug!( + "storage proof decode failed with error {:?}, try next peer", + reason + ); + } + } } - Err(e) => { - let response = StorageResponse { - id, - items: vec![], - error_reason: Some(e.to_string()), - }; - callback - .storage_response(serde_wasm_bindgen::to_value(&response).unwrap()); + Err(err) => { + log::debug!( + "storage proof request failed with error {:?}, try next peer", + err + ); } } - break; } - Err(err) => { - if retries == 0 { - let response = StorageResponse { - id, - items: vec![], - error_reason: Some(err.to_string()), - }; - callback.storage_response(serde_wasm_bindgen::to_value(&response).unwrap()); - break; - } - - log::debug!( - "storage proof request failed with error {:?}, try next peer", - err - ); - - // rotate peer - let peers = chain.peers_list(); - if peers.len() == 0 { - let response = StorageResponse { - id, - items: vec![], - error_reason: Some("no peers".to_string()), - }; - callback.storage_response(serde_wasm_bindgen::to_value(&response).unwrap()); - break; + Request::Block { + hash, + number, + header, + body, + } => { + let response = chain + .network_service + .clone() + .blocks_request( + peer_id, + network::codec::BlocksRequestConfig { + start: if hash.is_some() { + network::codec::BlocksRequestConfigStart::Hash( + hash.clone().unwrap().0, + ) + } else { + network::codec::BlocksRequestConfigStart::Number( + number.unwrap_or(0), + ) + }, + direction: network::codec::BlocksRequestDirection::Descending, + desired_count: NonZeroU32::new(1).unwrap(), + fields: network::codec::BlocksRequestFields { + header: *header, + body: *body, + justifications: false, + }, + }, + Duration::from_secs(30), + ) + .await; + + match response { + Ok(blocks) => { + let mut result = blocks + .into_iter() + .map(|block| Block { + hash: HashHexString(block.hash), + header: HexString(block.header.unwrap_or_default()), + body: block + .body + .unwrap_or_default() + .into_iter() + .map(|x| HexString(x)) + .collect(), + }) + .collect::>(); + if result.is_empty() { + log::debug!("blocks request returned empty result, try next peer"); + continue; + } + + let response = Response::Block(result.remove(0)); + callback.query_response( + request_id, + serde_wasm_bindgen::to_value(&response).unwrap(), + ); + break; + } + Err(err) => { + log::debug!( + "blocks request failed with error {:?}, try next peer", + err + ); + } } - index = index.saturating_add(1) % peers.len(); - peer_id = peers.get(index).cloned().expect("index out of range"); - retries = retries.saturating_sub(1); } } + + if retries == 0 { + let response = Response::Error("query out of retries".to_string()); + callback + .query_response(request_id, serde_wasm_bindgen::to_value(&response).unwrap()); + break; + } } }); @@ -434,111 +444,33 @@ pub async fn storage_request( } #[wasm_bindgen] -pub async fn blocks_request( - chain_id: usize, - request: JsValue, - callback: JsLightClientCallback, -) -> Result<(), JsValue> { - setup_console(None); - - let chains = CHAINS.lock().unwrap(); - let chain = chains.get(&chain_id).cloned().ok_or("chain not found")?; - drop(chains); - - let BlockRequest { - id, - block_hash, - block_number, - mut retries, - } = serde_wasm_bindgen::from_value::(request)?; +pub fn stream_message(connection_id: u32, stream_id: u32, data: Vec) { + crate::platform::stream_message(connection_id, stream_id, data); +} - if !chain.is_connected() { - return Err("no peers".into()); - } - let peers = chain.peers_list(); - let mut index = id % peers.len(); - let mut peer_id = peers.get(index).cloned().expect("index out of range"); +#[wasm_bindgen] +pub fn stream_writable_bytes(connection_id: u32, stream_id: u32, num_bytes: u32) { + crate::platform::stream_writable_bytes(connection_id, stream_id, num_bytes); +} - wasm_bindgen_futures::spawn_local(async move { - let config = network::codec::BlocksRequestConfig { - start: if block_hash.is_some() { - network::codec::BlocksRequestConfigStart::Hash(block_hash.clone().unwrap().0) - } else { - network::codec::BlocksRequestConfigStart::Number(block_number.unwrap_or(0)) - }, - direction: network::codec::BlocksRequestDirection::Descending, - desired_count: NonZeroU32::new(1).unwrap(), - fields: network::codec::BlocksRequestFields { - header: true, - body: true, - justifications: false, - }, - }; +#[wasm_bindgen] +pub fn connection_reset(connection_id: u32, data: Vec) { + crate::platform::connection_reset(connection_id, data); +} - loop { - let response = chain - .network_service - .clone() - .blocks_request(peer_id.clone(), config.clone(), Duration::from_secs(30)) - .await; - - match response { - Ok(blocks) => { - let blocks = blocks - .into_iter() - .map(|block| Block { - hash: HashHexString(block.hash), - header: HexString(block.header.unwrap_or_default()), - body: block - .body - .unwrap_or_default() - .into_iter() - .map(|x| HexString(x)) - .collect(), - }) - .collect::>(); - - let response = BlocksResponse { - id, - blocks, - error_reason: None, - }; - callback.block_response(serde_wasm_bindgen::to_value(&response).unwrap()); - break; - } - Err(err) => { - if retries == 0 { - let response = BlocksResponse { - id, - blocks: vec![], - error_reason: Some(err.to_string()), - }; - callback.block_response(serde_wasm_bindgen::to_value(&response).unwrap()); - break; - } +#[wasm_bindgen] +pub fn stream_reset(connection_id: u32, stream_id: u32, data: Vec) { + crate::platform::stream_reset(connection_id, stream_id, data); +} - log::debug!("blocks request failed with error {:?}, try next peer", err); - - // rotate peer - let peers = chain.peers_list(); - if peers.len() == 0 { - let response = BlocksResponse { - id, - blocks: vec![], - error_reason: Some("no peers".to_string()), - }; - callback.block_response(serde_wasm_bindgen::to_value(&response).unwrap()); - break; - } - index = index.saturating_add(1) % peers.len(); - peer_id = peers.get(index).cloned().expect("index out of range"); - retries = retries.saturating_sub(1); - } - } - } - }); +#[wasm_bindgen] +pub fn timer_finished(callback: JsLightClientCallback) { + crate::timers::timer_finished(Arc::new(callback)); +} - Ok(()) +#[wasm_bindgen] +pub fn connection_stream_opened(connection_id: u32, stream_id: u32, outbound: u32) { + crate::platform::connection_stream_opened(connection_id, stream_id, outbound); } #[wasm_bindgen] diff --git a/packages/core/src/blockchain/index.ts b/packages/core/src/blockchain/index.ts index 37796f43..d3a779fc 100644 --- a/packages/core/src/blockchain/index.ts +++ b/packages/core/src/blockchain/index.ts @@ -262,18 +262,15 @@ export class Blockchain { } if (this.lightClient) { try { - const blockData = await this.lightClient.queryBlock(number) - if (blockData && blockData.blocks.length > 0) { - const data = blockData.blocks[0] - const registry = await this.head.registry - const header = registry.createType
('Header', hexToU8a(data.header)) - const block = new Block(this, number, data.hash, undefined, { - header, - extrinsics: data.body, - }) - this.#registerBlock(block) - return block - } + const data = await this.lightClient.queryBlock(number) + const registry = await this.head.registry + const header = registry.createType
('Header', hexToU8a(data.header)) + const block = new Block(this, number, data.hash, undefined, { + header, + extrinsics: data.body, + }) + this.#registerBlock(block) + return block } catch (error) { logger.warn({ error }, `LightClient queryBlock ${number} failed`) } @@ -295,16 +292,13 @@ export class Blockchain { const registry = await this.head.registry if (this.lightClient) { try { - const blockData = await this.lightClient.queryBlock(hash) - if (blockData && blockData.blocks.length > 0) { - const data = blockData.blocks[0] - const header = registry.createType
('Header', hexToU8a(data.header)) - const block = new Block(this, header.number.toNumber(), hash, undefined, { - header, - extrinsics: data.body, - }) - return block - } + const data = await this.lightClient.queryBlock(hash) + const header = registry.createType
('Header', hexToU8a(data.header)) + const block = new Block(this, header.number.toNumber(), hash, undefined, { + header, + extrinsics: data.body, + }) + return block } catch (error) { logger.warn({ error }, `LightClient queryBlock ${hash} failed`) } diff --git a/packages/core/src/blockchain/storage-layer.ts b/packages/core/src/blockchain/storage-layer.ts index 8e7445b6..1e1f3445 100644 --- a/packages/core/src/blockchain/storage-layer.ts +++ b/packages/core/src/blockchain/storage-layer.ts @@ -62,7 +62,7 @@ export class RemoteStorageLayer implements StorageLayerProvider { if (this.#lightClient) { try { - const entries = await this.#lightClient.queryStorage(this.#at as HexString, [key as HexString]) + const entries = await this.#lightClient.queryStorage([key as HexString], this.#at as HexString) let maybeValue: HexString | undefined = undefined for (const [k, v] of entries) { this.#db?.saveStorage(this.#at as HexString, k, v) diff --git a/packages/core/src/wasm-executor/__snapshots__/executor.test.ts.snap b/packages/core/src/wasm-executor/__snapshots__/executor.test.ts.snap index 31a2be6e..6f9191bb 100644 --- a/packages/core/src/wasm-executor/__snapshots__/executor.test.ts.snap +++ b/packages/core/src/wasm-executor/__snapshots__/executor.test.ts.snap @@ -2,18 +2,12 @@ exports[`wasm > LightClient works 1`] = ` { - "blocks": [ - { - "body": [ - "0x280401000b20da607f8c01", - "0x1dda041e0091033659d46f5f48cdf0f9ac5e49fbc27657ec676dbe3b83ecc5f38fd4926a4dff23d67a39016500d671b12ef935e683569a9aa96adb613850d26d74645a6e00682614a7aa3f51e59c63b61d273dc0649a9891d78334836a6abb47c0e152d51be8008a31ef880c066175726120c767750800000000045250535290dbcae8a8b71ee45cf5c0e1bfb812ef7c14583f40dd78a0269f60c9a4f9ec215d5ede72040561757261010190ffd9ef55c7a6429a3e549652e03348054e90d587bac2b12ab795d9221e4b43b59b8889f8c3936c724e0154a76e4f2b43636dc4d3d6b54f4d8816b6d75e0f8399b71c01fecbb2d1c841cb5ab9b89e315282a71ef3d640ac12de4f266ec2810c465353a8000050005501b456f5a4efb16ffa83d007000080b23ce24557b486acbe8f76e1dc4a51faf9f1afe079534ff5b8f0d38cf1b6f6e9e856f5a4efb16ffa83d0070000b42ce8030000d4070000d6070000d8070000db070000dc070000f0070000f2070000f3070000f50700003808000099015c61975d97255ddb070000d00700005501e803000000900100009001000000000000000000018e8b1b1010c04392c2f54f101f4af58087d49b6f3b3141a401792d4ed222300600e8764817000000000000000000000000e8764817000000000000000000000099015c8c2de8299067f3070000d00700005501e80300000090010000900100000000000000000001d98c6c97df04a1cd31bd86f44de6e2ac0752153cb43ef42320a3381472121d0b00e8764817000000000000000000000000e8764817000000000000000000000099015ce1ee506d55f8d0070000f30700005501e80300000090010000900100000000000000000001873f836ad8dd3a1ce112bdb9416ee94c7d36a6be429aa6f5fe84076fca995d6b00e8764817000000000000000000000000e876481700000000000000000000009d015d01c872d0ebaf85d0070000f50700005501e80300000090010000900100000000000000000001929da5da4bbef691330783c8693787afee14c7a121b8614608098e1dcf70593900e8764817000000000000000000000000e876481700000000000000000000009d015d0452a22bee61fad0070000f20700005501e80300000090010000900100000000000000000001881c4a68554ccea849d73154df80a742bf368a91d266102e6a4736f91e5ac64400e8764817000000000000000000000000e876481700000000000000000000009d015d04d2a15ab51127e8030000d00700005501e803000000900100009001000000000000000000017c1dc6ebd2be7529c8b80bbe345f7904e676756afae7e70045fa057ac3b6098400e8764817000000000000000000000000e876481700000000000000000000009d015d057a605f506cfcd4070000d00700005501e803000000900100009001000000000000000000014117a3a323ff75a02f2a537fb64af30ec1533e52adfa25e7f793066b4d0b1b0e00e8764817000000000000000000000000e876481700000000000000000000009d015d06ad4314650419d0070000f00700005501e803000000900100009001000000000000000000013c77bdaaeff3a48e612968456031e8ec30cf19e4a79eb04a4b385774a0fa099c00e8764817000000000000000000000000e876481700000000000000000000009d015d07edc4cbc65e03d0070000d40700005501e8030000009001000090010000000000000000000195d565fb5bec539d20211ce72d66519697517d4c04cbcf598438f17354a4d5d900e8764817000000000000000000000000e876481700000000000000000000009d015d099274c2ff3639d0070000d60700005501e8030000009001000090010000000000000000000167b262980d743ac4e2cb50f0aa79778f2f909c84b7e8956d8ad3dc15523a125800e8764817000000000000000000000000e876481700000000000000000000009d015d0a87461a6f93a938080000d00700005501e80300000090010000900100000000000000000001a5b55864a6db03420b9512ae322e62a2b24e314541d0853ab59761969e962e2000e8764817000000000000000000000000e876481700000000000000000000009d015d0b652b2ae6ed1ddc070000d00700005501e80300000090010000900100000000000000000001146e54f204e45061ffac1cde16bf97dbe18681ff2dd5c23b68f5c128a79ab22b00e8764817000000000000000000000000e876481700000000000000000000009d015d0bc334ef110d8af5070000d00700005501e80300000090010000900100000000000000000001e9b5d5c413d4e8b434475465438ed59afc3c931bd7da3915f4fdf50f1eefcff600e8764817000000000000000000000000e876481700000000000000000000009d015d0c472775baca93f0070000d00700005501e8030000009001000090010000000000000000000121bcdfeb3b1009571129ab828480ff59a556cfb7196426f7e7ef10fbdf6ca46c00e8764817000000000000000000000000e876481700000000000000000000009d015d0fcde7347851edd0070000380800005501e80300000090010000900100000000000000000001081ee805c616b44e60071b808d81f0ad2bf4c36c34537c9c51c8b641c3b6c54400e8764817000000000000000000000000e876481700000000000000000000009d015e046fae65527199f2070000d00700005501e80300000090010000900100000000000000000001f420e187719ff5b6dd89709d5fe1bce35f4d8716fe62bb1b18ae8e0acfe9ee1300e8764817000000000000000000000000e876481700000000000000000000009d015e1a4e21b9b6ce11d8070000d00700005501e803000000900100009001000000000000000000015063b45fb0f97475aae3195a3a5ee0b06ce909ecc8c856d42d51ebde6fc6773900e8764817000000000000000000000000e876481700000000000000000000009d015e36c65ca123d5fbd0070000dc0700005501e8030000009001000090010000000000000000000174394a76857cb1bdfc1d612f215e5e9c36319e2a5b04adf1e892b90bfb73809b00e8764817000000000000000000000000e87648170000000000000000000000c45e414cb008e0e61e46722aa60abdd6728010fda851987882185b68b499bf2924b220e810111782fc3802ce80cf547e45289d015e4f36708366b722d0070000e80300005501e80300000090010000900100000000000000000001a522607a82c4fba67f1015b1be833a2a6a0e18f9b92efc11324527533d8e8c9300e8764817000000000000000000000000e876481700000000000000000000009d015e55c8e02d73966fd6070000d00700005501e80300000090010000900100000000000000000001aabb7b5c60751031ef98e4321ab5c4eb3f381228c2d21d373bfc2f1f6da20a2a00e8764817000000000000000000000000e87648170000000000000000000000c85e77dfdb8adb10f78f10a5df8742c5458401ed0d04cc5a6ea9743cb8341585f8fc5563e6e6c50efdb6c70573004e097eba729d015ec412f496dcf830d0070000d80700005501e8030000009001000090010000000000000000000117bd272b0646c5d00cba5dfa84f4bcf40affc6e0901c0e116b3a679aeec62bab00e8764817000000000000000000000000e876481700000000000000000000009d015ec648b30353eed1d0070000db0700005501e80300000090010000900100000000000000000001dbe04867c42f4be9c33447ce856b2079b63709c6f59e4423f2c3381e0969b20000e8764817000000000000000000000000e87648170000000000000000000000c45ee678799d3eff024253b90e84927cc6809f37720eb43a44f8657b87cf45bc389738884b3882f81c117568b8a57ed1932e9d035f04b49d95320d9021994c850f25b8e38551030000300000500000aaaa020000001000fbff0000100000000a000000403800005802000000000000000000000000500000c800001e00000000e8764817000000000000000000000000e87648170000000000000000000000e8030000009001001e000000009001000401002000004038000000000000000000001027000080b2e60e80c3c90180969800000000000000000000000000050000000a0000000a0000000100000001050000000006000000580200000300000059000000000000001e00000006000000020000001400000002000000150180000a80c84358a62885a21f919c583c76ffd7784d89441e84b8051e2a6f92d39e3e5ffc80532e85eed045d52d30950254f1a06bbc36c791ca6840be623485146ac19ad396150180001480cd86b870a09d37da7dcb1c95d7a97e2d061dd701b67f4dbc18227928dd07a3a280d58f3ee935d62db9ca52481526d98cafc3cf8b3c76d829f53f19dd18d3b5f3d899018000a480f0391087e1a965846400166e1319c552c5ed50cfd242aee90f409446ab7b94758005ab41999ad6a54ade7dfeb524e16772571b3f43bea2920b2958edabb66a04c580474034aec36a0baa6fb797679270c837ef5ed0f1c7e5673010dd16bf390469a9d48000c0787694c040f5e73d9b7addd6cb603d15d3b0021d9da3ceafbd080d00000401485e4993f016e2d2f8e5f43be7bb259486040015018001018093cd0833a646108627a0abb0c1c74d56dc93f5d37a5c12563c1f3f53661623cf805da29f971106bbbf298300105b9c497204a5bd81131c4e3d59af24182ee76af21501800102803808a1f9a1ed9df2f55624ee5ec1b4f90c5b5d6b1e7f6acdad2011d9b91126e1807f1966fef92b5aa55bc885ae3036e3845bb05551d0496fc667ace455d6ef764c1501800110808cde5ffcfc7f02559aa531e7fa1cac5cc3d4767ebc9e284a22ba5d1a385a7ed180e9865ddc9ead7f13d14df026ca4647f1a2e55d66ce23669df5ceeb554640f4f5150180011080bea88465837cd4f0f401c6bafd4ec367903d8ac6a0c7b52fc85cf30b9d354d9e800ebe650369ce0e3ce85b22816181f598cc6ecf7a3b22314357ab4181b9ccb8671501800404800cf6fb2aa078f9a747836812f8d89638a7fb3e5d0d7227756c8fc301f6d2f5cc803d38bf966c25f2f19b0b8cac0c5f4d25fb8962f20de5a8986d691ed9a2aa5a251501800404803a8ade541f10b1509992cc3fe4cfd267b61b83fdd731b55fbf10acfa7950fe4b800fe6b03639ff2739e42b263a64b97458bbb7b04a7f13a7dd10b77ba5c3d3940e1d0280046480addf2d2206b7757577996c9c37aafe43f52c3ff6860d21346850c182c03d24ef805c23ffebaea922397423cc697f0b769c384908cd8848a07673dbfe1ba706291980da6a91b27486aa7f48d015fff737e269addb169852ce12cb69a46cc350861d50809ce93dcb3a478e4f43cd270d64c97c3b3992919b5f151b4507b02394ab0be49f1501800802802097d23e0d23ace24d5f96fac6f6b62fa51b042a5af9ae4009c64df8d1eb382d80a9433febdd8594144651b2653ba5d64bd6703997436061f6ffc25fc1cf2097dc150180080480c9813c46a054d2966919ef836517e1006a7d9b2a2d424a3cd9b7cd7c8ea76e9a80d48c844a1076aaf08cffc0f6d90b01a98f61555a56e228d8b80dd081403171a5990180080c80d066a00dc79b0a8601ea466d09a4c969c246ed7d8b4f029afcd41944e33ae8b08014cb2a691c6adfa53c9a168a34fec9f34b2e52979ed9cec258a008bc43f7e1c78035310f9020f175e93e118197c2c76b1e445aad0fb35f10a81074b2ff201306cbbc8008405456f5a4efb16ffa83d0070000200000000000000000545628b8ad2696ed532c080000200000000000000000490180085080720475da1eb4b5f077fca4ad1e6add131c2ce61ab829c8ca4ef18029c79bacc048563524f87eaf5eaf000800001404e8030000685628b8ad2696ed532c080000340ce8030000d4070000f2070000990180085080f838d87f0dd4bf1f08671f82ff022192f9f34e61e0d02d036a71e137d373707380aa355c7789c126573837fde95fd60e274cddf219667c77dafaf68056bda3be668007042a0d62233e2c11a767e0231c176e6188e4e92a1a72824fee2a600cd81af31501800a0080db3bb1ed1a103404b67cbaf621d072a75c2e4032abbbd9131f808935b0d4ab7f801e33e7113f701d758488751ebeeba927f1c4868a4cd674ce8faf0c39eca4b16a9901800c20805d5b0e4db857b29690cd80b1a465f922930a2824583a17956bfa3e6bf9b472f380b6a9f43245bbc7b97e90b7a10f9f34a278b00033bfcebaa7e97870925c56055980e08202f643c479533f93eea09586502bdac989d24f518b5020bf99acd938b9f2150180100280e84015ffbd8d05efa8962f8aadae0ba4180ecca50821a4aa744245eefebb1e4580a54b0069bf2d0e88a8e00099dcc800f6e0474a904d3116953cdcdca2940d65a1a10280101780932edc507cca768cad5334f8059779676fbc4d2e2d6178c6fe8e2b0e3eefd44480f938d806a16fbb5367b9f4beca51cdb1389825570ff1201ed332005b0d452880803636a7a20fd9f62f714e4edc65a5888a6a2ed41993ca4da9f3f6b2f6fc998a4e80200d54a30fd69028b041b105165ec1d14f12fa9e2a0b94236403140e6bcdac1e8034484ffd63678ff2fc926492bea8177eb435a6f39e0256a05419a1c572c623329901801060807f443125bb5b140fa4143328f167f581dd45542cefd0fa40426893a68d6d90cc80130d8ca189ca04528b6c768c16d91748f4e8c4098239bf15ed58ceaaecc7ca64800695333da432176eb8189e38de4421658d1497ee0e9b2c4b39ec1543036c277f1501801200804f39a131d9ac6aabf819341c3cdb6865998fb630aa677145a5be9f00c4a71f4680aa21fb1594824bf9d5b9c19d8c1a2df251a0fe59a0945c434091945abc27975ca9038022d580c7f341af5a9b1a28524bb8b74512c3d9fab1b164dc92262c59af7aa77641db3d809c659382ea08c2c1a07c5dd938f72bac83b3f5fa9373875c4bda622e9962c66780de3b20bbcec97367816b77a7914e20ebb5e2324b058ab6766692410904e84dd0804de62c9f80e29431aeb3c0f713701cfa017c07f531c69b2dceb4b7ed3a7b75928077e20e3e8fd18cebde793df6204f3259fb7851cd86b5e29df9245d4bb4d06fee80ad4b1f1bff610d731ae7bab96ee9dbd3c0396f92a720cc9a814202f007dc03fc802e997541464939b382d86000353ce3a3b5e5f36e979ca2126ed611703c42e0872d0480364e8076d70d537f2d895f33fd09e2a3f5c0b198f2ddf6f600fe06060e927c9c278fa080c289a7ca876a070237a7e50624bc4180df99cac4f2864f39a538dda98dfd33ba802ad0c1b1c962b83bf4bcbc8670cefaa403273368f9e6e12a2947c5573d8a145480b9e3a62e0e36feeecf90645d515c1cb87efafae31d85025158131dd78de7a55e800cc15a9d83df18b5f4d9ef46245e9864d3a9314066ecfd4f39e576933c9feea08040d9ea03ca5269ac56b07c924191233ca2c0b3a8d238b5442983033f0e1e9e4380374b2c59ae8fe22deb920ac93589e20a1aca24bf9261afa1c553248fbb1473fd80d81c1d021087b20a853f76219fa76c718e994a7f6123975d1e24adb80316e260b1048038bd802a714a5a2ea4c5b25b3af09bce05f009316149bb7d14bd235e436d99b7bf2d48806a589224d4d534b6331ba0977040bc657cd51bf4c05a58067e3d6ac2be3da8908000912ae6a5c315f81a0e3b509a044e67419272ed02110c41a675c8c91fb5735b80993bbeeedb11f905e32baf1d7b1bd1fadda3acd0e076a83a2535b7aae39a0d3780ee24f3b2903321d446785887676af40410ce03efae00d65ad807f9eb6e44f6bc80ec32b4e081b8ead82fbe9c65cc39a91a97c009616c3f6b01437dc8c2730cbf88808d7aaccd962246b357466fc9d9c06006347edb93665759686056990b1db1c9f480d8ad9b7d7d3cecfbbb642a382f222160fe55dbf7e40e6c22de282a07dc361beb80bbbc5b0ff9f35df1a9b986a72b2442da03f93a45fef20802b94c773f9fe267923505803b7680742868383d04df90032d0698ffc3af78e94ad383d6882a863afe5eff0c6ed3678060bb8cf21be66125eed568f1fb055b56a0fb3def61f28054d42be5c7c724f7c880e3bac8ff7c3819e8351a6f38c6c32b2685a550ce4af50b631025a5d2ba7bdc5e8092d48796bc98e900a130bbffc27af63cf1c5b831c76316751a6679fbc228bb7380ab5806bff3f2e19e0ba562e0c3f27f955159f3641792d2acc8a1efec29dcd7e28036410790d4d330c0e848c3762394a60453ab1681e73d368147fbbcee24ec002e80160b83a164038f80571a0e5c69bf5b662acc68caa10cfa67cf9c50b87afab827803579a8242d681e18153339172c3ddbc356f53e1f46c60f0778959892e874d91e8017242a99d10f54e9f056e07b804693a81de82312f0735cab483b0a5a6cb443e680c6e23d4e232a91069bdb681e7e5a8309c6537da00df3ef481d6bc2859e0085c6a903803ca180670b04a57b97c2b26a9fea56e8925625427d3a3e4bf3952f149c94520d53a82280055461cd38a9cf927d9a89d17e559dcf1aa968924dc6f1941e327184c2ee9bf880fa7fd77d177520ad84723bc1d6635426a6389edf5ed9b55df52f37f644c3fc6d80d43aac2ec8df93850d8fb9e7bea864d228418eec6cb9cd00f7803b98f0b6c35e80abe87bb4984be27ca570bdf988035478b15e9427c5c42955d46e2c901675c74b805885c0f20bb2dddf674fa02d518ae49a306d54e1d4f83999f36fb100cbe0dd528041226c11ce70de8e21f814a8d8db54142882a37b18e183ea7c37f476600141181d0280410980b99d2f06330426d6fd2236079500a33e1655e08090f0962ea588cbd0eac4f7ee80413feba6193feec3f4bc72cde1c7aa72d21681191385e6f1c791614155951bf0805fdb71f3db4adaa5f1baf89d0dea15d9fdb4a1c8ac29a00ed1b8b6492bfdb35080a5544c237847f51820a9aee8563b7c83502755218c2cbf5a8e1810d2fe18de729501805139344607000020aaaa020000001000344607000020aaaa020000001000344607000020aaaa020000001000344607000020aaaa020000001000344607000020aaaa020000001000344607000020aaaa020000001000344607000020aaaa020000001000a9038058c38033fb0b82b4466907d39970dd5e098a14d0b7d085ced766dfbc88288c087bdc8c806301994fa7771d2f7c41d3c22670f0fe3909fb2a44d96c661ed3ded90e06b912804b67a06013fb7b06e5a5bcc576a575b380149037fe60434c6db85c7b4e4b536080befaa9698c856efcf4e587e452b1029cac0dc1e86582920759840687155794bb80107386445b6175c3bc908ebcd6c17d6a82dac1c1fb9bb600f6f73a8d6de4e822807feccf79d85e050f458cdf053a880d157db42d3fc1d382ea37e9e6b49c0a4365807cd2230ec8256dd2dc3189c852608334b9e531f73579143af31c2360ea9af729990180610080084fc0ec034be7d2bf630b685d86cc989a1ebff3c9b71494f747cee62f5d975080b4dd9b8d61fe6edfd32e5946762ab4b388cd03da8386ec5b2d6d85b1ae2d942980af579d5ddc5c697d42bfc014076594e66c7b324cfd3017810c4e93e4f6f0ae9ea903806938803271cfa7df9ee7c69a172b0eaafde90864e22f55abc4cf18c1005b6408e134c380378fe030a51ef64a26c89f3f170eb1c47cb837bc98a179c0ee6a646e323a8ee180f4c6355f089d28cca637f9115300b449a98d2869f0866ba4124cf2044e91ef778027f70c5fd56ffba90b436874f45eb3a6c3ea5ecacf30720af1f6753767a9580e80073b3e21ed410087262fa5f8981cde9fa62eb8b04ba9b56bf26ef03fb81a9870809fb6d7014c854f8ac4ac9eb1c7e014d4347a4781df21559f01afebdf01c92b0a807683b6b7c778d2e5822ad54a415ce6d79e931b7f76d14be1efdf961770c3ab061501808001809974c5f900dc6cf2270218c4084321b780e0f681d76432760971011e879b54a7807e88df503fa7f1c6b444ee4b48ba83f122b2d38cdc2df957ea9a3d1ff4c9e617b10480b07b8034551a19e8e89fc9727f7103cb1db3e468ea827957dc585852c39fa354bd3ffa8013a13f70fbd34f2a2e564450c978048a43317770fb0602e22763e85d4507b6f1805e7928d1195c354973747f2c22165d0df486ebdc226762eb7cc0abe8f4859d2c8059771dfc7bd636b0e438e7c44696986066e7960013c28401407ee85d28403794807216fb3af8f88cd40c131480ae0f690ddac4a73430db31a04ab1e87bef54ced780223476531d8b382ad64d32b640a401d2de1df2660df5bf4339083183a30981ef807c3e1c0f1cfa08ce5d7c2343cfd8df3da11834e0c05c7622d29032e7b56f8814805c96dd70b176e6d0cf7598fbec04e64fa3dfa06dda086083dee5c34e949bd11980fb9aa550a0af9fd0cd47a559bd1471291bf4da049240f88a964fd496c36ffefb3d0680bff280606ad500ff07801bb7b2bb86596bd12a19f3de80716c4da78763b9a4cd33cc2d802626fcd90645454a021e80719aec628af61dc941a03bba4ec0debc4de7e1d00180f5d1bed807825da595b42ef1ac5619b90267c8a050a1a31ac7da9cf6f72b2f1c80fca08370a18a8f3d85475a604ecc4d0984ee9a15fec6c48317ecc94e670757a3804c94605ed78ab21cf9de7122439b6899f5e27c241945ef0613d9bdc9173c299c801fda53452b457f7618c62f1616ed83e2c7fa7cf71f72f13ea34aa18e3d9ad78a808c1da5170fac10ce617b5d6d18c20b99991cce727b793e7e5a743dcc2c8e1e6a8000e50c9a800d3b9982be82539333cd4929760e31ae5dbba7d5a6e85bacf602b9808fac6f5b26a0a091512ba579c768e78cd75b5f91ff02dcd27a011a1b9d3f8ca080e5fb24d0ede34e1264234630abe08cc092f4b599e762eb6b274c1f222f6f2c4380ea31c1b905706f3f7a9557ab6e03ebc7fa459ed493288ab18b7248d56274d9d480b0e82fc048cc11108e543d86e9144d1f6b981230bb0c550a00df138b360b4fa3150180c00080c8f363b02c567228097f4141ff33ac30edbdc57e116dd12b31ca08dd6eedb7da806fd53b6a51e8f50f3bc69bcc298fe24cc7655c7b207660d28d6dcb26aa1b5e0e1d0280c08880cf00071125f3e7e02061c1d5ef78089721cbd3de3dd35818f197d9ce4047393480c53eb1641d7868157a4ea1f8116ed9172735bcb3a86494b8d1b9c3447cfc6fc9808c68f773671aec1db46b2c37b4c827e64aa7290d65e305cdaaa6b175596256f1808c42b10e16c2f8a5bae3bff8e01348d996b61749ced835c72181de8e3c3cf790b10480ce398045143a91a29e44ee933dd173865a746c593d4568da922617b6b25e8d49779a7e80519332e3dfefb7b4b6cb74150e52dd6cd5cacd5e33d707660394b1d3bf951a9680582326eb0161c33b55cfb3879dfbf9834b5278739ad1473b5916527e21afc16c8062baa6d4be8eace5285c5b1af2204784d5fd3990d8db14e01c7ea3d207e7af21807b7ad6eb73914fd07fc55ad89ba771070c1da532a9269475dc64f1995d4d926680b4fe59659ae0a562b4434ee170cc343daba709b4e6ad61be99fb5dd01c0f6efc808eb1dfd2bfd20b53f2a808e7d31c37875d42d6778ada487e75d274b70b8aa27e80bfede393866c60fd8d64d7518acf798aa6f77b8137333c680aed3de3c159e753808e7d06a371709c1c5f99e40a3e85a4c5a4f123b8ae16f5c87db977d17f30927e350580d2f580bcf393a2947a27925c1465aac44bb58da954b3cdebad224080d3930b6233b70880ea454c17403966165424880578a7212f7ecc8d1ec559a736d1a51395d70ee2b680b5d53fa5baccd383187aa9f249ca8d781430a943a42aaa118449a6d4fb5d034b80fa2f95cf5f443da1ce75b758629c2466fc7cb001ff3ea8ebe460fc4479e845ab80aed41dae45995900dccc86c02c2ce77a6b8581ebf9c78fe3f1385824ad87893680209819bb86075acb7874483c54a91cfd2e803479be2828b94c1d17c18a63346280f962ac5e4c61706f3e8e1aabf638e56867e25ac3b87d22801c05b043de364444802e87816751bd98244d1dc3629241dad5a5e56ad2e4ee90f3dfa65d83f70f8aa6805265811d246a9043865dd4d45ea416720c3fe719228c9eb139ecc5f80dbb906a80227968f2460b7fb0c536fcd7c0d6d682249aaa9f651c3e2a9b201a2d26e1c181b90580ddda802654f350f2901df03459efca15248804666c7ce2397bf89b8a61a52a7cb4908580ab36cb981c8e67b6cfb591f032139b2e586b1fe9905d7b324fd71df7e06a91ee802e1953c645c2b0ed27aacb7a47675de1f2fb687cdc7cc67f2b4d9fc329e898ca80f44b1c46a3ae1d8208dfc5295818fee4cd2568b2d286a8f15178448066ae60e580b9ffad52dc65652408d1652e750b2c81f47098139420a34c9a389bc14d7ccff980735ef803cdd397217afdfbbe850b3f5151d915a0e011e24fefe257f00327e91480bdfc1af50c16e33afacbb49999e93688b45101f50f8ef228be0976da0a916eb980045622a5e6f1835b920bbb8a0b3fd8c31f3516b74ddbc93628e6d8e2f830cebf80dc8ba398e0ab0313cff307a9bf29dbf30509bc737d3bcafcfb647f8a0b81290f80c02bd1fef4b8c700febf28d1739305b4a093ccf3fe0476b62c7861e865ad315180723d1fcb4b0bdb05810017af15de0ccd0c2b8f7581d07e82d49a9520ab3836784d0880ffff80d6173d5589210b3a301d4833ab0204b398370586c0c0040594cfc53bf817e0c38084b762ffc6d28885b9dab75a47bb231ee903b7ee035c1b57d7b3fb4825a9c5c48078381a8ce1fea930d598d66350dedc89a28648d42b83148e8ae5b55787c0cf5b806a4e7a611c4080d0234e45c80c6080f35576df9bfbe399a5483ea6a24c32a10980716550542c078f89b87a12ff14a7337b901fb4252843e2d104a50c0f823abe7c802e54fe01f7fbb40cbb796dee788dbc22a9d310710c8609fbf88f7fffc48a210f80ef479a8e3b93e1f101aef14f708332b5e777c7d31a2e7fd6c4fc6dc0c331718680089d8ce6b66ea01d9b02aa65b92f604518e726646998e65ed26abd0c52eff8d6803b9f59d065396d623dda6468a7dfee22b0ecec0bc4d396962021c574abf2c37480a199ba02d76a45392076693831744897e697d1c8edd32b76ac2e9d7b6494af238026c3c20f98dc3cabb3f326af3446634c5b26a1564289bcb81381fb71d18bea52803b29b5852f8737552afd5a9cbae2a8d5745ea706c8de272c5f66711f44460951806ebbdaf6616cb74a2a50e9387c5c3bb685cde4c34da1f597e3ad6606ad0112dd8070528a344a17926d3533e02b3b17d1fed5ff719c005fe77be2fb274ac719b2bb80aa9c8cdaa24f8a404c87ee7a64945cbe9a4923c340c61b641c63d6c803bf886480189db87304a3ab0f81c4eb13a2fd1c23f4d61cb492092c34fd99aceab4c8ccbd19018104090080cb211743348cd1a951cd184fc512dafe5e0b4a415af2118db6e807d851a2cdc780c2638cb1714bf35497ff309a7433d63e4dd90e364e1d1a424b2c51d93b7f4997190181094001806201a048b20d5ac9248d3d6d59705679846a8fcda38d451b781a55215a237efb802e8902539049bd590ac5802df66bde9e4bcaff54dcc7db75da266fef55d5143fbd029d007f03cfdce586301014700e2c2593d1c08078a9420cbcaebf2b45ae305b2d61fe8ef887e9b070e85a935507ec7565bba69b505f0e7b9012096b41c4eb3aaf947f6ea429080100685f0d9ef3b78afddab7f5c7142131132ad4200200000000000000585f02275f64c354954352b71eea39cfaca210020000004c5f0ec2d17a76153ff51817f12d9cfc3c7f04008017697cf415a5fc1cba73b5f96c2875f93d6c3686e9136ae8902bc2702d01dabd69049d0da05ca59913bc38a8630590f2627c07d9806f985f1aafbe565bb8f15e007a1f749d884af5c580ba76cfcd84988f7a0765e880f74409233555db1d3ee2ab18fc556397536bde697e768a8a3ef619c44d1f43a580ae08e04f1cc53d432812523051675ba60f4d07652ff9290995b445d9325c3bc380ec85a49e15777a970df3463a0d93e4e40c8c65fb8a96a2b2f726e1b7aa8bddf0802aad815050372f7147bd756b071bf40b09a185063d353a0cc5e3bf3ac96290e3809ed496cd348041136959e09bdc5d688210fe22809a8adaaf04a22cc77b18fe54803fcd727d8e7b0648c685c10e60b5b8bc2d426ed06bc3666b1256dcac2150c345802183cff80b4c7bb89689fb72b9f125176d92923b9cb4c143d6cf557341af3405c1059e710b30bd2eab0352ddcc26417aa1945fd380bce7e5aade74d2874c4e83e100906f97ffaa555efe5ed31dcf69d7f082752bbf8032a5322997010e2f65fd80990af0edff34712de10c2de5d33e41450add52432280911e79cbfc8b9cccf4f128b0dae62b732e9cdb31e960b64f52ce03a4707b3cdb80b7bc9fec0d5e9a2cba69174c8902604e8f8374f820ff1013a8002ab4adfe0bf280b9aee043e378f8313e68a6030679ccf3880fa1e7ab19b6244b5c262b7a152f004c5f03c716fb8fff3de61a883bb76adb34a20400809ee74aa0ad552ff03b9e9b742bc0f896f90d28eed97092d688392b6028203df78008a0c609ab4888f02c2545c002153297c2641c5a7b4f3d8e25c634e721f80bea80b6617c764df278313c426c46961ccde8ee7a03f9007b74bc8bc6c49d1583cf7d803125d663e61c3ccf1296de13ac02fba7a7aa89cd117b047d14386bc0f7c5e4dd80765c01d5c4c06a84f25a6b160ec74761b004793c0fbc2f848caab5551305ebb809019e7bbb460270642b5bcaf032ea04d56a00853c570f6ccad3d833b8c4110d000004003c57033ab67066cce00ee307000004003c570cbb81823589d77dd607000004007d059eb6f36e027abb2091cfb5110ab5087ff96e685f06155b3cd9a8c9e5e9a23fd5dc13a5ed2091cfea1000000000685f08316cbf8fa0da822a20ac1c55bf1be320801e000000000000505f0e7b9012096b41c4eb3aaf947f6ea429080000808127f92e325e777e31ce64afed9cbf6747185e2deba3b77c7f1fb40a689f70ab80ccd7eea271ab4ba95eed0fbf898a82fb654e2a3232ed1234bcaa4ab30e8e91ec8030c90301fa21909abd6a0ff174a6b455f3f96cfc740df229aea0976643336926800d49fef039517cc312c00412803ca1df50ac6d90c50541f649a9c85b83c0fdd880a33a93b27b85892bc18f025efca8636773ff54e7562558400e4eb7d220f8e043803c9ba584e93befb83760f055d65e0bb40817bb54391c88ca9f7b161eb7079f9680b5889eaf970461a048cfba76c1b3fb6c627d77b818e7e9d9aa935514881ba5a8685f090e2fbf2d792cb324bffa9427fe1f0e204aad1c019fb61c0171019ede3d8a54d27e44a9d5ce189618f22d3008505f0e7b9012096b41c4eb3aaf947f6ea4290809004c5f03b4123b2e186e07fb7bad5dda5f55c0040080d346b6d27624a25b7b2015ba73675ccf5f645768d0976dec8a4a6dc5a3a04c31d5019ef78c98723ddc9073523ef3beefda0c104480de2cdcc7fc6b10feb2adda900ebb9ae378fe711655f1ff2346d308d82faed8a080ae9dfbd24879b8d42c59bbf7e72885d0dd8cadf1a10dcf4eabbde8641f2430b280cf928e710e57bd0c41dd5df6657675dc5a6aba5d3ede20fe149124fd6a179159c9069f012b746dcf32e843354583c9702cc020ebbf809d43ec70c4c51aacf2423b930571c48be74da508f5fb762a3eaad5dea20afd4f80051c3c7b71cafc975cc5c2b6e46eeb7d05bd250985273206afa294347ff32b77805f3c47a03129d01a26c21447e16d61cb840a588a06ec526a33c53037539ef07f6c570f9d7e46afdef838d8070000340cd0070000db070000f8070000800a52ea7adf57aff93d43fe7bd95d3c1e71b533a01c157d8916c13bde099b2e565c5700bd9a93e85e3ce1d20700002408d6070000dc07000080173a411eefb7091e94e8519f0a50f9b8b30dbf2b4de0b8635bc61255b5fc16a6800f1ee02e9ff1d16be98c8af4be8cb4bec5b9b8a9ec379b6cad9ff91950004b1c80c97c91e2e5a027b0420eb33bc7c337502e2ba0961c4f3b67648c500e30a88a3780cea9bc128aee6d6c9113a748f7f04046fd05a553ad30e94780dcb84642cdc4e380c0d74b1d4f6d5ae4ad1685e208872663b69666eddba51d0d05f059527d9e267b80c1a41ba7cb2edae98cb3467e393c4744559c525529ed82aab276258f11798ada80007a2b526bbc5b14ab43d462fbec870cc7ddc93d78a7ae32ce9b2c1ce826aea38d089f06604cff828a6e3f579ca6c59ace013dffff806f3d3a93c0a973bfe9dd3b5374c55d54bfb53853368ccbc3cddffff0cb3fb59f80328bf5f1fcdf834f91542719684a7e941c521df826d6769e10c7a387c21b240680da2c952a23b289de0889cca9aaa5888a11bd031a978975b3722656e00b2ba02c8097974986d5a66937ce2457d5cae4f22b762ca9c5b348fd2f3f88459c415d8fa980721afc2bf227efcdd25c70681d673c50fac798a31805f56e5d9790d8215ae83380983ca1f01ba94b20756250e56543e579e921fa9410d5c79fe54a4708d2d71775800b58bdf8fa8dceb4d82a4c2e51f692921536176062343cd90b07cf58b5aea231808586a6e7dfe4ab2d0df6034ff337a8d252874e0af6be212688f602481c73ab1e8029241b5c67484fb7797a730867a2a79ee6ee4c8c45e32f74d7935a5f1de75c8380d3cec5aa5096a611b04c9f9083bbf0739b911d028fd9cdddeb66a490f7dd1d7280f7477af09e803dc67c0493a5774603c23fcfa475c860775d23ba6968002ea55a80901c4465af974bfe690004bdfa941b38cdb9437f8a21266bcb1498732488c76580fe2902e6f88bc48fd097e36b98a75eed7895555afcfff011246370112b36abbc80806f30f4e79b96408e369531c35a6cde27880d9e3c191a37585f3010f9bbff5d8094934fbb6dcc5432dbde3ebaae96ca62366caa4a71c925b647b272d12b79931d80daa3d13a763e7f11d95ca372c96bd6554077e507a19cd11f250bfcdf421cf3aa2d069f0ad157e461d71fd4c1f936839a5f1f3e6bbf801c145cc01e958be57dbedca18f3fac8a3a94edd9acd6ef14f97e98be00d7e0445857060394315fe95b98f30700002000000000000000005857002b2165312d890ff0070000200000000000000000803244367efa8dc598855a70630c52f6c7ab99e6da40fc84849a3addec3b8a6ffe8066c8c4538dee13fa77c9b9a6916ac016d25911e28b3d4478328941a5f842b19980e0bc9102a86834b5ef2029eb4c635c7d14ec8347288d2d28450594ccf555f2d5801978bc40ea365e72da0597e8d86a4d3479ca2f871bbab8030cfdbe2d3db2b6bc80236e3f56efc7c729153aab82d4d9340784523591b976670a3a82daf2ea1005398058a47f2bb2cc12ee02880a99bb9d5b0dd181550fb022df318c5b9c74d6ba703d80449416449b190c57da732355497e86010089761fade693cdd8a464f87ed4979280d4fe3e57123722d2122f49971109b3658b13ae721b425adf35e278e6ddd4bb8c804793443a067df5e20bbe8022ed078cea4d714ade8d279591ed29d8a9dc7543e2c9069f0d3719f5b0b12c7105c073c507445948ebbf805bf7a3246f314f535cd13a55fa998fac4e0bdc8c5b7e7c4a21cdde39069f1f3580051c3c7b71cafc975cc5c2b6e46eeb7d05bd250985273206afa294347ff32b778066c68d34e6e4d2f9bb541ad53f0262d6ad6a1e3140be41d4ed25a9c0657467946c570f9d7e46afdef838d8070000340cd0070000db070000f8070000800a52ea7adf57aff93d43fe7bd95d3c1e71b533a01c157d8916c13bde099b2e565c5700bd9a93e85e3ce1d20700002408d6070000dc07000080173a411eefb7091e94e8519f0a50f9b8b30dbf2b4de0b8635bc61255b5fc16a6800f1ee02e9ff1d16be98c8af4be8cb4bec5b9b8a9ec379b6cad9ff91950004b1c80c97c91e2e5a027b0420eb33bc7c337502e2ba0961c4f3b67648c500e30a88a37802d477121ddfd03fed7301b1b80e1719cb18cbe12502bd5ca9cfbf69efbec7f3780c0d74b1d4f6d5ae4ad1685e208872663b69666eddba51d0d05f059527d9e267b80c1a41ba7cb2edae98cb3467e393c4744559c525529ed82aab276258f11798ada80007a2b526bbc5b14ab43d462fbec870cc7ddc93d78a7ae32ce9b2c1ce826aea309089f0d7fefc408aac59dbfe80a72ac8e3ce5ffbf80f9f7e3fdb6d2a44499042c0c872ffd157e5f7b99ebe000c8cacd340a86246e0d803f166db79f0e184b7cd30a8e3afd5f7856601df6c23ae947cf298a9b71c650b9807e1ee2c8362cd0745aa0c58780b99fefa1024c1b472d3386a2319e71b68028a680cede792b17ae08708e2484013c18a5ad76db74c964854e03cf0e0754056f0ff38090c332a4d08d465efe0aa60e5ac5f8079cd6aacc9d0357f350d4998c01559f3f80c6c78ab7769ee8a3b518544830e25875a2ff4b29ca19344b90e09d056a5be9eb80230bd1a0336cfdc9d8885f9651418bebd978630ead20faa99a124ae9084011ac80a9e6d29bd1ee69601d4b3bc1f545c816c3d0508772b487f6b2a3d8d3e14fadd7802b8202f42f4f42a9cf05655ee07a836c4829b8d1eeb746fed8c6ad329a0dca3780562088503d26107972931cda7381468daededd36a51baac07a9b8abe0d020ddd80b918df5aca54ab15ece824ccc16ae099547b66f0833d749a33af593965eda3ee80f190a16091b9458b87d836b1185615422ecc58bd13333667fdb0dbf8f2604c5a80afedabf2a2e50b825dcc04154645e29ec25fd1176ef4a4632f56549b170914f6800b8f6c7ad5920a31d451776516aa912a9524aacc198b9dcb7d481c898be49e4b805887454a350907355a2e7a821380eafde4e9d74dcd1766524b6e4dc73cbe290eb103bf0e02656c61795f64697370617463685f71756575655f72656d61696e696e675f63617061636974790fe038470c0d000020aaaa0200000010003847070d000020aaaa0200000010007c800050344608000020aaaa020000001000344608000020aaaa0200000010007c802001344608000020aaaa020000001000344608000020aaaa02000000100080f56581bb3ed11321a39e25730eaa0e99cdb01282d383f8c8d53aab621a9381018079c36a25c7c0116fed11cce5e179b6f65f5c8824c17cb8048fd2ccb007ebc49980c1c5d7cd0aff0514d7affea47aff45e00929fdeffcd7bcd00f573c585559c1e5002ce803000000d407000000d607000000d807000000db07000000dc07000000f007000000f207000000f307000000f5070000003808000000", - ], - "hash": "0x15177d4bdc975077b85261c09503bf40932aae9d3a7a2e948870afe3432976be", - "header": "0x4e945cf0873decede74578298736ecf712d71319f10e4c7b37d7d7f503b43f5fda7a39015beb846ad4364b35d2c07b3e7c25217183431c264f973727ec9bf8f3744cda8f561dce8cf71ded3927204ced6a6e7a545d4eff86a59c94af93e7afd7fa7265eb0c066175726120c867750800000000045250535290fecbb2d1c841cb5ab9b89e315282a71ef3d640ac12de4f266ec2810c465353a866de720405617572610101466ddd00c77882cb47a572a8d8620a6bd3fb1d4b33ebf1e3e6e58d5837316540472f9f682b2562486eb1589999e9d2e0e99a2997fe32c94ea44b9a2a89992883", - }, + "body": [ + "0x280401000b20da607f8c01", + "0x1dda041e0091033659d46f5f48cdf0f9ac5e49fbc27657ec676dbe3b83ecc5f38fd4926a4dff23d67a39016500d671b12ef935e683569a9aa96adb613850d26d74645a6e00682614a7aa3f51e59c63b61d273dc0649a9891d78334836a6abb47c0e152d51be8008a31ef880c066175726120c767750800000000045250535290dbcae8a8b71ee45cf5c0e1bfb812ef7c14583f40dd78a0269f60c9a4f9ec215d5ede72040561757261010190ffd9ef55c7a6429a3e549652e03348054e90d587bac2b12ab795d9221e4b43b59b8889f8c3936c724e0154a76e4f2b43636dc4d3d6b54f4d8816b6d75e0f8399b71c01fecbb2d1c841cb5ab9b89e315282a71ef3d640ac12de4f266ec2810c465353a8000050005501b456f5a4efb16ffa83d007000080b23ce24557b486acbe8f76e1dc4a51faf9f1afe079534ff5b8f0d38cf1b6f6e9e856f5a4efb16ffa83d0070000b42ce8030000d4070000d6070000d8070000db070000dc070000f0070000f2070000f3070000f50700003808000099015c61975d97255ddb070000d00700005501e803000000900100009001000000000000000000018e8b1b1010c04392c2f54f101f4af58087d49b6f3b3141a401792d4ed222300600e8764817000000000000000000000000e8764817000000000000000000000099015c8c2de8299067f3070000d00700005501e80300000090010000900100000000000000000001d98c6c97df04a1cd31bd86f44de6e2ac0752153cb43ef42320a3381472121d0b00e8764817000000000000000000000000e8764817000000000000000000000099015ce1ee506d55f8d0070000f30700005501e80300000090010000900100000000000000000001873f836ad8dd3a1ce112bdb9416ee94c7d36a6be429aa6f5fe84076fca995d6b00e8764817000000000000000000000000e876481700000000000000000000009d015d01c872d0ebaf85d0070000f50700005501e80300000090010000900100000000000000000001929da5da4bbef691330783c8693787afee14c7a121b8614608098e1dcf70593900e8764817000000000000000000000000e876481700000000000000000000009d015d0452a22bee61fad0070000f20700005501e80300000090010000900100000000000000000001881c4a68554ccea849d73154df80a742bf368a91d266102e6a4736f91e5ac64400e8764817000000000000000000000000e876481700000000000000000000009d015d04d2a15ab51127e8030000d00700005501e803000000900100009001000000000000000000017c1dc6ebd2be7529c8b80bbe345f7904e676756afae7e70045fa057ac3b6098400e8764817000000000000000000000000e876481700000000000000000000009d015d057a605f506cfcd4070000d00700005501e803000000900100009001000000000000000000014117a3a323ff75a02f2a537fb64af30ec1533e52adfa25e7f793066b4d0b1b0e00e8764817000000000000000000000000e876481700000000000000000000009d015d06ad4314650419d0070000f00700005501e803000000900100009001000000000000000000013c77bdaaeff3a48e612968456031e8ec30cf19e4a79eb04a4b385774a0fa099c00e8764817000000000000000000000000e876481700000000000000000000009d015d07edc4cbc65e03d0070000d40700005501e8030000009001000090010000000000000000000195d565fb5bec539d20211ce72d66519697517d4c04cbcf598438f17354a4d5d900e8764817000000000000000000000000e876481700000000000000000000009d015d099274c2ff3639d0070000d60700005501e8030000009001000090010000000000000000000167b262980d743ac4e2cb50f0aa79778f2f909c84b7e8956d8ad3dc15523a125800e8764817000000000000000000000000e876481700000000000000000000009d015d0a87461a6f93a938080000d00700005501e80300000090010000900100000000000000000001a5b55864a6db03420b9512ae322e62a2b24e314541d0853ab59761969e962e2000e8764817000000000000000000000000e876481700000000000000000000009d015d0b652b2ae6ed1ddc070000d00700005501e80300000090010000900100000000000000000001146e54f204e45061ffac1cde16bf97dbe18681ff2dd5c23b68f5c128a79ab22b00e8764817000000000000000000000000e876481700000000000000000000009d015d0bc334ef110d8af5070000d00700005501e80300000090010000900100000000000000000001e9b5d5c413d4e8b434475465438ed59afc3c931bd7da3915f4fdf50f1eefcff600e8764817000000000000000000000000e876481700000000000000000000009d015d0c472775baca93f0070000d00700005501e8030000009001000090010000000000000000000121bcdfeb3b1009571129ab828480ff59a556cfb7196426f7e7ef10fbdf6ca46c00e8764817000000000000000000000000e876481700000000000000000000009d015d0fcde7347851edd0070000380800005501e80300000090010000900100000000000000000001081ee805c616b44e60071b808d81f0ad2bf4c36c34537c9c51c8b641c3b6c54400e8764817000000000000000000000000e876481700000000000000000000009d015e046fae65527199f2070000d00700005501e80300000090010000900100000000000000000001f420e187719ff5b6dd89709d5fe1bce35f4d8716fe62bb1b18ae8e0acfe9ee1300e8764817000000000000000000000000e876481700000000000000000000009d015e1a4e21b9b6ce11d8070000d00700005501e803000000900100009001000000000000000000015063b45fb0f97475aae3195a3a5ee0b06ce909ecc8c856d42d51ebde6fc6773900e8764817000000000000000000000000e876481700000000000000000000009d015e36c65ca123d5fbd0070000dc0700005501e8030000009001000090010000000000000000000174394a76857cb1bdfc1d612f215e5e9c36319e2a5b04adf1e892b90bfb73809b00e8764817000000000000000000000000e87648170000000000000000000000c45e414cb008e0e61e46722aa60abdd6728010fda851987882185b68b499bf2924b220e810111782fc3802ce80cf547e45289d015e4f36708366b722d0070000e80300005501e80300000090010000900100000000000000000001a522607a82c4fba67f1015b1be833a2a6a0e18f9b92efc11324527533d8e8c9300e8764817000000000000000000000000e876481700000000000000000000009d015e55c8e02d73966fd6070000d00700005501e80300000090010000900100000000000000000001aabb7b5c60751031ef98e4321ab5c4eb3f381228c2d21d373bfc2f1f6da20a2a00e8764817000000000000000000000000e87648170000000000000000000000c85e77dfdb8adb10f78f10a5df8742c5458401ed0d04cc5a6ea9743cb8341585f8fc5563e6e6c50efdb6c70573004e097eba729d015ec412f496dcf830d0070000d80700005501e8030000009001000090010000000000000000000117bd272b0646c5d00cba5dfa84f4bcf40affc6e0901c0e116b3a679aeec62bab00e8764817000000000000000000000000e876481700000000000000000000009d015ec648b30353eed1d0070000db0700005501e80300000090010000900100000000000000000001dbe04867c42f4be9c33447ce856b2079b63709c6f59e4423f2c3381e0969b20000e8764817000000000000000000000000e87648170000000000000000000000c45ee678799d3eff024253b90e84927cc6809f37720eb43a44f8657b87cf45bc389738884b3882f81c117568b8a57ed1932e9d035f04b49d95320d9021994c850f25b8e38551030000300000500000aaaa020000001000fbff0000100000000a000000403800005802000000000000000000000000500000c800001e00000000e8764817000000000000000000000000e87648170000000000000000000000e8030000009001001e000000009001000401002000004038000000000000000000001027000080b2e60e80c3c90180969800000000000000000000000000050000000a0000000a0000000100000001050000000006000000580200000300000059000000000000001e00000006000000020000001400000002000000150180000a80c84358a62885a21f919c583c76ffd7784d89441e84b8051e2a6f92d39e3e5ffc80532e85eed045d52d30950254f1a06bbc36c791ca6840be623485146ac19ad396150180001480cd86b870a09d37da7dcb1c95d7a97e2d061dd701b67f4dbc18227928dd07a3a280d58f3ee935d62db9ca52481526d98cafc3cf8b3c76d829f53f19dd18d3b5f3d899018000a480f0391087e1a965846400166e1319c552c5ed50cfd242aee90f409446ab7b94758005ab41999ad6a54ade7dfeb524e16772571b3f43bea2920b2958edabb66a04c580474034aec36a0baa6fb797679270c837ef5ed0f1c7e5673010dd16bf390469a9d48000c0787694c040f5e73d9b7addd6cb603d15d3b0021d9da3ceafbd080d00000401485e4993f016e2d2f8e5f43be7bb259486040015018001018093cd0833a646108627a0abb0c1c74d56dc93f5d37a5c12563c1f3f53661623cf805da29f971106bbbf298300105b9c497204a5bd81131c4e3d59af24182ee76af21501800102803808a1f9a1ed9df2f55624ee5ec1b4f90c5b5d6b1e7f6acdad2011d9b91126e1807f1966fef92b5aa55bc885ae3036e3845bb05551d0496fc667ace455d6ef764c1501800110808cde5ffcfc7f02559aa531e7fa1cac5cc3d4767ebc9e284a22ba5d1a385a7ed180e9865ddc9ead7f13d14df026ca4647f1a2e55d66ce23669df5ceeb554640f4f5150180011080bea88465837cd4f0f401c6bafd4ec367903d8ac6a0c7b52fc85cf30b9d354d9e800ebe650369ce0e3ce85b22816181f598cc6ecf7a3b22314357ab4181b9ccb8671501800404800cf6fb2aa078f9a747836812f8d89638a7fb3e5d0d7227756c8fc301f6d2f5cc803d38bf966c25f2f19b0b8cac0c5f4d25fb8962f20de5a8986d691ed9a2aa5a251501800404803a8ade541f10b1509992cc3fe4cfd267b61b83fdd731b55fbf10acfa7950fe4b800fe6b03639ff2739e42b263a64b97458bbb7b04a7f13a7dd10b77ba5c3d3940e1d0280046480addf2d2206b7757577996c9c37aafe43f52c3ff6860d21346850c182c03d24ef805c23ffebaea922397423cc697f0b769c384908cd8848a07673dbfe1ba706291980da6a91b27486aa7f48d015fff737e269addb169852ce12cb69a46cc350861d50809ce93dcb3a478e4f43cd270d64c97c3b3992919b5f151b4507b02394ab0be49f1501800802802097d23e0d23ace24d5f96fac6f6b62fa51b042a5af9ae4009c64df8d1eb382d80a9433febdd8594144651b2653ba5d64bd6703997436061f6ffc25fc1cf2097dc150180080480c9813c46a054d2966919ef836517e1006a7d9b2a2d424a3cd9b7cd7c8ea76e9a80d48c844a1076aaf08cffc0f6d90b01a98f61555a56e228d8b80dd081403171a5990180080c80d066a00dc79b0a8601ea466d09a4c969c246ed7d8b4f029afcd41944e33ae8b08014cb2a691c6adfa53c9a168a34fec9f34b2e52979ed9cec258a008bc43f7e1c78035310f9020f175e93e118197c2c76b1e445aad0fb35f10a81074b2ff201306cbbc8008405456f5a4efb16ffa83d0070000200000000000000000545628b8ad2696ed532c080000200000000000000000490180085080720475da1eb4b5f077fca4ad1e6add131c2ce61ab829c8ca4ef18029c79bacc048563524f87eaf5eaf000800001404e8030000685628b8ad2696ed532c080000340ce8030000d4070000f2070000990180085080f838d87f0dd4bf1f08671f82ff022192f9f34e61e0d02d036a71e137d373707380aa355c7789c126573837fde95fd60e274cddf219667c77dafaf68056bda3be668007042a0d62233e2c11a767e0231c176e6188e4e92a1a72824fee2a600cd81af31501800a0080db3bb1ed1a103404b67cbaf621d072a75c2e4032abbbd9131f808935b0d4ab7f801e33e7113f701d758488751ebeeba927f1c4868a4cd674ce8faf0c39eca4b16a9901800c20805d5b0e4db857b29690cd80b1a465f922930a2824583a17956bfa3e6bf9b472f380b6a9f43245bbc7b97e90b7a10f9f34a278b00033bfcebaa7e97870925c56055980e08202f643c479533f93eea09586502bdac989d24f518b5020bf99acd938b9f2150180100280e84015ffbd8d05efa8962f8aadae0ba4180ecca50821a4aa744245eefebb1e4580a54b0069bf2d0e88a8e00099dcc800f6e0474a904d3116953cdcdca2940d65a1a10280101780932edc507cca768cad5334f8059779676fbc4d2e2d6178c6fe8e2b0e3eefd44480f938d806a16fbb5367b9f4beca51cdb1389825570ff1201ed332005b0d452880803636a7a20fd9f62f714e4edc65a5888a6a2ed41993ca4da9f3f6b2f6fc998a4e80200d54a30fd69028b041b105165ec1d14f12fa9e2a0b94236403140e6bcdac1e8034484ffd63678ff2fc926492bea8177eb435a6f39e0256a05419a1c572c623329901801060807f443125bb5b140fa4143328f167f581dd45542cefd0fa40426893a68d6d90cc80130d8ca189ca04528b6c768c16d91748f4e8c4098239bf15ed58ceaaecc7ca64800695333da432176eb8189e38de4421658d1497ee0e9b2c4b39ec1543036c277f1501801200804f39a131d9ac6aabf819341c3cdb6865998fb630aa677145a5be9f00c4a71f4680aa21fb1594824bf9d5b9c19d8c1a2df251a0fe59a0945c434091945abc27975ca9038022d580c7f341af5a9b1a28524bb8b74512c3d9fab1b164dc92262c59af7aa77641db3d809c659382ea08c2c1a07c5dd938f72bac83b3f5fa9373875c4bda622e9962c66780de3b20bbcec97367816b77a7914e20ebb5e2324b058ab6766692410904e84dd0804de62c9f80e29431aeb3c0f713701cfa017c07f531c69b2dceb4b7ed3a7b75928077e20e3e8fd18cebde793df6204f3259fb7851cd86b5e29df9245d4bb4d06fee80ad4b1f1bff610d731ae7bab96ee9dbd3c0396f92a720cc9a814202f007dc03fc802e997541464939b382d86000353ce3a3b5e5f36e979ca2126ed611703c42e0872d0480364e8076d70d537f2d895f33fd09e2a3f5c0b198f2ddf6f600fe06060e927c9c278fa080c289a7ca876a070237a7e50624bc4180df99cac4f2864f39a538dda98dfd33ba802ad0c1b1c962b83bf4bcbc8670cefaa403273368f9e6e12a2947c5573d8a145480b9e3a62e0e36feeecf90645d515c1cb87efafae31d85025158131dd78de7a55e800cc15a9d83df18b5f4d9ef46245e9864d3a9314066ecfd4f39e576933c9feea08040d9ea03ca5269ac56b07c924191233ca2c0b3a8d238b5442983033f0e1e9e4380374b2c59ae8fe22deb920ac93589e20a1aca24bf9261afa1c553248fbb1473fd80d81c1d021087b20a853f76219fa76c718e994a7f6123975d1e24adb80316e260b1048038bd802a714a5a2ea4c5b25b3af09bce05f009316149bb7d14bd235e436d99b7bf2d48806a589224d4d534b6331ba0977040bc657cd51bf4c05a58067e3d6ac2be3da8908000912ae6a5c315f81a0e3b509a044e67419272ed02110c41a675c8c91fb5735b80993bbeeedb11f905e32baf1d7b1bd1fadda3acd0e076a83a2535b7aae39a0d3780ee24f3b2903321d446785887676af40410ce03efae00d65ad807f9eb6e44f6bc80ec32b4e081b8ead82fbe9c65cc39a91a97c009616c3f6b01437dc8c2730cbf88808d7aaccd962246b357466fc9d9c06006347edb93665759686056990b1db1c9f480d8ad9b7d7d3cecfbbb642a382f222160fe55dbf7e40e6c22de282a07dc361beb80bbbc5b0ff9f35df1a9b986a72b2442da03f93a45fef20802b94c773f9fe267923505803b7680742868383d04df90032d0698ffc3af78e94ad383d6882a863afe5eff0c6ed3678060bb8cf21be66125eed568f1fb055b56a0fb3def61f28054d42be5c7c724f7c880e3bac8ff7c3819e8351a6f38c6c32b2685a550ce4af50b631025a5d2ba7bdc5e8092d48796bc98e900a130bbffc27af63cf1c5b831c76316751a6679fbc228bb7380ab5806bff3f2e19e0ba562e0c3f27f955159f3641792d2acc8a1efec29dcd7e28036410790d4d330c0e848c3762394a60453ab1681e73d368147fbbcee24ec002e80160b83a164038f80571a0e5c69bf5b662acc68caa10cfa67cf9c50b87afab827803579a8242d681e18153339172c3ddbc356f53e1f46c60f0778959892e874d91e8017242a99d10f54e9f056e07b804693a81de82312f0735cab483b0a5a6cb443e680c6e23d4e232a91069bdb681e7e5a8309c6537da00df3ef481d6bc2859e0085c6a903803ca180670b04a57b97c2b26a9fea56e8925625427d3a3e4bf3952f149c94520d53a82280055461cd38a9cf927d9a89d17e559dcf1aa968924dc6f1941e327184c2ee9bf880fa7fd77d177520ad84723bc1d6635426a6389edf5ed9b55df52f37f644c3fc6d80d43aac2ec8df93850d8fb9e7bea864d228418eec6cb9cd00f7803b98f0b6c35e80abe87bb4984be27ca570bdf988035478b15e9427c5c42955d46e2c901675c74b805885c0f20bb2dddf674fa02d518ae49a306d54e1d4f83999f36fb100cbe0dd528041226c11ce70de8e21f814a8d8db54142882a37b18e183ea7c37f476600141181d0280410980b99d2f06330426d6fd2236079500a33e1655e08090f0962ea588cbd0eac4f7ee80413feba6193feec3f4bc72cde1c7aa72d21681191385e6f1c791614155951bf0805fdb71f3db4adaa5f1baf89d0dea15d9fdb4a1c8ac29a00ed1b8b6492bfdb35080a5544c237847f51820a9aee8563b7c83502755218c2cbf5a8e1810d2fe18de729501805139344607000020aaaa020000001000344607000020aaaa020000001000344607000020aaaa020000001000344607000020aaaa020000001000344607000020aaaa020000001000344607000020aaaa020000001000344607000020aaaa020000001000a9038058c38033fb0b82b4466907d39970dd5e098a14d0b7d085ced766dfbc88288c087bdc8c806301994fa7771d2f7c41d3c22670f0fe3909fb2a44d96c661ed3ded90e06b912804b67a06013fb7b06e5a5bcc576a575b380149037fe60434c6db85c7b4e4b536080befaa9698c856efcf4e587e452b1029cac0dc1e86582920759840687155794bb80107386445b6175c3bc908ebcd6c17d6a82dac1c1fb9bb600f6f73a8d6de4e822807feccf79d85e050f458cdf053a880d157db42d3fc1d382ea37e9e6b49c0a4365807cd2230ec8256dd2dc3189c852608334b9e531f73579143af31c2360ea9af729990180610080084fc0ec034be7d2bf630b685d86cc989a1ebff3c9b71494f747cee62f5d975080b4dd9b8d61fe6edfd32e5946762ab4b388cd03da8386ec5b2d6d85b1ae2d942980af579d5ddc5c697d42bfc014076594e66c7b324cfd3017810c4e93e4f6f0ae9ea903806938803271cfa7df9ee7c69a172b0eaafde90864e22f55abc4cf18c1005b6408e134c380378fe030a51ef64a26c89f3f170eb1c47cb837bc98a179c0ee6a646e323a8ee180f4c6355f089d28cca637f9115300b449a98d2869f0866ba4124cf2044e91ef778027f70c5fd56ffba90b436874f45eb3a6c3ea5ecacf30720af1f6753767a9580e80073b3e21ed410087262fa5f8981cde9fa62eb8b04ba9b56bf26ef03fb81a9870809fb6d7014c854f8ac4ac9eb1c7e014d4347a4781df21559f01afebdf01c92b0a807683b6b7c778d2e5822ad54a415ce6d79e931b7f76d14be1efdf961770c3ab061501808001809974c5f900dc6cf2270218c4084321b780e0f681d76432760971011e879b54a7807e88df503fa7f1c6b444ee4b48ba83f122b2d38cdc2df957ea9a3d1ff4c9e617b10480b07b8034551a19e8e89fc9727f7103cb1db3e468ea827957dc585852c39fa354bd3ffa8013a13f70fbd34f2a2e564450c978048a43317770fb0602e22763e85d4507b6f1805e7928d1195c354973747f2c22165d0df486ebdc226762eb7cc0abe8f4859d2c8059771dfc7bd636b0e438e7c44696986066e7960013c28401407ee85d28403794807216fb3af8f88cd40c131480ae0f690ddac4a73430db31a04ab1e87bef54ced780223476531d8b382ad64d32b640a401d2de1df2660df5bf4339083183a30981ef807c3e1c0f1cfa08ce5d7c2343cfd8df3da11834e0c05c7622d29032e7b56f8814805c96dd70b176e6d0cf7598fbec04e64fa3dfa06dda086083dee5c34e949bd11980fb9aa550a0af9fd0cd47a559bd1471291bf4da049240f88a964fd496c36ffefb3d0680bff280606ad500ff07801bb7b2bb86596bd12a19f3de80716c4da78763b9a4cd33cc2d802626fcd90645454a021e80719aec628af61dc941a03bba4ec0debc4de7e1d00180f5d1bed807825da595b42ef1ac5619b90267c8a050a1a31ac7da9cf6f72b2f1c80fca08370a18a8f3d85475a604ecc4d0984ee9a15fec6c48317ecc94e670757a3804c94605ed78ab21cf9de7122439b6899f5e27c241945ef0613d9bdc9173c299c801fda53452b457f7618c62f1616ed83e2c7fa7cf71f72f13ea34aa18e3d9ad78a808c1da5170fac10ce617b5d6d18c20b99991cce727b793e7e5a743dcc2c8e1e6a8000e50c9a800d3b9982be82539333cd4929760e31ae5dbba7d5a6e85bacf602b9808fac6f5b26a0a091512ba579c768e78cd75b5f91ff02dcd27a011a1b9d3f8ca080e5fb24d0ede34e1264234630abe08cc092f4b599e762eb6b274c1f222f6f2c4380ea31c1b905706f3f7a9557ab6e03ebc7fa459ed493288ab18b7248d56274d9d480b0e82fc048cc11108e543d86e9144d1f6b981230bb0c550a00df138b360b4fa3150180c00080c8f363b02c567228097f4141ff33ac30edbdc57e116dd12b31ca08dd6eedb7da806fd53b6a51e8f50f3bc69bcc298fe24cc7655c7b207660d28d6dcb26aa1b5e0e1d0280c08880cf00071125f3e7e02061c1d5ef78089721cbd3de3dd35818f197d9ce4047393480c53eb1641d7868157a4ea1f8116ed9172735bcb3a86494b8d1b9c3447cfc6fc9808c68f773671aec1db46b2c37b4c827e64aa7290d65e305cdaaa6b175596256f1808c42b10e16c2f8a5bae3bff8e01348d996b61749ced835c72181de8e3c3cf790b10480ce398045143a91a29e44ee933dd173865a746c593d4568da922617b6b25e8d49779a7e80519332e3dfefb7b4b6cb74150e52dd6cd5cacd5e33d707660394b1d3bf951a9680582326eb0161c33b55cfb3879dfbf9834b5278739ad1473b5916527e21afc16c8062baa6d4be8eace5285c5b1af2204784d5fd3990d8db14e01c7ea3d207e7af21807b7ad6eb73914fd07fc55ad89ba771070c1da532a9269475dc64f1995d4d926680b4fe59659ae0a562b4434ee170cc343daba709b4e6ad61be99fb5dd01c0f6efc808eb1dfd2bfd20b53f2a808e7d31c37875d42d6778ada487e75d274b70b8aa27e80bfede393866c60fd8d64d7518acf798aa6f77b8137333c680aed3de3c159e753808e7d06a371709c1c5f99e40a3e85a4c5a4f123b8ae16f5c87db977d17f30927e350580d2f580bcf393a2947a27925c1465aac44bb58da954b3cdebad224080d3930b6233b70880ea454c17403966165424880578a7212f7ecc8d1ec559a736d1a51395d70ee2b680b5d53fa5baccd383187aa9f249ca8d781430a943a42aaa118449a6d4fb5d034b80fa2f95cf5f443da1ce75b758629c2466fc7cb001ff3ea8ebe460fc4479e845ab80aed41dae45995900dccc86c02c2ce77a6b8581ebf9c78fe3f1385824ad87893680209819bb86075acb7874483c54a91cfd2e803479be2828b94c1d17c18a63346280f962ac5e4c61706f3e8e1aabf638e56867e25ac3b87d22801c05b043de364444802e87816751bd98244d1dc3629241dad5a5e56ad2e4ee90f3dfa65d83f70f8aa6805265811d246a9043865dd4d45ea416720c3fe719228c9eb139ecc5f80dbb906a80227968f2460b7fb0c536fcd7c0d6d682249aaa9f651c3e2a9b201a2d26e1c181b90580ddda802654f350f2901df03459efca15248804666c7ce2397bf89b8a61a52a7cb4908580ab36cb981c8e67b6cfb591f032139b2e586b1fe9905d7b324fd71df7e06a91ee802e1953c645c2b0ed27aacb7a47675de1f2fb687cdc7cc67f2b4d9fc329e898ca80f44b1c46a3ae1d8208dfc5295818fee4cd2568b2d286a8f15178448066ae60e580b9ffad52dc65652408d1652e750b2c81f47098139420a34c9a389bc14d7ccff980735ef803cdd397217afdfbbe850b3f5151d915a0e011e24fefe257f00327e91480bdfc1af50c16e33afacbb49999e93688b45101f50f8ef228be0976da0a916eb980045622a5e6f1835b920bbb8a0b3fd8c31f3516b74ddbc93628e6d8e2f830cebf80dc8ba398e0ab0313cff307a9bf29dbf30509bc737d3bcafcfb647f8a0b81290f80c02bd1fef4b8c700febf28d1739305b4a093ccf3fe0476b62c7861e865ad315180723d1fcb4b0bdb05810017af15de0ccd0c2b8f7581d07e82d49a9520ab3836784d0880ffff80d6173d5589210b3a301d4833ab0204b398370586c0c0040594cfc53bf817e0c38084b762ffc6d28885b9dab75a47bb231ee903b7ee035c1b57d7b3fb4825a9c5c48078381a8ce1fea930d598d66350dedc89a28648d42b83148e8ae5b55787c0cf5b806a4e7a611c4080d0234e45c80c6080f35576df9bfbe399a5483ea6a24c32a10980716550542c078f89b87a12ff14a7337b901fb4252843e2d104a50c0f823abe7c802e54fe01f7fbb40cbb796dee788dbc22a9d310710c8609fbf88f7fffc48a210f80ef479a8e3b93e1f101aef14f708332b5e777c7d31a2e7fd6c4fc6dc0c331718680089d8ce6b66ea01d9b02aa65b92f604518e726646998e65ed26abd0c52eff8d6803b9f59d065396d623dda6468a7dfee22b0ecec0bc4d396962021c574abf2c37480a199ba02d76a45392076693831744897e697d1c8edd32b76ac2e9d7b6494af238026c3c20f98dc3cabb3f326af3446634c5b26a1564289bcb81381fb71d18bea52803b29b5852f8737552afd5a9cbae2a8d5745ea706c8de272c5f66711f44460951806ebbdaf6616cb74a2a50e9387c5c3bb685cde4c34da1f597e3ad6606ad0112dd8070528a344a17926d3533e02b3b17d1fed5ff719c005fe77be2fb274ac719b2bb80aa9c8cdaa24f8a404c87ee7a64945cbe9a4923c340c61b641c63d6c803bf886480189db87304a3ab0f81c4eb13a2fd1c23f4d61cb492092c34fd99aceab4c8ccbd19018104090080cb211743348cd1a951cd184fc512dafe5e0b4a415af2118db6e807d851a2cdc780c2638cb1714bf35497ff309a7433d63e4dd90e364e1d1a424b2c51d93b7f4997190181094001806201a048b20d5ac9248d3d6d59705679846a8fcda38d451b781a55215a237efb802e8902539049bd590ac5802df66bde9e4bcaff54dcc7db75da266fef55d5143fbd029d007f03cfdce586301014700e2c2593d1c08078a9420cbcaebf2b45ae305b2d61fe8ef887e9b070e85a935507ec7565bba69b505f0e7b9012096b41c4eb3aaf947f6ea429080100685f0d9ef3b78afddab7f5c7142131132ad4200200000000000000585f02275f64c354954352b71eea39cfaca210020000004c5f0ec2d17a76153ff51817f12d9cfc3c7f04008017697cf415a5fc1cba73b5f96c2875f93d6c3686e9136ae8902bc2702d01dabd69049d0da05ca59913bc38a8630590f2627c07d9806f985f1aafbe565bb8f15e007a1f749d884af5c580ba76cfcd84988f7a0765e880f74409233555db1d3ee2ab18fc556397536bde697e768a8a3ef619c44d1f43a580ae08e04f1cc53d432812523051675ba60f4d07652ff9290995b445d9325c3bc380ec85a49e15777a970df3463a0d93e4e40c8c65fb8a96a2b2f726e1b7aa8bddf0802aad815050372f7147bd756b071bf40b09a185063d353a0cc5e3bf3ac96290e3809ed496cd348041136959e09bdc5d688210fe22809a8adaaf04a22cc77b18fe54803fcd727d8e7b0648c685c10e60b5b8bc2d426ed06bc3666b1256dcac2150c345802183cff80b4c7bb89689fb72b9f125176d92923b9cb4c143d6cf557341af3405c1059e710b30bd2eab0352ddcc26417aa1945fd380bce7e5aade74d2874c4e83e100906f97ffaa555efe5ed31dcf69d7f082752bbf8032a5322997010e2f65fd80990af0edff34712de10c2de5d33e41450add52432280911e79cbfc8b9cccf4f128b0dae62b732e9cdb31e960b64f52ce03a4707b3cdb80b7bc9fec0d5e9a2cba69174c8902604e8f8374f820ff1013a8002ab4adfe0bf280b9aee043e378f8313e68a6030679ccf3880fa1e7ab19b6244b5c262b7a152f004c5f03c716fb8fff3de61a883bb76adb34a20400809ee74aa0ad552ff03b9e9b742bc0f896f90d28eed97092d688392b6028203df78008a0c609ab4888f02c2545c002153297c2641c5a7b4f3d8e25c634e721f80bea80b6617c764df278313c426c46961ccde8ee7a03f9007b74bc8bc6c49d1583cf7d803125d663e61c3ccf1296de13ac02fba7a7aa89cd117b047d14386bc0f7c5e4dd80765c01d5c4c06a84f25a6b160ec74761b004793c0fbc2f848caab5551305ebb809019e7bbb460270642b5bcaf032ea04d56a00853c570f6ccad3d833b8c4110d000004003c57033ab67066cce00ee307000004003c570cbb81823589d77dd607000004007d059eb6f36e027abb2091cfb5110ab5087ff96e685f06155b3cd9a8c9e5e9a23fd5dc13a5ed2091cfea1000000000685f08316cbf8fa0da822a20ac1c55bf1be320801e000000000000505f0e7b9012096b41c4eb3aaf947f6ea429080000808127f92e325e777e31ce64afed9cbf6747185e2deba3b77c7f1fb40a689f70ab80ccd7eea271ab4ba95eed0fbf898a82fb654e2a3232ed1234bcaa4ab30e8e91ec8030c90301fa21909abd6a0ff174a6b455f3f96cfc740df229aea0976643336926800d49fef039517cc312c00412803ca1df50ac6d90c50541f649a9c85b83c0fdd880a33a93b27b85892bc18f025efca8636773ff54e7562558400e4eb7d220f8e043803c9ba584e93befb83760f055d65e0bb40817bb54391c88ca9f7b161eb7079f9680b5889eaf970461a048cfba76c1b3fb6c627d77b818e7e9d9aa935514881ba5a8685f090e2fbf2d792cb324bffa9427fe1f0e204aad1c019fb61c0171019ede3d8a54d27e44a9d5ce189618f22d3008505f0e7b9012096b41c4eb3aaf947f6ea4290809004c5f03b4123b2e186e07fb7bad5dda5f55c0040080d346b6d27624a25b7b2015ba73675ccf5f645768d0976dec8a4a6dc5a3a04c31d5019ef78c98723ddc9073523ef3beefda0c104480de2cdcc7fc6b10feb2adda900ebb9ae378fe711655f1ff2346d308d82faed8a080ae9dfbd24879b8d42c59bbf7e72885d0dd8cadf1a10dcf4eabbde8641f2430b280cf928e710e57bd0c41dd5df6657675dc5a6aba5d3ede20fe149124fd6a179159c9069f012b746dcf32e843354583c9702cc020ebbf809d43ec70c4c51aacf2423b930571c48be74da508f5fb762a3eaad5dea20afd4f80051c3c7b71cafc975cc5c2b6e46eeb7d05bd250985273206afa294347ff32b77805f3c47a03129d01a26c21447e16d61cb840a588a06ec526a33c53037539ef07f6c570f9d7e46afdef838d8070000340cd0070000db070000f8070000800a52ea7adf57aff93d43fe7bd95d3c1e71b533a01c157d8916c13bde099b2e565c5700bd9a93e85e3ce1d20700002408d6070000dc07000080173a411eefb7091e94e8519f0a50f9b8b30dbf2b4de0b8635bc61255b5fc16a6800f1ee02e9ff1d16be98c8af4be8cb4bec5b9b8a9ec379b6cad9ff91950004b1c80c97c91e2e5a027b0420eb33bc7c337502e2ba0961c4f3b67648c500e30a88a3780cea9bc128aee6d6c9113a748f7f04046fd05a553ad30e94780dcb84642cdc4e380c0d74b1d4f6d5ae4ad1685e208872663b69666eddba51d0d05f059527d9e267b80c1a41ba7cb2edae98cb3467e393c4744559c525529ed82aab276258f11798ada80007a2b526bbc5b14ab43d462fbec870cc7ddc93d78a7ae32ce9b2c1ce826aea38d089f06604cff828a6e3f579ca6c59ace013dffff806f3d3a93c0a973bfe9dd3b5374c55d54bfb53853368ccbc3cddffff0cb3fb59f80328bf5f1fcdf834f91542719684a7e941c521df826d6769e10c7a387c21b240680da2c952a23b289de0889cca9aaa5888a11bd031a978975b3722656e00b2ba02c8097974986d5a66937ce2457d5cae4f22b762ca9c5b348fd2f3f88459c415d8fa980721afc2bf227efcdd25c70681d673c50fac798a31805f56e5d9790d8215ae83380983ca1f01ba94b20756250e56543e579e921fa9410d5c79fe54a4708d2d71775800b58bdf8fa8dceb4d82a4c2e51f692921536176062343cd90b07cf58b5aea231808586a6e7dfe4ab2d0df6034ff337a8d252874e0af6be212688f602481c73ab1e8029241b5c67484fb7797a730867a2a79ee6ee4c8c45e32f74d7935a5f1de75c8380d3cec5aa5096a611b04c9f9083bbf0739b911d028fd9cdddeb66a490f7dd1d7280f7477af09e803dc67c0493a5774603c23fcfa475c860775d23ba6968002ea55a80901c4465af974bfe690004bdfa941b38cdb9437f8a21266bcb1498732488c76580fe2902e6f88bc48fd097e36b98a75eed7895555afcfff011246370112b36abbc80806f30f4e79b96408e369531c35a6cde27880d9e3c191a37585f3010f9bbff5d8094934fbb6dcc5432dbde3ebaae96ca62366caa4a71c925b647b272d12b79931d80daa3d13a763e7f11d95ca372c96bd6554077e507a19cd11f250bfcdf421cf3aa2d069f0ad157e461d71fd4c1f936839a5f1f3e6bbf801c145cc01e958be57dbedca18f3fac8a3a94edd9acd6ef14f97e98be00d7e0445857060394315fe95b98f30700002000000000000000005857002b2165312d890ff0070000200000000000000000803244367efa8dc598855a70630c52f6c7ab99e6da40fc84849a3addec3b8a6ffe8066c8c4538dee13fa77c9b9a6916ac016d25911e28b3d4478328941a5f842b19980e0bc9102a86834b5ef2029eb4c635c7d14ec8347288d2d28450594ccf555f2d5801978bc40ea365e72da0597e8d86a4d3479ca2f871bbab8030cfdbe2d3db2b6bc80236e3f56efc7c729153aab82d4d9340784523591b976670a3a82daf2ea1005398058a47f2bb2cc12ee02880a99bb9d5b0dd181550fb022df318c5b9c74d6ba703d80449416449b190c57da732355497e86010089761fade693cdd8a464f87ed4979280d4fe3e57123722d2122f49971109b3658b13ae721b425adf35e278e6ddd4bb8c804793443a067df5e20bbe8022ed078cea4d714ade8d279591ed29d8a9dc7543e2c9069f0d3719f5b0b12c7105c073c507445948ebbf805bf7a3246f314f535cd13a55fa998fac4e0bdc8c5b7e7c4a21cdde39069f1f3580051c3c7b71cafc975cc5c2b6e46eeb7d05bd250985273206afa294347ff32b778066c68d34e6e4d2f9bb541ad53f0262d6ad6a1e3140be41d4ed25a9c0657467946c570f9d7e46afdef838d8070000340cd0070000db070000f8070000800a52ea7adf57aff93d43fe7bd95d3c1e71b533a01c157d8916c13bde099b2e565c5700bd9a93e85e3ce1d20700002408d6070000dc07000080173a411eefb7091e94e8519f0a50f9b8b30dbf2b4de0b8635bc61255b5fc16a6800f1ee02e9ff1d16be98c8af4be8cb4bec5b9b8a9ec379b6cad9ff91950004b1c80c97c91e2e5a027b0420eb33bc7c337502e2ba0961c4f3b67648c500e30a88a37802d477121ddfd03fed7301b1b80e1719cb18cbe12502bd5ca9cfbf69efbec7f3780c0d74b1d4f6d5ae4ad1685e208872663b69666eddba51d0d05f059527d9e267b80c1a41ba7cb2edae98cb3467e393c4744559c525529ed82aab276258f11798ada80007a2b526bbc5b14ab43d462fbec870cc7ddc93d78a7ae32ce9b2c1ce826aea309089f0d7fefc408aac59dbfe80a72ac8e3ce5ffbf80f9f7e3fdb6d2a44499042c0c872ffd157e5f7b99ebe000c8cacd340a86246e0d803f166db79f0e184b7cd30a8e3afd5f7856601df6c23ae947cf298a9b71c650b9807e1ee2c8362cd0745aa0c58780b99fefa1024c1b472d3386a2319e71b68028a680cede792b17ae08708e2484013c18a5ad76db74c964854e03cf0e0754056f0ff38090c332a4d08d465efe0aa60e5ac5f8079cd6aacc9d0357f350d4998c01559f3f80c6c78ab7769ee8a3b518544830e25875a2ff4b29ca19344b90e09d056a5be9eb80230bd1a0336cfdc9d8885f9651418bebd978630ead20faa99a124ae9084011ac80a9e6d29bd1ee69601d4b3bc1f545c816c3d0508772b487f6b2a3d8d3e14fadd7802b8202f42f4f42a9cf05655ee07a836c4829b8d1eeb746fed8c6ad329a0dca3780562088503d26107972931cda7381468daededd36a51baac07a9b8abe0d020ddd80b918df5aca54ab15ece824ccc16ae099547b66f0833d749a33af593965eda3ee80f190a16091b9458b87d836b1185615422ecc58bd13333667fdb0dbf8f2604c5a80afedabf2a2e50b825dcc04154645e29ec25fd1176ef4a4632f56549b170914f6800b8f6c7ad5920a31d451776516aa912a9524aacc198b9dcb7d481c898be49e4b805887454a350907355a2e7a821380eafde4e9d74dcd1766524b6e4dc73cbe290eb103bf0e02656c61795f64697370617463685f71756575655f72656d61696e696e675f63617061636974790fe038470c0d000020aaaa0200000010003847070d000020aaaa0200000010007c800050344608000020aaaa020000001000344608000020aaaa0200000010007c802001344608000020aaaa020000001000344608000020aaaa02000000100080f56581bb3ed11321a39e25730eaa0e99cdb01282d383f8c8d53aab621a9381018079c36a25c7c0116fed11cce5e179b6f65f5c8824c17cb8048fd2ccb007ebc49980c1c5d7cd0aff0514d7affea47aff45e00929fdeffcd7bcd00f573c585559c1e5002ce803000000d407000000d607000000d807000000db07000000dc07000000f007000000f207000000f307000000f5070000003808000000", ], - "errorReason": undefined, - "id": 1, + "hash": "0x15177d4bdc975077b85261c09503bf40932aae9d3a7a2e948870afe3432976be", + "header": "0x4e945cf0873decede74578298736ecf712d71319f10e4c7b37d7d7f503b43f5fda7a39015beb846ad4364b35d2c07b3e7c25217183431c264f973727ec9bf8f3744cda8f561dce8cf71ded3927204ced6a6e7a545d4eff86a59c94af93e7afd7fa7265eb0c066175726120c867750800000000045250535290fecbb2d1c841cb5ab9b89e315282a71ef3d640ac12de4f266ec2810c465353a866de720405617572610101466ddd00c77882cb47a572a8d8620a6bd3fb1d4b33ebf1e3e6e58d5837316540472f9f682b2562486eb1589999e9d2e0e99a2997fe32c94ea44b9a2a89992883", } `; diff --git a/packages/core/src/wasm-executor/browser-wasm-executor.js b/packages/core/src/wasm-executor/browser-wasm-executor.js index fd633ef6..9f4f9928 100644 --- a/packages/core/src/wasm-executor/browser-wasm-executor.js +++ b/packages/core/src/wasm-executor/browser-wasm-executor.js @@ -54,12 +54,8 @@ const timerFinished = async (callback) => { return pkg.timer_finished(callback) } -const storageRequest = async (chainId, req, callback) => { - return pkg.storage_request(chainId, req, callback) -} - -const blocksRequest = async (chainId, req, callback) => { - return pkg.blocks_request(chainId, req, callback) +const queryChain = async (chainId, requestId, request, retries, callback) => { + return pkg.query_chain(chainId, requestId, request, retries, callback) } const getPeers = async (chainId) => { @@ -78,8 +74,7 @@ const wasmExecutor = { decodeProof, testing, startNetworkService, - storageRequest, - blocksRequest, + queryChain, getPeers, getLatestBlock, connectionStreamOpened, diff --git a/packages/core/src/wasm-executor/executor.test.ts b/packages/core/src/wasm-executor/executor.test.ts index fc87bda2..05d71d41 100644 --- a/packages/core/src/wasm-executor/executor.test.ts +++ b/packages/core/src/wasm-executor/executor.test.ts @@ -179,9 +179,10 @@ describe('wasm', () => { '0x26aa394eea5630e07c48ae0c9558cef734abf5cb34d6244378cddbf18e849d96', '0x45323df7cc47150b3930e2666b0aa31362f8058e9dc65b738fce4a22e26fa4f2', ].map((key) => - lightClient.queryStorage('0x15177d4bdc975077b85261c09503bf40932aae9d3a7a2e948870afe3432976be', [ - key as HexString, - ]), + lightClient.queryStorage( + [key as HexString], + '0x15177d4bdc975077b85261c09503bf40932aae9d3a7a2e948870afe3432976be', + ), ), ) expect(storage).toMatchSnapshot() diff --git a/packages/core/src/wasm-executor/index.ts b/packages/core/src/wasm-executor/index.ts index 20af9ff6..9ee010d0 100644 --- a/packages/core/src/wasm-executor/index.ts +++ b/packages/core/src/wasm-executor/index.ts @@ -10,12 +10,7 @@ import { defaultLogger, truncate } from '../logger.js' import { stripChildPrefix } from '../utils/index.js' import { LightClientConfig } from './light-client.js' -import type { - BlockRequest, - JsLightClientCallback, - JsRuntimeCallback, - StorageRequest, -} from '@acala-network/chopsticks-executor' +import type { JsLightClientCallback, JsRuntimeCallback, Request } from '@acala-network/chopsticks-executor' export { JsRuntimeCallback } export type RuntimeVersion = { @@ -82,8 +77,13 @@ export interface WasmExecutor { connectionReset: (connectionId: number, data: Uint8Array) => Promise streamReset: (connectionId: number, streamId: number) => Promise timerFinished: (callback: JsLightClientCallback) => Promise - storageRequest: (chainId: number, req: StorageRequest, callback: JsLightClientCallback) => Promise - blocksRequest: (chainId: number, req: BlockRequest, callback: JsLightClientCallback) => Promise + queryChain: ( + chainId: number, + requestId: number, + request: Request, + retries: number, + callback: JsLightClientCallback, + ) => Promise } const logger = defaultLogger.child({ name: 'executor' }) @@ -242,14 +242,15 @@ export const startNetworkService = async (config: LightClientConfig, callback: J return worker.remote.startNetworkService(config, Comlink.proxy(callback)) } -export const storageRequest = async (chainId: number, req: StorageRequest, callback: JsLightClientCallback) => { - const worker = await getWorker() - return worker.remote.storageRequest(chainId, req, callback) -} - -export const blocksRequest = async (chainId: number, req: BlockRequest, callback: JsLightClientCallback) => { +export const queryChain = async ( + chainId: number, + requestId: number, + request: Request, + retries: number, + callback: JsLightClientCallback, +) => { const worker = await getWorker() - return worker.remote.blocksRequest(chainId, req, callback) + return worker.remote.queryChain(chainId, requestId, request, retries, callback) } export const getPeers = async (chainId: number) => { diff --git a/packages/core/src/wasm-executor/light-client.ts b/packages/core/src/wasm-executor/light-client.ts index 257fff76..729778b2 100644 --- a/packages/core/src/wasm-executor/light-client.ts +++ b/packages/core/src/wasm-executor/light-client.ts @@ -1,5 +1,5 @@ -import { BlockRequest, BlocksResponse, StorageRequest, StorageResponse } from '@acala-network/chopsticks-executor' import { HexString } from '@polkadot/util/types' +import { Response } from '@acala-network/chopsticks-executor' import { WebSocket } from 'ws' import { stringToU8a } from '@polkadot/util' @@ -7,12 +7,11 @@ globalThis.WebSocket = typeof globalThis.WebSocket !== 'undefined' ? globalThis. import { Deferred, defer } from '../utils/index.js' import { - blocksRequest, connectionReset, getLatestBlock, getPeers, + queryChain, startNetworkService, - storageRequest, streamMessage, streamWritableBytes, timerFinished, @@ -85,8 +84,7 @@ export class LightClient { // blacklist of addresses that we have failed to connect to #blacklist: string[] = [] #connections: Record = {} - #storageResponse: Map> = new Map() - #blockResponse: Map> = new Map() + #queryResponse: Map> = new Map() #chainId = defer() @@ -159,14 +157,9 @@ export class LightClient { } } - async storageResponse(response: StorageResponse) { - this.#storageResponse.get(response.id)?.resolve(response) - this.#storageResponse.delete(response.id) - } - - async blockResponse(response: BlocksResponse) { - this.#blockResponse.get(response.id)?.resolve(response) - this.#blockResponse.delete(response.id) + async queryResponse(requestId: number, response: Response) { + this.#queryResponse.get(requestId)?.resolve(response) + this.#queryResponse.delete(requestId) } streamSend(connectionId: number, data: Uint8Array) { @@ -219,39 +212,60 @@ export class LightClient { } } - async queryStorage(blockHash: HexString, keys: HexString[]) { + async queryStorage(keys: HexString[], at: HexString) { const chainId = await this.#chainId.promise - const id = this.#requestId++ - const deferred = defer() - this.#storageResponse.set(id, deferred) - await storageRequest(chainId, { id, blockHash, keys, retries: 10 } satisfies StorageRequest, this) + const requestId = this.#requestId++ + const deferred = defer() + this.#queryResponse.set(requestId, deferred) + await queryChain( + chainId, + requestId, + { + storage: { + hash: at, + keys, + }, + }, + 10, + this, + ) const response = await deferred.promise - if (response.errorReason) { - throw new Error(response.errorReason) + if ('Error' in response) { + throw new Error(response.Error) } - return response.items + if ('Storage' in response) { + return response.Storage + } + throw new Error('Invalid response') } async queryBlock(block: HexString | number) { const chainId = await this.#chainId.promise - const id = this.#requestId++ - const deferred = defer() - this.#blockResponse.set(id, deferred) - await blocksRequest( + const requestId = this.#requestId++ + const deferred = defer() + this.#queryResponse.set(requestId, deferred) + await queryChain( chainId, + requestId, { - id, - blockNumber: typeof block === 'number' ? block : null, - blockHash: typeof block === 'string' ? block : null, - retries: 10, - } satisfies BlockRequest, + block: { + number: typeof block === 'number' ? block : null, + hash: typeof block === 'string' ? block : null, + header: true, + body: true, + }, + }, + 10, this, ) const response = await deferred.promise - if (response.errorReason) { - throw new Error(response.errorReason) + if ('Error' in response) { + throw new Error(response.Error) + } + if ('Block' in response) { + return response.Block } - return response + throw new Error('Invalid response') } connectionStreamOpen(_connectionId: number) {} diff --git a/packages/core/src/wasm-executor/node-wasm-executor.js b/packages/core/src/wasm-executor/node-wasm-executor.js index 866870b0..264acb22 100644 --- a/packages/core/src/wasm-executor/node-wasm-executor.js +++ b/packages/core/src/wasm-executor/node-wasm-executor.js @@ -57,12 +57,8 @@ const timerFinished = async (callback) => { return pkg.timer_finished(callback) } -const storageRequest = async (chainId, req, callback) => { - return pkg.storage_request(chainId, req, callback) -} - -const blocksRequest = async (chainId, req, callback) => { - return pkg.blocks_request(chainId, req, callback) +const queryChain = async (chainId, requestId, request, retries, callback) => { + return pkg.query_chain(chainId, requestId, request, retries, callback) } const getPeers = async (chainId) => { @@ -81,8 +77,7 @@ const wasmExecutor = { decodeProof, testing, startNetworkService, - storageRequest, - blocksRequest, + queryChain, getPeers, getLatestBlock, connectionStreamOpened,