From 5ba8921787609dddbabb8be3443186b65fdde190 Mon Sep 17 00:00:00 2001 From: Alexandru Gheorghe <49718502+alexggh@users.noreply.github.com> Date: Fri, 2 Feb 2024 11:02:49 +0200 Subject: [PATCH 001/133] Fixup backing group computation time (#3170) Fixes: https://github.com/paritytech/polkadot-sdk/issues/3165 --------- Signed-off-by: Alexandru Gheorghe --- .gitlab/pipeline/zombienet/polkadot.yml | 8 +++ .../parachains/src/runtime_api_impl/v7.rs | 9 +++- .../0011-async-backing-6-seconds-rate.toml | 51 +++++++++++++++++++ .../0011-async-backing-6-seconds-rate.zndsl | 20 ++++++++ 4 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.toml create mode 100644 polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.zndsl diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml index 7f5d424ec1b..54eb6db48ca 100644 --- a/.gitlab/pipeline/zombienet/polkadot.yml +++ b/.gitlab/pipeline/zombienet/polkadot.yml @@ -150,6 +150,14 @@ zombienet-polkadot-functional-0010-validator-disabling: --local-dir="${LOCAL_DIR}/functional" --test="0010-validator-disabling.zndsl" +zombienet-polkadot-functional-0011-async-backing-6-seconds-rate: + extends: + - .zombienet-polkadot-common + script: + - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh + --local-dir="${LOCAL_DIR}/functional" + --test="0011-async-backing-6-seconds-rate.zndsl" + zombienet-polkadot-smoke-0001-parachains-smoke-test: extends: - .zombienet-polkadot-common diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/v7.rs b/polkadot/runtime/parachains/src/runtime_api_impl/v7.rs index b3a060e1cb8..1bbd4dfb716 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/v7.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/v7.rs @@ -45,6 +45,8 @@ pub fn validators() -> Vec { /// Implementation for the `validator_groups` function of the runtime API. pub fn validator_groups( ) -> (Vec>, GroupRotationInfo>) { + // This formula needs to be the same as the one we use + // when populating group_responsible in `availability_cores` let now = >::block_number() + One::one(); let groups = >::validator_groups(); @@ -95,6 +97,11 @@ pub fn availability_cores() -> Vec>::next_up_on_available(CoreIndex( i as u32, @@ -106,7 +113,7 @@ pub fn availability_cores() -> Vec Date: Fri, 2 Feb 2024 13:56:32 +0100 Subject: [PATCH 002/133] [cumulus] Improved check for sane bridge fees calculations (#3175) ## TODO - [x] change constants when CI fails (should fail :) ) ## Result On the AssetHubRococo: 1701175800126 -> 1700929825257 = 0.15 % decreased. ``` # Before ( [xcm] Fix `SovereignPaidRemoteExporter` and `DepositAsset` handling (#3157)) Feb 02 12:59:05.520 ERROR bridges::estimate: `bridging::XcmBridgeHubRouterBaseFee` actual value: 1701175800126 for runtime: statemine-1006000 (statemine-0.tx14.au1) # After Feb 02 13:02:40.647 ERROR bridges::estimate: `bridging::XcmBridgeHubRouterBaseFee` actual value: 1700929825257 for runtime: statemine-1006000 (statemine-0.tx14.au1) ``` On the AssetHubWestend: 2116038876326 -> 1641718372993 = 22.4 % decreased. ``` # Before ( [xcm] Fix `SovereignPaidRemoteExporter` and `DepositAsset` handling (#3157)) Feb 02 12:56:00.880 ERROR bridges::estimate: `bridging::XcmBridgeHubRouterBaseFee` actual value: 2116038876326 for runtime: westmint-1006000 (westmint-0.tx14.au1) # After Feb 02 13:04:42.515 ERROR bridges::estimate: `bridging::XcmBridgeHubRouterBaseFee` actual value: 1641718372993 for runtime: westmint-1006000 (westmint-0.tx14.au1) ``` --- .../chain-bridge-hub-rococo/src/lib.rs | 4 +- .../chain-bridge-hub-westend/src/lib.rs | 2 +- .../assets/asset-hub-rococo/tests/tests.rs | 12 ++ .../assets/asset-hub-westend/tests/tests.rs | 32 ++++ .../bridge-hub-rococo/tests/tests.rs | 164 +++++++++--------- .../bridge-hub-westend/tests/tests.rs | 81 +++++---- .../bridge-hubs/test-utils/src/lib.rs | 57 ++++++ .../src/test_cases/from_grandpa_chain.rs | 22 +-- .../src/test_cases/from_parachain.rs | 22 +-- .../test-utils/src/test_cases/mod.rs | 8 - 10 files changed, 235 insertions(+), 169 deletions(-) diff --git a/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs b/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs index 7b109f30fe0..fa17a719b44 100644 --- a/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs +++ b/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs @@ -99,7 +99,7 @@ frame_support::parameter_types! { /// The XCM fee that is paid for executing XCM program (with `ExportMessage` instruction) at the Rococo /// BridgeHub. /// (initially was calculated by test `BridgeHubRococo::can_calculate_weight_for_paid_export_message_with_reserve_transfer` + `33%`) - pub const BridgeHubRococoBaseXcmFeeInRocs: u128 = 1_640_102_205; + pub const BridgeHubRococoBaseXcmFeeInRocs: u128 = 59_034_266; /// Transaction fee that is paid at the Rococo BridgeHub for delivering single inbound message. /// (initially was calculated by test `BridgeHubRococo::can_calculate_fee_for_complex_message_delivery_transaction` + `33%`) @@ -107,5 +107,5 @@ frame_support::parameter_types! { /// Transaction fee that is paid at the Rococo BridgeHub for delivering single outbound message confirmation. /// (initially was calculated by test `BridgeHubRococo::can_calculate_fee_for_complex_message_confirmation_transaction` + `33%`) - pub const BridgeHubRococoBaseConfirmationFeeInRocs: u128 = 4_045_736_577; + pub const BridgeHubRococoBaseConfirmationFeeInRocs: u128 = 5_380_829_647; } diff --git a/bridges/primitives/chain-bridge-hub-westend/src/lib.rs b/bridges/primitives/chain-bridge-hub-westend/src/lib.rs index 83d4d6e33a7..59754451dfd 100644 --- a/bridges/primitives/chain-bridge-hub-westend/src/lib.rs +++ b/bridges/primitives/chain-bridge-hub-westend/src/lib.rs @@ -90,7 +90,7 @@ frame_support::parameter_types! { /// The XCM fee that is paid for executing XCM program (with `ExportMessage` instruction) at the Westend /// BridgeHub. /// (initially was calculated by test `BridgeHubWestend::can_calculate_weight_for_paid_export_message_with_reserve_transfer` + `33%`) - pub const BridgeHubWestendBaseXcmFeeInWnds: u128 = 492_077_333_333; + pub const BridgeHubWestendBaseXcmFeeInWnds: u128 = 17_756_830_000; /// Transaction fee that is paid at the Westend BridgeHub for delivering single inbound message. /// (initially was calculated by test `BridgeHubWestend::can_calculate_fee_for_complex_message_delivery_transaction` + `33%`) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs index b70758eb653..b07d40e8ed3 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs @@ -1263,6 +1263,12 @@ fn change_xcm_bridge_hub_router_base_fee_by_governance_works() { 1000, Box::new(|call| RuntimeCall::System(call).encode()), || { + log::error!( + target: "bridges::estimate", + "`bridging::XcmBridgeHubRouterBaseFee` actual value: {} for runtime: {}", + bridging::XcmBridgeHubRouterBaseFee::get(), + ::Version::get(), + ); ( bridging::XcmBridgeHubRouterBaseFee::key().to_vec(), bridging::XcmBridgeHubRouterBaseFee::get(), @@ -1289,6 +1295,12 @@ fn change_xcm_bridge_hub_ethereum_base_fee_by_governance_works() { 1000, Box::new(|call| RuntimeCall::System(call).encode()), || { + log::error!( + target: "bridges::estimate", + "`bridging::BridgeHubEthereumBaseFee` actual value: {} for runtime: {}", + bridging::to_ethereum::BridgeHubEthereumBaseFee::get(), + ::Version::get(), + ); ( bridging::to_ethereum::BridgeHubEthereumBaseFee::key().to_vec(), bridging::to_ethereum::BridgeHubEthereumBaseFee::get(), diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs index 7be21c99b98..0bd17ddbd24 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs @@ -1209,6 +1209,38 @@ fn change_xcm_bridge_hub_router_byte_fee_by_governance_works() { ) } +#[test] +fn change_xcm_bridge_hub_router_base_fee_by_governance_works() { + asset_test_utils::test_cases::change_storage_constant_by_governance_works::< + Runtime, + bridging::XcmBridgeHubRouterBaseFee, + Balance, + >( + collator_session_keys(), + 1000, + Box::new(|call| RuntimeCall::System(call).encode()), + || { + log::error!( + target: "bridges::estimate", + "`bridging::XcmBridgeHubRouterBaseFee` actual value: {} for runtime: {}", + bridging::XcmBridgeHubRouterBaseFee::get(), + ::Version::get(), + ); + ( + bridging::XcmBridgeHubRouterBaseFee::key().to_vec(), + bridging::XcmBridgeHubRouterBaseFee::get(), + ) + }, + |old_value| { + if let Some(new_value) = old_value.checked_add(1) { + new_value + } else { + old_value.checked_sub(1).unwrap() + } + }, + ) +} + #[test] fn reserve_transfer_native_asset_to_non_teleport_para_works() { asset_test_utils::test_cases::reserve_transfer_native_asset_to_non_teleport_para_works::< diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs index a68d0e6a106..0d55569712d 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs @@ -34,7 +34,7 @@ use sp_core::H160; use sp_keyring::AccountKeyring::Alice; use sp_runtime::{ generic::{Era, SignedPayload}, - AccountId32, + AccountId32, Perbill, }; use testnet_parachains_constants::rococo::{ consensus::RELAY_CHAIN_SLOT_DURATION_MILLIS, fee::WeightToFee, @@ -400,53 +400,61 @@ mod bridge_hub_westend_tests { #[test] pub fn can_calculate_weight_for_paid_export_message_with_reserve_transfer() { - let estimated = bridge_hub_test_utils::test_cases::can_calculate_weight_for_paid_export_message_with_reserve_transfer::< - Runtime, - XcmConfig, - WeightToFee, - >(); - - // check if estimated value is sane - let max_expected = bp_bridge_hub_rococo::BridgeHubRococoBaseXcmFeeInRocs::get(); - assert!( - estimated <= max_expected, - "calculated: {:?}, max_expected: {:?}, please adjust `bp_bridge_hub_rococo::BridgeHubRococoBaseXcmFeeInRocs` value", - estimated, - max_expected - ); + bridge_hub_test_utils::check_sane_fees_values( + "bp_bridge_hub_rococo::BridgeHubRococoBaseXcmFeeInRocs", + bp_bridge_hub_rococo::BridgeHubRococoBaseXcmFeeInRocs::get(), + || { + bridge_hub_test_utils::test_cases::can_calculate_weight_for_paid_export_message_with_reserve_transfer::< + Runtime, + XcmConfig, + WeightToFee, + >() + }, + Perbill::from_percent(33), + Some(-33), + &format!( + "Estimate fee for `ExportMessage` for runtime: {:?}", + ::Version::get() + ), + ) } #[test] pub fn can_calculate_fee_for_complex_message_delivery_transaction() { - let estimated = from_parachain::can_calculate_fee_for_complex_message_delivery_transaction::< - RuntimeTestsAdapter, - >(collator_session_keys(), construct_and_estimate_extrinsic_fee); - - // check if estimated value is sane - let max_expected = bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs::get(); - assert!( - estimated <= max_expected, - "calculated: {:?}, max_expected: {:?}, please adjust `bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs` value", - estimated, - max_expected - ); + bridge_hub_test_utils::check_sane_fees_values( + "bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs", + bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs::get(), + || { + from_parachain::can_calculate_fee_for_complex_message_delivery_transaction::< + RuntimeTestsAdapter, + >(collator_session_keys(), construct_and_estimate_extrinsic_fee) + }, + Perbill::from_percent(33), + Some(-33), + &format!( + "Estimate fee for `single message delivery` for runtime: {:?}", + ::Version::get() + ), + ) } #[test] pub fn can_calculate_fee_for_complex_message_confirmation_transaction() { - let estimated = - from_parachain::can_calculate_fee_for_complex_message_confirmation_transaction::< - RuntimeTestsAdapter, - >(collator_session_keys(), construct_and_estimate_extrinsic_fee); - - // check if estimated value is sane - let max_expected = bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs::get(); - assert!( - estimated <= max_expected, - "calculated: {:?}, max_expected: {:?}, please adjust `bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs` value", - estimated, - max_expected - ); + bridge_hub_test_utils::check_sane_fees_values( + "bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs", + bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs::get(), + || { + from_parachain::can_calculate_fee_for_complex_message_confirmation_transaction::< + RuntimeTestsAdapter, + >(collator_session_keys(), construct_and_estimate_extrinsic_fee) + }, + Perbill::from_percent(33), + Some(-33), + &format!( + "Estimate fee for `single message confirmation` for runtime: {:?}", + ::Version::get() + ), + ) } } @@ -594,55 +602,43 @@ mod bridge_hub_bulletin_tests { ); } - #[test] - pub fn can_calculate_weight_for_paid_export_message_with_reserve_transfer() { - let estimated = bridge_hub_test_utils::test_cases::can_calculate_weight_for_paid_export_message_with_reserve_transfer::< - Runtime, - XcmConfig, - WeightToFee, - >(); - - // check if estimated value is sane - let max_expected = bp_bridge_hub_rococo::BridgeHubRococoBaseXcmFeeInRocs::get(); - assert!( - estimated <= max_expected, - "calculated: {:?}, max_expected: {:?}, please adjust `bp_bridge_hub_rococo::BridgeHubRococoBaseXcmFeeInRocs` value", - estimated, - max_expected - ); - } - #[test] pub fn can_calculate_fee_for_complex_message_delivery_transaction() { - let estimated = - from_grandpa_chain::can_calculate_fee_for_complex_message_delivery_transaction::< - RuntimeTestsAdapter, - >(collator_session_keys(), construct_and_estimate_extrinsic_fee); - - // check if estimated value is sane - let max_expected = bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs::get(); - assert!( - estimated <= max_expected, - "calculated: {:?}, max_expected: {:?}, please adjust `bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs` value", - estimated, - max_expected - ); + bridge_hub_test_utils::check_sane_fees_values( + "bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs", + bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs::get(), + || { + from_grandpa_chain::can_calculate_fee_for_complex_message_delivery_transaction::< + RuntimeTestsAdapter, + >(collator_session_keys(), construct_and_estimate_extrinsic_fee) + }, + Perbill::from_percent(33), + None, /* we don't want lowering according to the Bulletin setup, because + * `from_grandpa_chain` is cheaper then `from_parachain_chain` */ + &format!( + "Estimate fee for `single message delivery` for runtime: {:?}", + ::Version::get() + ), + ) } #[test] pub fn can_calculate_fee_for_complex_message_confirmation_transaction() { - let estimated = - from_grandpa_chain::can_calculate_fee_for_complex_message_confirmation_transaction::< - RuntimeTestsAdapter, - >(collator_session_keys(), construct_and_estimate_extrinsic_fee); - - // check if estimated value is sane - let max_expected = bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs::get(); - assert!( - estimated <= max_expected, - "calculated: {:?}, max_expected: {:?}, please adjust `bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs` value", - estimated, - max_expected - ); + bridge_hub_test_utils::check_sane_fees_values( + "bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs", + bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs::get(), + || { + from_grandpa_chain::can_calculate_fee_for_complex_message_confirmation_transaction::< + RuntimeTestsAdapter, + >(collator_session_keys(), construct_and_estimate_extrinsic_fee) + }, + Perbill::from_percent(33), + None, /* we don't want lowering according to the Bulletin setup, because + * `from_grandpa_chain` is cheaper then `from_parachain_chain` */ + &format!( + "Estimate fee for `single message confirmation` for runtime: {:?}", + ::Version::get() + ), + ) } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs index 22ade91334c..2c0da05c867 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs @@ -38,7 +38,7 @@ use sp_consensus_aura::SlotDuration; use sp_keyring::AccountKeyring::Alice; use sp_runtime::{ generic::{Era, SignedPayload}, - AccountId32, + AccountId32, Perbill, }; use testnet_parachains_constants::westend::{ consensus::RELAY_CHAIN_SLOT_DURATION_MILLIS, fee::WeightToFee, @@ -295,50 +295,59 @@ pub fn complex_relay_extrinsic_works() { #[test] pub fn can_calculate_weight_for_paid_export_message_with_reserve_transfer() { - let estimated = bridge_hub_test_utils::test_cases::can_calculate_weight_for_paid_export_message_with_reserve_transfer::< + bridge_hub_test_utils::check_sane_fees_values( + "bp_bridge_hub_westend::BridgeHubWestendBaseXcmFeeInWnds", + bp_bridge_hub_westend::BridgeHubWestendBaseXcmFeeInWnds::get(), + || { + bridge_hub_test_utils::test_cases::can_calculate_weight_for_paid_export_message_with_reserve_transfer::< Runtime, XcmConfig, WeightToFee, - >(); - - // check if estimated value is sane - let max_expected = bp_bridge_hub_westend::BridgeHubWestendBaseXcmFeeInWnds::get(); - assert!( - estimated <= max_expected, - "calculated: {:?}, max_expected: {:?}, please adjust `bp_bridge_hub_westend::BridgeHubWestendBaseXcmFeeInWnds` value", - estimated, - max_expected - ); + >() + }, + Perbill::from_percent(33), + Some(-33), + &format!( + "Estimate fee for `ExportMessage` for runtime: {:?}", + ::Version::get() + ), + ) } #[test] pub fn can_calculate_fee_for_complex_message_delivery_transaction() { - let estimated = from_parachain::can_calculate_fee_for_complex_message_delivery_transaction::< - RuntimeTestsAdapter, - >(collator_session_keys(), construct_and_estimate_extrinsic_fee); - - // check if estimated value is sane - let max_expected = bp_bridge_hub_westend::BridgeHubWestendBaseDeliveryFeeInWnds::get(); - assert!( - estimated <= max_expected, - "calculated: {:?}, max_expected: {:?}, please adjust `bp_bridge_hub_westend::BridgeHubWestendBaseDeliveryFeeInWnds` value", - estimated, - max_expected - ); + bridge_hub_test_utils::check_sane_fees_values( + "bp_bridge_hub_westend::BridgeHubWestendBaseDeliveryFeeInWnds", + bp_bridge_hub_westend::BridgeHubWestendBaseDeliveryFeeInWnds::get(), + || { + from_parachain::can_calculate_fee_for_complex_message_delivery_transaction::< + RuntimeTestsAdapter, + >(collator_session_keys(), construct_and_estimate_extrinsic_fee) + }, + Perbill::from_percent(33), + Some(-33), + &format!( + "Estimate fee for `single message delivery` for runtime: {:?}", + ::Version::get() + ), + ) } #[test] pub fn can_calculate_fee_for_complex_message_confirmation_transaction() { - let estimated = from_parachain::can_calculate_fee_for_complex_message_confirmation_transaction::< - RuntimeTestsAdapter, - >(collator_session_keys(), construct_and_estimate_extrinsic_fee); - - // check if estimated value is sane - let max_expected = bp_bridge_hub_westend::BridgeHubWestendBaseConfirmationFeeInWnds::get(); - assert!( - estimated <= max_expected, - "calculated: {:?}, max_expected: {:?}, please adjust `bp_bridge_hub_westend::BridgeHubWestendBaseConfirmationFeeInWnds` value", - estimated, - max_expected - ); + bridge_hub_test_utils::check_sane_fees_values( + "bp_bridge_hub_westend::BridgeHubWestendBaseConfirmationFeeInWnds", + bp_bridge_hub_westend::BridgeHubWestendBaseConfirmationFeeInWnds::get(), + || { + from_parachain::can_calculate_fee_for_complex_message_confirmation_transaction::< + RuntimeTestsAdapter, + >(collator_session_keys(), construct_and_estimate_extrinsic_fee) + }, + Perbill::from_percent(33), + Some(-33), + &format!( + "Estimate fee for `single message confirmation` for runtime: {:?}", + ::Version::get() + ), + ) } diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/lib.rs index 445f001f1a4..1874f38de2d 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/lib.rs @@ -21,3 +21,60 @@ pub mod test_data; pub use bp_test_utils::test_header; pub use parachains_runtimes_test_utils::*; +use sp_runtime::Perbill; + +/// A helper function for comparing the actual value of a fee constant with its estimated value. The +/// estimated value can be overestimated (`overestimate_in_percent`), and if the difference to the +/// actual value is below `margin_overestimate_diff_in_percent_for_lowering`, we should lower the +/// actual value. +pub fn check_sane_fees_values( + const_name: &str, + actual: u128, + calculate_estimated_fee: fn() -> u128, + overestimate_in_percent: Perbill, + margin_overestimate_diff_in_percent_for_lowering: Option, + label: &str, +) { + let estimated = calculate_estimated_fee(); + let estimated_plus_overestimate = estimated + (overestimate_in_percent * estimated); + let diff_to_estimated = diff_as_percent(actual, estimated); + let diff_to_estimated_plus_overestimate = diff_as_percent(actual, estimated_plus_overestimate); + + sp_tracing::try_init_simple(); + log::error!( + target: "bridges::estimate", + "{label}:\nconstant: {const_name}\n[+] actual: {actual}\n[+] estimated: {estimated} ({diff_to_estimated:.2?})\n[+] estimated(+33%): {estimated_plus_overestimate} ({diff_to_estimated_plus_overestimate:.2?})", + ); + + // check if estimated value is sane + assert!( + estimated <= actual, + "estimated: {estimated}, actual: {actual}, please adjust `{const_name}` to the value: {estimated_plus_overestimate}", + ); + assert!( + estimated_plus_overestimate <= actual, + "estimated_plus_overestimate: {estimated_plus_overestimate}, actual: {actual}, please adjust `{const_name}` to the value: {estimated_plus_overestimate}", + ); + + if let Some(margin_overestimate_diff_in_percent_for_lowering) = + margin_overestimate_diff_in_percent_for_lowering + { + assert!( + diff_to_estimated_plus_overestimate > margin_overestimate_diff_in_percent_for_lowering as f64, + "diff_to_estimated_plus_overestimate: {diff_to_estimated_plus_overestimate:.2}, overestimate_diff_in_percent_for_lowering: {margin_overestimate_diff_in_percent_for_lowering}, please adjust `{const_name}` to the value: {estimated_plus_overestimate}", + ); + } +} + +pub fn diff_as_percent(left: u128, right: u128) -> f64 { + let left = left as f64; + let right = right as f64; + ((left - right).abs() / left) * 100f64 * (if left >= right { -1 } else { 1 }) as f64 +} + +#[test] +fn diff_as_percent_works() { + assert_eq!(-20_f64, diff_as_percent(100, 80)); + assert_eq!(25_f64, diff_as_percent(80, 100)); + assert_eq!(33_f64, diff_as_percent(13351000000, 17756830000)); +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs index acf0f2c7162..8aaaa4f59d7 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs @@ -36,7 +36,7 @@ use bridge_runtime_common::{ }, messages_xcm_extension::XcmAsPlainPayload, }; -use frame_support::traits::{Get, OnFinalize, OnInitialize}; +use frame_support::traits::{OnFinalize, OnInitialize}; use frame_system::pallet_prelude::BlockNumberFor; use parachains_runtimes_test_utils::{ AccountIdOf, BasicParachainRuntime, CollatorSessionKeys, RuntimeCallOf, SlotDurations, @@ -358,16 +358,8 @@ where message_proof, helpers::relayer_id_at_bridged_chain::(), ); - let estimated_fee = compute_extrinsic_fee(batch); - log::error!( - target: "bridges::estimate", - "Estimate fee: {:?} for single message delivery for runtime: {:?}", - estimated_fee, - ::Version::get(), - ); - - estimated_fee + compute_extrinsic_fee(batch) }) } @@ -427,15 +419,7 @@ where message_delivery_proof, unrewarded_relayers, ); - let estimated_fee = compute_extrinsic_fee(batch); - - log::error!( - target: "bridges::estimate", - "Estimate fee: {:?} for single message confirmation for runtime: {:?}", - estimated_fee, - ::Version::get(), - ); - estimated_fee + compute_extrinsic_fee(batch) }) } diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs index 8a86c5bb72f..72ec0718acf 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs @@ -37,7 +37,7 @@ use bridge_runtime_common::{ }, messages_xcm_extension::XcmAsPlainPayload, }; -use frame_support::traits::{Get, OnFinalize, OnInitialize}; +use frame_support::traits::{OnFinalize, OnInitialize}; use frame_system::pallet_prelude::BlockNumberFor; use parachains_runtimes_test_utils::{ AccountIdOf, BasicParachainRuntime, CollatorSessionKeys, RuntimeCallOf, SlotDurations, @@ -446,16 +446,8 @@ where message_proof, helpers::relayer_id_at_bridged_chain::(), ); - let estimated_fee = compute_extrinsic_fee(batch); - log::error!( - target: "bridges::estimate", - "Estimate fee: {:?} for single message delivery for runtime: {:?}", - estimated_fee, - ::Version::get(), - ); - - estimated_fee + compute_extrinsic_fee(batch) }) } @@ -531,15 +523,7 @@ where message_delivery_proof, unrewarded_relayers, ); - let estimated_fee = compute_extrinsic_fee(batch); - - log::error!( - target: "bridges::estimate", - "Estimate fee: {:?} for single message confirmation for runtime: {:?}", - estimated_fee, - ::Version::get(), - ); - estimated_fee + compute_extrinsic_fee(batch) }) } diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs index 8d08915bf1f..bc1c7ec5e03 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs @@ -641,13 +641,5 @@ where let estimated_fee = WeightToFee::weight_to_fee(&weight); assert!(estimated_fee > BalanceOf::::zero()); - sp_tracing::try_init_simple(); - log::error!( - target: "bridges::estimate", - "Estimate fee: {:?} for `ExportMessage` for runtime: {:?}", - estimated_fee, - Runtime::Version::get(), - ); - estimated_fee.into() } -- GitLab From 700d5f85b768fe1867660938aa5edfcf4b26f632 Mon Sep 17 00:00:00 2001 From: Marcin S Date: Fri, 2 Feb 2024 14:58:21 +0100 Subject: [PATCH 003/133] Enable async backing on all testnet system chains (#2949) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Built on top of https://github.com/paritytech/polkadot-sdk/pull/2826/ which was a trial run. Guide: https://github.com/w3f/polkadot-wiki/blob/master/docs/maintain/maintain-guides-async-backing.md --------- Signed-off-by: georgepisaltu Co-authored-by: Branislav Kontur Co-authored-by: Dónal Murray Co-authored-by: Dmitry Sinyavin Co-authored-by: s0me0ne-unkn0wn <48632512+s0me0ne-unkn0wn@users.noreply.github.com> Co-authored-by: Svyatoslav Nikolsky Co-authored-by: Bastian Köcher Co-authored-by: georgepisaltu <52418509+georgepisaltu@users.noreply.github.com> --- Cargo.lock | 10 ++ .../chain-bridge-hub-cumulus/src/lib.rs | 26 ++++ .../chain-bridge-hub-rococo/src/lib.rs | 2 +- .../chain-bridge-hub-westend/src/lib.rs | 2 +- .../collator-selection/src/benchmarking.rs | 2 +- .../assets/asset-hub-rococo/src/lib.rs | 29 +---- .../assets/asset-hub-rococo/tests/tests.rs | 6 +- .../assets/asset-hub-westend/Cargo.toml | 2 + .../assets/asset-hub-westend/src/lib.rs | 44 ++++--- .../assets/asset-hub-westend/tests/tests.rs | 6 +- .../bridge-hubs/bridge-hub-rococo/Cargo.toml | 2 + .../src/bridge_to_westend_config.rs | 2 +- .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 44 ++++--- .../bridge-hub-rococo/tests/tests.rs | 6 +- .../bridge-hubs/bridge-hub-westend/Cargo.toml | 2 + .../src/bridge_to_rococo_config.rs | 2 +- .../bridge-hubs/bridge-hub-westend/src/lib.rs | 40 ++++-- .../bridge-hub-westend/tests/tests.rs | 6 +- .../collectives-westend/Cargo.toml | 2 + .../collectives-westend/src/lib.rs | 41 ++++-- .../parachains/runtimes/constants/Cargo.toml | 4 + .../runtimes/constants/src/rococo.rs | 30 ++++- .../runtimes/constants/src/westend.rs | 30 ++++- .../contracts/contracts-rococo/Cargo.toml | 2 + .../contracts/contracts-rococo/src/lib.rs | 39 ++++-- .../coretime/coretime-rococo/Cargo.toml | 2 + .../coretime/coretime-rococo/src/lib.rs | 40 ++++-- .../coretime/coretime-westend/Cargo.toml | 2 + .../coretime/coretime-westend/src/lib.rs | 40 ++++-- .../glutton/glutton-westend/src/lib.rs | 24 +--- .../runtimes/people/people-rococo/Cargo.toml | 2 + .../runtimes/people/people-rococo/src/lib.rs | 38 ++++-- .../runtimes/people/people-westend/Cargo.toml | 2 + .../runtimes/people/people-westend/src/lib.rs | 40 ++++-- cumulus/polkadot-parachain/src/command.rs | 23 +++- cumulus/polkadot-parachain/src/service.rs | 123 ++++++++++++++++-- .../smoke/0004-configure-broker.js | 6 +- ...backing-on-all-testnet-system-chains.prdoc | 23 ++++ 38 files changed, 536 insertions(+), 210 deletions(-) create mode 100644 prdoc/pr_2949-async-backing-on-all-testnet-system-chains.prdoc diff --git a/Cargo.lock b/Cargo.lock index 36e94ebae4e..952eaee4997 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -978,6 +978,7 @@ dependencies = [ "cumulus-pallet-session-benchmarking", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", + "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-utility", "frame-benchmarking", @@ -1993,6 +1994,7 @@ dependencies = [ "cumulus-pallet-session-benchmarking", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", + "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-utility", "frame-benchmarking", @@ -2161,6 +2163,7 @@ dependencies = [ "cumulus-pallet-session-benchmarking", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", + "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-utility", "frame-benchmarking", @@ -2718,6 +2721,7 @@ dependencies = [ "cumulus-pallet-session-benchmarking", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", + "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-utility", "frame-benchmarking", @@ -2972,6 +2976,7 @@ dependencies = [ "cumulus-pallet-session-benchmarking", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", + "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-utility", "frame-benchmarking", @@ -3065,6 +3070,7 @@ dependencies = [ "cumulus-pallet-session-benchmarking", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", + "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-utility", "frame-benchmarking", @@ -3129,6 +3135,7 @@ dependencies = [ "cumulus-pallet-session-benchmarking", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", + "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-utility", "frame-benchmarking", @@ -11660,6 +11667,7 @@ dependencies = [ "cumulus-pallet-session-benchmarking", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", + "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-utility", "enumflags2", @@ -11758,6 +11766,7 @@ dependencies = [ "cumulus-pallet-session-benchmarking", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", + "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-utility", "enumflags2", @@ -19998,6 +20007,7 @@ dependencies = [ name = "testnet-parachains-constants" version = "1.0.0" dependencies = [ + "cumulus-primitives-core", "frame-support", "polkadot-core-primitives", "rococo-runtime-constants", diff --git a/bridges/primitives/chain-bridge-hub-cumulus/src/lib.rs b/bridges/primitives/chain-bridge-hub-cumulus/src/lib.rs index 285f0020481..c49aa4b8563 100644 --- a/bridges/primitives/chain-bridge-hub-cumulus/src/lib.rs +++ b/bridges/primitives/chain-bridge-hub-cumulus/src/lib.rs @@ -57,6 +57,12 @@ const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts(constants::WEIGHT_REF_TI .saturating_div(2) .set_proof_size(polkadot_primitives::MAX_POV_SIZE as u64); +/// We allow for 2 seconds of compute with a 6 second average block. +const MAXIMUM_BLOCK_WEIGHT_FOR_ASYNC_BACKING: Weight = Weight::from_parts( + constants::WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), + polkadot_primitives::MAX_POV_SIZE as u64, +); + /// All cumulus bridge hubs assume that about 5 percent of the block weight is consumed by /// `on_initialize` handlers. This is used to limit the maximal weight of a single extrinsic. /// @@ -96,6 +102,26 @@ parameter_types! { }) .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) .build_or_panic(); + + /// Weight limit of the Cumulus-based bridge hub blocks when async backing is enabled. + pub BlockWeightsForAsyncBacking: limits::BlockWeights = limits::BlockWeights::builder() + .base_block(BlockExecutionWeight::get()) + .for_class(DispatchClass::all(), |weights| { + weights.base_extrinsic = ExtrinsicBaseWeight::get(); + }) + .for_class(DispatchClass::Normal, |weights| { + weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT_FOR_ASYNC_BACKING); + }) + .for_class(DispatchClass::Operational, |weights| { + weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT_FOR_ASYNC_BACKING); + // Operational transactions have an extra reserved space, so that they + // are included even if block reached `MAXIMUM_BLOCK_WEIGHT_FOR_ASYNC_BACKING`. + weights.reserved = Some( + MAXIMUM_BLOCK_WEIGHT_FOR_ASYNC_BACKING - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT_FOR_ASYNC_BACKING, + ); + }) + .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) + .build_or_panic(); } /// Public key of the chain account that may be used to verify signatures. diff --git a/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs b/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs index fa17a719b44..c4e697fbe95 100644 --- a/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs +++ b/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs @@ -50,7 +50,7 @@ impl Chain for BridgeHubRococo { } fn max_extrinsic_weight() -> Weight { - BlockWeights::get() + BlockWeightsForAsyncBacking::get() .get(DispatchClass::Normal) .max_extrinsic .unwrap_or(Weight::MAX) diff --git a/bridges/primitives/chain-bridge-hub-westend/src/lib.rs b/bridges/primitives/chain-bridge-hub-westend/src/lib.rs index 59754451dfd..4af895cc6d3 100644 --- a/bridges/primitives/chain-bridge-hub-westend/src/lib.rs +++ b/bridges/primitives/chain-bridge-hub-westend/src/lib.rs @@ -49,7 +49,7 @@ impl Chain for BridgeHubWestend { } fn max_extrinsic_weight() -> Weight { - BlockWeights::get() + BlockWeightsForAsyncBacking::get() .get(DispatchClass::Normal) .max_extrinsic .unwrap_or(Weight::MAX) diff --git a/cumulus/pallets/collator-selection/src/benchmarking.rs b/cumulus/pallets/collator-selection/src/benchmarking.rs index fa95303495d..2c40f4dd0ea 100644 --- a/cumulus/pallets/collator-selection/src/benchmarking.rs +++ b/cumulus/pallets/collator-selection/src/benchmarking.rs @@ -394,7 +394,7 @@ mod benchmarks { register_validators::(c); register_candidates::(c); - let new_block: BlockNumberFor = 1800u32.into(); + let new_block: BlockNumberFor = T::KickThreshold::get(); let zero_block: BlockNumberFor = 0u32.into(); let candidates: Vec = >::get() .iter() diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index d67595b10b4..2c111c0efac 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -62,7 +62,7 @@ use frame_support::{ ConstU128, ConstU32, ConstU64, ConstU8, EitherOfDiverse, Equals, InstanceFilter, TransformOrigin, }, - weights::{constants::WEIGHT_REF_TIME_PER_SECOND, ConstantMultiplier, Weight}, + weights::{ConstantMultiplier, Weight}, BoundedVec, PalletId, }; use frame_system::{ @@ -75,11 +75,10 @@ use parachains_common::{ impls::DealWithFees, message_queue::{NarrowOriginToSibling, ParaIdToSibling}, AccountId, AssetIdForTrustBackedAssets, AuraId, Balance, BlockNumber, CollectionId, Hash, - Header, ItemId, Nonce, Signature, AVERAGE_ON_INITIALIZE_RATIO, DAYS, HOURS, - NORMAL_DISPATCH_RATIO, + Header, ItemId, Nonce, Signature, AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, }; use sp_runtime::{Perbill, RuntimeDebug}; -use testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee}; +use testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee, time::*}; use xcm_config::{ ForeignAssetsConvertedConcreteId, ForeignCreatorsSovereignAccountOf, GovernanceLocation, PoolAssetsConvertedConcreteId, TokenLocation, TokenLocationV3, @@ -140,28 +139,6 @@ pub fn native_version() -> NativeVersion { NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } } -/// We allow for 2 seconds of compute with a 6 second average block. -const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts( - WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), - cumulus_primitives_core::relay_chain::MAX_POV_SIZE as u64, -); - -/// Maximum number of blocks simultaneously accepted by the Runtime, not yet included -/// into the relay chain. -const UNINCLUDED_SEGMENT_CAPACITY: u32 = 3; -/// How many parachain blocks are processed by the relay chain per parent. Limits the -/// number of blocks authored per slot. -const BLOCK_PROCESSING_VELOCITY: u32 = 1; - -/// This determines the average expected block time that we are targeting. -/// Blocks will be produced at a minimum duration defined by `SLOT_DURATION`. -/// `SLOT_DURATION` is picked up by `pallet_timestamp` which is in turn picked -/// up by `pallet_aura` to implement `fn slot_duration()`. -/// -/// Change this to adjust the block time. -pub const MILLISECS_PER_BLOCK: u64 = 6000; -pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; - parameter_types! { pub const Version: RuntimeVersion = VERSION; pub RuntimeBlockLength: BlockLength = diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs index b07d40e8ed3..cff2290222c 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs @@ -28,7 +28,7 @@ use asset_hub_rococo_runtime::{ AllPalletsWithoutSystem, AssetConversion, AssetDeposit, Assets, Balances, CollatorSelection, ExistentialDeposit, ForeignAssets, ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, Runtime, RuntimeCall, RuntimeEvent, SessionKeys, - ToWestendXcmRouterInstance, TrustBackedAssetsInstance, XcmpQueue, SLOT_DURATION, + ToWestendXcmRouterInstance, TrustBackedAssetsInstance, XcmpQueue, }; use asset_test_utils::{ test_cases_over_bridge::TestBridgingConfig, CollatorSessionKey, CollatorSessionKeys, @@ -50,9 +50,7 @@ use parachains_common::{AccountId, AssetIdForTrustBackedAssets, AuraId, Balance} use sp_consensus_aura::SlotDuration; use sp_runtime::traits::MaybeEquivalence; use std::convert::Into; -use testnet_parachains_constants::rococo::{ - consensus::RELAY_CHAIN_SLOT_DURATION_MILLIS, currency::UNITS, fee::WeightToFee, -}; +use testnet_parachains_constants::rococo::{consensus::*, currency::UNITS, fee::WeightToFee}; use xcm::latest::prelude::{Assets as XcmAssets, *}; use xcm_builder::V4V3LocationConverter; use xcm_executor::traits::{JustTry, WeightTrader}; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml index 62db0b454db..415c8c1cb06 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml @@ -73,6 +73,7 @@ cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system cumulus-pallet-session-benchmarking = { path = "../../../../pallets/session-benchmarking", default-features = false } cumulus-pallet-xcm = { path = "../../../../pallets/xcm", default-features = false } cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false, features = ["bridging"] } +cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } @@ -175,6 +176,7 @@ std = [ "cumulus-pallet-session-benchmarking/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", + "cumulus-primitives-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 6424cec675d..aa319d9f892 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -32,7 +32,7 @@ use assets_common::{ AssetIdForTrustBackedAssetsConvert, }; use codec::{Decode, Encode, MaxEncodedLen}; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; use frame_support::{ construct_runtime, derive_impl, @@ -58,7 +58,7 @@ use pallet_xcm::EnsureXcm; use parachains_common::{ impls::DealWithFees, message_queue::*, AccountId, AssetIdForTrustBackedAssets, AuraId, Balance, BlockNumber, CollectionId, Hash, Header, ItemId, Nonce, Signature, AVERAGE_ON_INITIALIZE_RATIO, - DAYS, HOURS, MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO, SLOT_DURATION, + NORMAL_DISPATCH_RATIO, }; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; @@ -72,7 +72,7 @@ use sp_std::prelude::*; #[cfg(feature = "std")] use sp_version::NativeVersion; use sp_version::RuntimeVersion; -use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee}; +use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee, time::*}; use xcm_config::{ ForeignAssetsConvertedConcreteId, PoolAssetsConvertedConcreteId, TrustBackedAssetsConvertedConcreteId, TrustBackedAssetsPalletLocationV3, WestendLocation, @@ -171,6 +171,9 @@ impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; type OnTimestampSet = Aura; + #[cfg(feature = "experimental")] + type MinimumPeriod = ConstU64<0>; + #[cfg(not(feature = "experimental"))] type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; type WeightInfo = weights::pallet_timestamp::WeightInfo; } @@ -604,15 +607,17 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type OutboundXcmpMessageSource = XcmpQueue; type XcmpMessageHandler = XcmpQueue; type ReservedXcmpWeight = ReservedXcmpWeight; - type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; - type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, - >; + type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; } +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + impl parachain_info::Config for Runtime {} parameter_types! { @@ -697,9 +702,9 @@ impl pallet_aura::Config for Runtime { type AuthorityId = AuraId; type DisabledValidators = (); type MaxAuthorities = ConstU32<100_000>; - type AllowMultipleBlocksPerSlot = ConstBool; + type AllowMultipleBlocksPerSlot = ConstBool; #[cfg(feature = "experimental")] - type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; + type SlotDuration = ConstU64; } parameter_types! { @@ -980,7 +985,7 @@ impl frame_support::traits::OnRuntimeUpgrade for DeleteUndecodableStorage { }, Err(e) => { log::error!("Failed to destroy undecodable NFT item: {:?}", e); - return ::DbWeight::get().reads_writes(0, writes) + return ::DbWeight::get().reads_writes(0, writes); }, } @@ -992,7 +997,7 @@ impl frame_support::traits::OnRuntimeUpgrade for DeleteUndecodableStorage { }, Err(e) => { log::error!("Failed to destroy undecodable NFT item: {:?}", e); - return ::DbWeight::get().reads_writes(0, writes) + return ::DbWeight::get().reads_writes(0, writes); }, } @@ -1090,7 +1095,7 @@ mod benches { impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) } fn authorities() -> Vec { @@ -1098,6 +1103,15 @@ impl_runtime_apis! { } } + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) + } + } + impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { VERSION diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs index 0bd17ddbd24..5f6e4c007cd 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs @@ -47,13 +47,11 @@ use frame_support::{ }, weights::{Weight, WeightToFee as WeightToFeeT}, }; -use parachains_common::{AccountId, AssetIdForTrustBackedAssets, AuraId, Balance, SLOT_DURATION}; +use parachains_common::{AccountId, AssetIdForTrustBackedAssets, AuraId, Balance}; use sp_consensus_aura::SlotDuration; use sp_runtime::traits::MaybeEquivalence; use std::convert::Into; -use testnet_parachains_constants::westend::{ - consensus::RELAY_CHAIN_SLOT_DURATION_MILLIS, currency::UNITS, fee::WeightToFee, -}; +use testnet_parachains_constants::westend::{consensus::*, currency::UNITS, fee::WeightToFee}; use xcm::latest::prelude::{Assets as XcmAssets, *}; use xcm_builder::V4V3LocationConverter; use xcm_executor::traits::{JustTry, WeightTrader}; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index 3e2dadd11ca..0b750d30a5f 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -76,6 +76,7 @@ cumulus-pallet-xcm = { path = "../../../../pallets/xcm", default-features = fals cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false, features = [ "bridging", ] } +cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } @@ -153,6 +154,7 @@ std = [ "cumulus-pallet-session-benchmarking/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", + "cumulus-primitives-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", "frame-benchmarking/std", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs index 3a626e2cba7..5d55d7afbac 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs @@ -299,7 +299,7 @@ mod tests { >(AssertCompleteBridgeConstants { this_chain_constants: AssertChainConstants { block_length: bp_bridge_hub_rococo::BlockLength::get(), - block_weights: bp_bridge_hub_rococo::BlockWeights::get(), + block_weights: bp_bridge_hub_rococo::BlockWeightsForAsyncBacking::get(), }, messages_pallet_constants: AssertBridgeMessagesPalletConstants { max_unrewarded_relayers_in_bridged_confirmation_tx: diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 94d5772861c..2be5652c434 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -35,7 +35,7 @@ pub mod bridge_to_westend_config; mod weights; pub mod xcm_config; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use snowbridge_beacon_primitives::{Fork, ForkVersions}; use snowbridge_core::{ gwei, meth, outbound::Message, AgentId, AllowSiblingsOnly, PricingParameters, Rewards, @@ -69,6 +69,9 @@ use frame_system::{ limits::{BlockLength, BlockWeights}, EnsureRoot, }; +use testnet_parachains_constants::rococo::{ + consensus::*, currency::*, fee::WeightToFee, snowbridge::INBOUND_QUEUE_PALLET_INDEX, time::*, +}; #[cfg(feature = "runtime-benchmarks")] use bp_runtime::Chain; @@ -94,10 +97,7 @@ use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; use parachains_common::{ impls::DealWithFees, AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature, - AVERAGE_ON_INITIALIZE_RATIO, HOURS, MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO, SLOT_DURATION, -}; -use testnet_parachains_constants::rococo::{ - consensus::*, currency::*, fee::WeightToFee, snowbridge::INBOUND_QUEUE_PALLET_INDEX, + AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, }; use polkadot_runtime_common::prod_or_fast; @@ -279,6 +279,9 @@ impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; type OnTimestampSet = Aura; + #[cfg(feature = "experimental")] + type MinimumPeriod = ConstU64<0>; + #[cfg(not(feature = "experimental"))] type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; type WeightInfo = weights::pallet_timestamp::WeightInfo; } @@ -340,15 +343,17 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type ReservedDmpWeight = ReservedDmpWeight; type XcmpMessageHandler = XcmpQueue; type ReservedXcmpWeight = ReservedXcmpWeight; - type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; - type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, - >; + type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; } +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + impl parachain_info::Config for Runtime {} parameter_types! { @@ -437,9 +442,9 @@ impl pallet_aura::Config for Runtime { type AuthorityId = AuraId; type DisabledValidators = (); type MaxAuthorities = ConstU32<100_000>; - type AllowMultipleBlocksPerSlot = ConstBool; + type AllowMultipleBlocksPerSlot = ConstBool; #[cfg(feature = "experimental")] - type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; + type SlotDuration = ConstU64; } parameter_types! { @@ -783,7 +788,7 @@ mod benches { impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) } fn authorities() -> Vec { @@ -791,6 +796,15 @@ impl_runtime_apis! { } } + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) + } + } + impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { VERSION diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs index 0d55569712d..f11954cf165 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs @@ -27,7 +27,7 @@ use bridge_hub_rococo_runtime::{ use bridge_hub_test_utils::SlotDurations; use codec::{Decode, Encode}; use frame_support::{dispatch::GetDispatchInfo, parameter_types, traits::ConstU8}; -use parachains_common::{AccountId, AuraId, Balance, SLOT_DURATION}; +use parachains_common::{AccountId, AuraId, Balance}; use snowbridge_core::ChannelId; use sp_consensus_aura::SlotDuration; use sp_core::H160; @@ -36,9 +36,7 @@ use sp_runtime::{ generic::{Era, SignedPayload}, AccountId32, Perbill, }; -use testnet_parachains_constants::rococo::{ - consensus::RELAY_CHAIN_SLOT_DURATION_MILLIS, fee::WeightToFee, -}; +use testnet_parachains_constants::rococo::{consensus::*, fee::WeightToFee}; use xcm::latest::prelude::*; parameter_types! { diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml index e6d3db1ba49..4cbfde91d00 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -68,6 +68,7 @@ cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system cumulus-pallet-session-benchmarking = { path = "../../../../pallets/session-benchmarking", default-features = false } cumulus-pallet-xcm = { path = "../../../../pallets/xcm", default-features = false } cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false, features = ["bridging"] } +cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } @@ -125,6 +126,7 @@ std = [ "cumulus-pallet-session-benchmarking/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", + "cumulus-primitives-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", "frame-benchmarking/std", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs index 4567494f825..bce722aa5f8 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs @@ -334,7 +334,7 @@ mod tests { >(AssertCompleteBridgeConstants { this_chain_constants: AssertChainConstants { block_length: bp_bridge_hub_westend::BlockLength::get(), - block_weights: bp_bridge_hub_westend::BlockWeights::get(), + block_weights: bp_bridge_hub_westend::BlockWeightsForAsyncBacking::get(), }, messages_pallet_constants: AssertBridgeMessagesPalletConstants { max_unrewarded_relayers_in_bridged_confirmation_tx: diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 5637c3552f5..edfb08f163b 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -32,7 +32,7 @@ pub mod bridge_to_rococo_config; mod weights; pub mod xcm_config; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::ParaId; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; @@ -83,9 +83,9 @@ use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; use parachains_common::{ impls::DealWithFees, AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature, - AVERAGE_ON_INITIALIZE_RATIO, HOURS, MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO, SLOT_DURATION, + AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, }; -use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee}; +use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee, time::*}; /// The address format for describing accounts. pub type Address = MultiAddress; @@ -251,6 +251,9 @@ impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; type OnTimestampSet = Aura; + #[cfg(feature = "experimental")] + type MinimumPeriod = ConstU64<0>; + #[cfg(not(feature = "experimental"))] type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; type WeightInfo = weights::pallet_timestamp::WeightInfo; } @@ -312,15 +315,17 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type ReservedDmpWeight = ReservedDmpWeight; type XcmpMessageHandler = XcmpQueue; type ReservedXcmpWeight = ReservedXcmpWeight; - type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; - type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, - >; + type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; } +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + impl parachain_info::Config for Runtime {} parameter_types! { @@ -401,9 +406,9 @@ impl pallet_aura::Config for Runtime { type AuthorityId = AuraId; type DisabledValidators = (); type MaxAuthorities = ConstU32<100_000>; - type AllowMultipleBlocksPerSlot = ConstBool; + type AllowMultipleBlocksPerSlot = ConstBool; #[cfg(feature = "experimental")] - type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; + type SlotDuration = ConstU64; } parameter_types! { @@ -534,7 +539,7 @@ mod benches { impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) } fn authorities() -> Vec { @@ -542,6 +547,15 @@ impl_runtime_apis! { } } + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) + } + } + impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { VERSION diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs index 2c0da05c867..149a3bbeb75 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs @@ -33,16 +33,14 @@ use bridge_to_rococo_config::{ }; use codec::{Decode, Encode}; use frame_support::{dispatch::GetDispatchInfo, parameter_types, traits::ConstU8}; -use parachains_common::{AccountId, AuraId, Balance, SLOT_DURATION}; +use parachains_common::{AccountId, AuraId, Balance}; use sp_consensus_aura::SlotDuration; use sp_keyring::AccountKeyring::Alice; use sp_runtime::{ generic::{Era, SignedPayload}, AccountId32, Perbill, }; -use testnet_parachains_constants::westend::{ - consensus::RELAY_CHAIN_SLOT_DURATION_MILLIS, fee::WeightToFee, -}; +use testnet_parachains_constants::westend::{consensus::*, fee::WeightToFee}; use xcm::latest::prelude::*; // Para id of sibling chain used in tests. diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml index 21f4d2a1970..c16cee4fedd 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml @@ -74,6 +74,7 @@ cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system cumulus-pallet-session-benchmarking = { path = "../../../../pallets/session-benchmarking", default-features = false } cumulus-pallet-xcm = { path = "../../../../pallets/xcm", default-features = false } cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false } +cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } @@ -169,6 +170,7 @@ std = [ "cumulus-pallet-session-benchmarking/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", + "cumulus-primitives-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs index 3f488d04ef0..6016955221c 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs @@ -44,7 +44,7 @@ pub mod xcm_config; pub mod fellowship; pub use ambassador::pallet_ambassador_origins; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use fellowship::{pallet_fellowship_origins, Fellows}; use impls::{AllianceProposalProvider, EqualOrGreatestRootCmp}; use sp_api::impl_runtime_apis; @@ -84,12 +84,11 @@ use parachains_common::{ impls::{DealWithFees, ToParentTreasury}, message_queue::*, AccountId, AuraId, Balance, BlockNumber, Hash, Header, Nonce, Signature, - AVERAGE_ON_INITIALIZE_RATIO, DAYS, HOURS, MAXIMUM_BLOCK_WEIGHT, MINUTES, NORMAL_DISPATCH_RATIO, - SLOT_DURATION, + AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, }; use sp_runtime::RuntimeDebug; use testnet_parachains_constants::westend::{ - account::*, consensus::*, currency::*, fee::WeightToFee, + account::*, consensus::*, currency::*, fee::WeightToFee, time::*, }; use xcm_config::{ GovernanceLocation, LocationToAccountId, TreasurerBodyId, XcmOriginToTransactDispatchOrigin, @@ -185,6 +184,9 @@ impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; type OnTimestampSet = Aura; + #[cfg(feature = "experimental")] + type MinimumPeriod = ConstU64<0>; + #[cfg(not(feature = "experimental"))] type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; type WeightInfo = weights::pallet_timestamp::WeightInfo; } @@ -387,15 +389,17 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type OutboundXcmpMessageSource = XcmpQueue; type XcmpMessageHandler = XcmpQueue; type ReservedXcmpWeight = ReservedXcmpWeight; - type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; - type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, - >; + type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; } +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + impl parachain_info::Config for Runtime {} parameter_types! { @@ -478,9 +482,9 @@ impl pallet_aura::Config for Runtime { type AuthorityId = AuraId; type DisabledValidators = (); type MaxAuthorities = ConstU32<100_000>; - type AllowMultipleBlocksPerSlot = ConstBool; + type AllowMultipleBlocksPerSlot = ConstBool; #[cfg(feature = "experimental")] - type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; + type SlotDuration = ConstU64; } parameter_types! { @@ -771,7 +775,7 @@ mod benches { impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) } fn authorities() -> Vec { @@ -779,6 +783,15 @@ impl_runtime_apis! { } } + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) + } + } + impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { VERSION diff --git a/cumulus/parachains/runtimes/constants/Cargo.toml b/cumulus/parachains/runtimes/constants/Cargo.toml index b9aea7ec48d..561e8276b5f 100644 --- a/cumulus/parachains/runtimes/constants/Cargo.toml +++ b/cumulus/parachains/runtimes/constants/Cargo.toml @@ -25,9 +25,13 @@ rococo-runtime-constants = { path = "../../../../polkadot/runtime/rococo/constan westend-runtime-constants = { path = "../../../../polkadot/runtime/westend/constants", default-features = false, optional = true } xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false } +# Cumulus +cumulus-primitives-core = { path = "../../../primitives/core", default-features = false } + [features] default = ["std"] std = [ + "cumulus-primitives-core/std", "frame-support/std", "polkadot-core-primitives/std", "rococo-runtime-constants?/std", diff --git a/cumulus/parachains/runtimes/constants/src/rococo.rs b/cumulus/parachains/runtimes/constants/src/rococo.rs index 9ab57f0a6c8..d10b5e7d3af 100644 --- a/cumulus/parachains/runtimes/constants/src/rococo.rs +++ b/cumulus/parachains/runtimes/constants/src/rococo.rs @@ -108,14 +108,42 @@ pub mod fee { /// Consensus-related. pub mod consensus { + use frame_support::weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}; + /// Maximum number of blocks simultaneously accepted by the Runtime, not yet included /// into the relay chain. - pub const UNINCLUDED_SEGMENT_CAPACITY: u32 = 1; + pub const UNINCLUDED_SEGMENT_CAPACITY: u32 = 3; /// How many parachain blocks are processed by the relay chain per parent. Limits the /// number of blocks authored per slot. pub const BLOCK_PROCESSING_VELOCITY: u32 = 1; /// Relay chain slot duration, in milliseconds. pub const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000; + + /// We allow for 2 seconds of compute with a 6 second average block. + pub const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts( + WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), + cumulus_primitives_core::relay_chain::MAX_POV_SIZE as u64, + ); + + /// This determines the average expected block time that we are targeting. + /// Blocks will be produced at a minimum duration defined by `SLOT_DURATION`. + /// `SLOT_DURATION` is picked up by `pallet_timestamp` which is in turn picked + /// up by `pallet_aura` to implement `fn slot_duration()`. + /// + /// Change this to adjust the block time. + pub const MILLISECS_PER_BLOCK: u64 = 6000; + pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; +} + +/// Time-related +pub mod time { + use polkadot_core_primitives::BlockNumber; + + // Time is measured by number of blocks. + pub const MINUTES: BlockNumber = + 60_000 / (super::consensus::MILLISECS_PER_BLOCK as BlockNumber); + pub const HOURS: BlockNumber = MINUTES * 60; + pub const DAYS: BlockNumber = HOURS * 24; } pub mod snowbridge { diff --git a/cumulus/parachains/runtimes/constants/src/westend.rs b/cumulus/parachains/runtimes/constants/src/westend.rs index 2bd4d18a15e..607d91e8808 100644 --- a/cumulus/parachains/runtimes/constants/src/westend.rs +++ b/cumulus/parachains/runtimes/constants/src/westend.rs @@ -131,12 +131,40 @@ pub mod fee { /// Consensus-related. pub mod consensus { + use frame_support::weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}; + /// Maximum number of blocks simultaneously accepted by the Runtime, not yet included into the /// relay chain. - pub const UNINCLUDED_SEGMENT_CAPACITY: u32 = 1; + pub const UNINCLUDED_SEGMENT_CAPACITY: u32 = 3; /// How many parachain blocks are processed by the relay chain per parent. Limits the number of /// blocks authored per slot. pub const BLOCK_PROCESSING_VELOCITY: u32 = 1; /// Relay chain slot duration, in milliseconds. pub const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000; + + /// We allow for 2 seconds of compute with a 6 second average block. + pub const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts( + WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), + cumulus_primitives_core::relay_chain::MAX_POV_SIZE as u64, + ); + + /// This determines the average expected block time that we are targeting. + /// Blocks will be produced at a minimum duration defined by `SLOT_DURATION`. + /// `SLOT_DURATION` is picked up by `pallet_timestamp` which is in turn picked + /// up by `pallet_aura` to implement `fn slot_duration()`. + /// + /// Change this to adjust the block time. + pub const MILLISECS_PER_BLOCK: u64 = 6000; + pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; +} + +/// Time-related +pub mod time { + use polkadot_core_primitives::BlockNumber; + + // Time is measured by number of blocks. + pub const MINUTES: BlockNumber = + 60_000 / (super::consensus::MILLISECS_PER_BLOCK as BlockNumber); + pub const HOURS: BlockNumber = MINUTES * 60; + pub const DAYS: BlockNumber = HOURS * 24; } diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml b/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml index b8da276567e..9f719421b9d 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml @@ -71,6 +71,7 @@ cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system cumulus-pallet-session-benchmarking = { path = "../../../../pallets/session-benchmarking", default-features = false } cumulus-pallet-xcm = { path = "../../../../pallets/xcm", default-features = false } cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false } +cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } @@ -87,6 +88,7 @@ std = [ "cumulus-pallet-session-benchmarking/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", + "cumulus-primitives-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs index f3ef5e421fb..b8203701d1a 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs @@ -29,7 +29,7 @@ mod contracts; mod weights; mod xcm_config; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::AggregateMessageOrigin; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; @@ -58,8 +58,7 @@ use frame_system::limits::{BlockLength, BlockWeights}; pub use parachains_common as common; use parachains_common::{ impls::DealWithFees, message_queue::*, AccountId, BlockNumber, Hash, Header, Nonce, Signature, - AVERAGE_ON_INITIALIZE_RATIO, MAXIMUM_BLOCK_WEIGHT, MINUTES, NORMAL_DISPATCH_RATIO, - SLOT_DURATION, + AVERAGE_ON_INITIALIZE_RATIO, MINUTES, NORMAL_DISPATCH_RATIO, }; pub use parachains_common::{AuraId, Balance}; use testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee}; @@ -192,6 +191,9 @@ impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; type OnTimestampSet = Aura; + #[cfg(feature = "experimental")] + type MinimumPeriod = ConstU64<0>; + #[cfg(not(feature = "experimental"))] type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; type WeightInfo = pallet_timestamp::weights::SubstrateWeight; } @@ -274,15 +276,17 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type OutboundXcmpMessageSource = XcmpQueue; type XcmpMessageHandler = XcmpQueue; type ReservedXcmpWeight = ReservedXcmpWeight; - type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; - type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, - >; + type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; } +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + impl pallet_insecure_randomness_collective_flip::Config for Runtime {} impl parachain_info::Config for Runtime {} @@ -338,9 +342,9 @@ impl pallet_aura::Config for Runtime { type AuthorityId = AuraId; type DisabledValidators = (); type MaxAuthorities = ConstU32<100_000>; - type AllowMultipleBlocksPerSlot = ConstBool; + type AllowMultipleBlocksPerSlot = ConstBool; #[cfg(feature = "experimental")] - type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; + type SlotDuration = ConstU64; } parameter_types! { @@ -429,7 +433,7 @@ mod benches { impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) } fn authorities() -> Vec { @@ -437,6 +441,15 @@ impl_runtime_apis! { } } + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) + } + } + impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { VERSION diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml index 2c28f229651..ae604da87a9 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml @@ -70,6 +70,7 @@ cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system cumulus-pallet-session-benchmarking = { path = "../../../../pallets/session-benchmarking", default-features = false } cumulus-pallet-xcm = { path = "../../../../pallets/xcm", default-features = false } cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false } +cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } @@ -86,6 +87,7 @@ std = [ "cumulus-pallet-session-benchmarking/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", + "cumulus-primitives-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs index 66fc4b28cd2..1e880cf6cce 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs @@ -33,7 +33,7 @@ mod coretime; mod weights; pub mod xcm_config; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; use frame_support::{ construct_runtime, derive_impl, @@ -53,7 +53,7 @@ use parachains_common::{ impls::DealWithFees, message_queue::{NarrowOriginToSibling, ParaIdToSibling}, AccountId, AuraId, Balance, BlockNumber, Hash, Header, Nonce, Signature, - AVERAGE_ON_INITIALIZE_RATIO, HOURS, MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO, SLOT_DURATION, + AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, }; use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; use sp_api::impl_runtime_apis; @@ -70,7 +70,7 @@ use sp_std::prelude::*; #[cfg(feature = "std")] use sp_version::NativeVersion; use sp_version::RuntimeVersion; -use testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee}; +use testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee, time::*}; use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; use xcm::latest::prelude::*; use xcm_config::{ @@ -202,6 +202,9 @@ impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; type OnTimestampSet = Aura; + #[cfg(feature = "experimental")] + type MinimumPeriod = ConstU64<0>; + #[cfg(not(feature = "experimental"))] type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; type WeightInfo = weights::pallet_timestamp::WeightInfo; } @@ -262,15 +265,17 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type ReservedDmpWeight = ReservedDmpWeight; type XcmpMessageHandler = XcmpQueue; type ReservedXcmpWeight = ReservedXcmpWeight; - type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; - type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, - >; + type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; } +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + parameter_types! { pub MessageQueueServiceWeight: Weight = Perbill::from_percent(35) * RuntimeBlockWeights::get().max_block; } @@ -359,9 +364,9 @@ impl pallet_aura::Config for Runtime { type AuthorityId = AuraId; type DisabledValidators = (); type MaxAuthorities = ConstU32<100_000>; - type AllowMultipleBlocksPerSlot = ConstBool; + type AllowMultipleBlocksPerSlot = ConstBool; #[cfg(feature = "experimental")] - type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; + type SlotDuration = ConstU64; } parameter_types! { @@ -486,7 +491,7 @@ mod benches { impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) } fn authorities() -> Vec { @@ -494,6 +499,15 @@ impl_runtime_apis! { } } + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) + } + } + impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { VERSION diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml index f2e1757dd3e..f85f6896cbe 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml @@ -69,6 +69,7 @@ cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system cumulus-pallet-session-benchmarking = { path = "../../../../pallets/session-benchmarking", default-features = false } cumulus-pallet-xcm = { path = "../../../../pallets/xcm", default-features = false } cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false } +cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } @@ -85,6 +86,7 @@ std = [ "cumulus-pallet-session-benchmarking/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", + "cumulus-primitives-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs index e1db9158ce9..55fe3ba9fbf 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs @@ -24,7 +24,7 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); mod weights; pub mod xcm_config; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; use frame_support::{ construct_runtime, derive_impl, @@ -44,7 +44,7 @@ use parachains_common::{ impls::DealWithFees, message_queue::{NarrowOriginToSibling, ParaIdToSibling}, AccountId, AuraId, Balance, BlockNumber, Hash, Header, Nonce, Signature, - AVERAGE_ON_INITIALIZE_RATIO, HOURS, MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO, SLOT_DURATION, + AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, }; use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; use sp_api::impl_runtime_apis; @@ -61,7 +61,7 @@ use sp_std::prelude::*; #[cfg(feature = "std")] use sp_version::NativeVersion; use sp_version::RuntimeVersion; -use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee}; +use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee, time::*}; use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; use xcm::latest::prelude::*; use xcm_config::{ @@ -193,6 +193,9 @@ impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; type OnTimestampSet = Aura; + #[cfg(feature = "experimental")] + type MinimumPeriod = ConstU64<0>; + #[cfg(not(feature = "experimental"))] type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; type WeightInfo = weights::pallet_timestamp::WeightInfo; } @@ -253,15 +256,17 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type ReservedDmpWeight = ReservedDmpWeight; type XcmpMessageHandler = XcmpQueue; type ReservedXcmpWeight = ReservedXcmpWeight; - type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; - type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, - >; + type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; } +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + impl parachain_info::Config for Runtime {} parameter_types! { @@ -350,9 +355,9 @@ impl pallet_aura::Config for Runtime { type AuthorityId = AuraId; type DisabledValidators = (); type MaxAuthorities = ConstU32<100_000>; - type AllowMultipleBlocksPerSlot = ConstBool; + type AllowMultipleBlocksPerSlot = ConstBool; #[cfg(feature = "experimental")] - type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; + type SlotDuration = ConstU64; } parameter_types! { @@ -473,7 +478,7 @@ mod benches { impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) } fn authorities() -> Vec { @@ -481,6 +486,15 @@ impl_runtime_apis! { } } + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) + } + } + impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { VERSION diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs index ce9c31ba73d..e0ab9c590ba 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs @@ -87,6 +87,7 @@ use parachains_common::{AccountId, Signature}; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; pub use sp_runtime::{Perbill, Permill}; +use testnet_parachains_constants::westend::consensus::*; impl_opaque_keys! { pub struct SessionKeys { @@ -118,29 +119,6 @@ const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10); /// We allow `Normal` extrinsics to fill up the block up to 75%, the rest can be used /// by Operational extrinsics. const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); -/// We allow for .5 seconds of compute with a 12 second average block time. -const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts( - WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), - cumulus_primitives_core::relay_chain::MAX_POV_SIZE as u64, -); - -/// Maximum number of blocks simultaneously accepted by the Runtime, not yet included -/// into the relay chain. -const UNINCLUDED_SEGMENT_CAPACITY: u32 = 3; -/// How many parachain blocks are processed by the relay chain per parent. Limits the -/// number of blocks authored per slot. -const BLOCK_PROCESSING_VELOCITY: u32 = 2; -/// Relay chain slot duration, in milliseconds. -const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000; - -/// This determines the average expected block time that we are targeting. -/// Blocks will be produced at a minimum duration defined by `SLOT_DURATION`. -/// `SLOT_DURATION` is picked up by `pallet_timestamp` which is in turn picked -/// up by `pallet_aura` to implement `fn slot_duration()`. -/// -/// Change this to adjust the block time. -pub const MILLISECS_PER_BLOCK: u64 = 6000; -pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; parameter_types! { pub const BlockHashCount: BlockNumber = 4096; diff --git a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml index efb214fd491..5e5e1e01606 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml @@ -66,6 +66,7 @@ cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system cumulus-pallet-session-benchmarking = { path = "../../../../pallets/session-benchmarking", default-features = false } cumulus-pallet-xcm = { path = "../../../../pallets/xcm", default-features = false } cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false } +cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } @@ -82,6 +83,7 @@ std = [ "cumulus-pallet-session-benchmarking/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", + "cumulus-primitives-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", "enumflags2/std", diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs index 2074b81c2cc..790be31da03 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -22,7 +22,7 @@ pub mod people; mod weights; pub mod xcm_config; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; use frame_support::{ construct_runtime, derive_impl, @@ -44,7 +44,7 @@ use parachains_common::{ impls::DealWithFees, message_queue::{NarrowOriginToSibling, ParaIdToSibling}, AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature, AVERAGE_ON_INITIALIZE_RATIO, - HOURS, MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO, SLOT_DURATION, + HOURS, NORMAL_DISPATCH_RATIO, }; use polkadot_runtime_common::{identity_migrator, BlockHashCount, SlowAdjustingFeeUpdate}; use sp_api::impl_runtime_apis; @@ -184,6 +184,9 @@ impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; type OnTimestampSet = Aura; + #[cfg(feature = "experimental")] + type MinimumPeriod = ConstU64<0>; + #[cfg(not(feature = "experimental"))] type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; type WeightInfo = weights::pallet_timestamp::WeightInfo; } @@ -243,16 +246,18 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type ReservedDmpWeight = ReservedDmpWeight; type XcmpMessageHandler = XcmpQueue; type ReservedXcmpWeight = ReservedXcmpWeight; - type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; - type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, - >; + type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; type WeightInfo = weights::cumulus_pallet_parachain_system::WeightInfo; } +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + parameter_types! { pub MessageQueueServiceWeight: Weight = Perbill::from_percent(35) * RuntimeBlockWeights::get().max_block; @@ -328,9 +333,9 @@ impl pallet_aura::Config for Runtime { type AuthorityId = AuraId; type DisabledValidators = (); type MaxAuthorities = ConstU32<100_000>; - type AllowMultipleBlocksPerSlot = ConstBool; + type AllowMultipleBlocksPerSlot = ConstBool; #[cfg(feature = "experimental")] - type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; + type SlotDuration = ConstU64; } parameter_types! { @@ -459,7 +464,7 @@ mod benches { impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) } fn authorities() -> Vec { @@ -467,6 +472,15 @@ impl_runtime_apis! { } } + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) + } + } + impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { VERSION diff --git a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml index b5d1d6d43d7..c4c76074d75 100644 --- a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml @@ -66,6 +66,7 @@ cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system cumulus-pallet-session-benchmarking = { path = "../../../../pallets/session-benchmarking", default-features = false } cumulus-pallet-xcm = { path = "../../../../pallets/xcm", default-features = false } cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false } +cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } @@ -82,6 +83,7 @@ std = [ "cumulus-pallet-session-benchmarking/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", + "cumulus-primitives-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", "enumflags2/std", diff --git a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs index 4f650fed3cd..24e203c9f01 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs @@ -22,7 +22,7 @@ pub mod people; mod weights; pub mod xcm_config; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; use frame_support::{ construct_runtime, derive_impl, @@ -44,7 +44,7 @@ use parachains_common::{ impls::DealWithFees, message_queue::{NarrowOriginToSibling, ParaIdToSibling}, AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature, AVERAGE_ON_INITIALIZE_RATIO, - HOURS, MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO, SLOT_DURATION, + NORMAL_DISPATCH_RATIO, }; use polkadot_runtime_common::{identity_migrator, BlockHashCount, SlowAdjustingFeeUpdate}; use sp_api::impl_runtime_apis; @@ -63,7 +63,7 @@ use sp_std::prelude::*; #[cfg(feature = "std")] use sp_version::NativeVersion; use sp_version::RuntimeVersion; -use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee}; +use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee, time::*}; use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; use xcm::latest::prelude::BodyId; use xcm_config::{ @@ -184,6 +184,9 @@ impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; type OnTimestampSet = Aura; + #[cfg(feature = "experimental")] + type MinimumPeriod = ConstU64<0>; + #[cfg(not(feature = "experimental"))] type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; type WeightInfo = weights::pallet_timestamp::WeightInfo; } @@ -243,16 +246,18 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type ReservedDmpWeight = ReservedDmpWeight; type XcmpMessageHandler = XcmpQueue; type ReservedXcmpWeight = ReservedXcmpWeight; - type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; - type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, - >; + type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; type WeightInfo = weights::cumulus_pallet_parachain_system::WeightInfo; } +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + parameter_types! { pub MessageQueueServiceWeight: Weight = Perbill::from_percent(35) * RuntimeBlockWeights::get().max_block; @@ -328,9 +333,9 @@ impl pallet_aura::Config for Runtime { type AuthorityId = AuraId; type DisabledValidators = (); type MaxAuthorities = ConstU32<100_000>; - type AllowMultipleBlocksPerSlot = ConstBool; + type AllowMultipleBlocksPerSlot = ConstBool; #[cfg(feature = "experimental")] - type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; + type SlotDuration = ConstU64; } parameter_types! { @@ -459,7 +464,7 @@ mod benches { impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) } fn authorities() -> Vec { @@ -467,6 +472,15 @@ impl_runtime_apis! { } } + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) + } + } + impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { VERSION diff --git a/cumulus/polkadot-parachain/src/command.rs b/cumulus/polkadot-parachain/src/command.rs index a7319b6dfbb..19d067068f4 100644 --- a/cumulus/polkadot-parachain/src/command.rs +++ b/cumulus/polkadot-parachain/src/command.rs @@ -695,7 +695,7 @@ pub fn run() -> Result<()> { .map(|r| r.0) .map_err(Into::into), - AssetHubKusama | AssetHubWestend => + AssetHubKusama => crate::service::start_asset_hub_node::< RuntimeApi, AuraId, @@ -704,7 +704,7 @@ pub fn run() -> Result<()> { .map(|r| r.0) .map_err(Into::into), - AssetHubRococo => + AssetHubRococo | AssetHubWestend => crate::service::start_asset_hub_lookahead_node::< RuntimeApi, AuraId, @@ -713,7 +713,7 @@ pub fn run() -> Result<()> { .map(|r| r.0) .map_err(Into::into), - CollectivesPolkadot | CollectivesWestend => + CollectivesPolkadot => crate::service::start_generic_aura_node::< RuntimeApi, AuraId, @@ -722,6 +722,15 @@ pub fn run() -> Result<()> { .map(|r| r.0) .map_err(Into::into), + CollectivesWestend => + crate::service::start_generic_aura_lookahead_node::< + RuntimeApi, + AuraId, + >(config, polkadot_config, collator_options, id, hwbench) + .await + .map(|r| r.0) + .map_err(Into::into), + Seedling | Shell => crate::service::start_shell_node::( config, @@ -763,7 +772,7 @@ pub fn run() -> Result<()> { chain_spec::bridge_hubs::BridgeHubRuntimeType::Westend | chain_spec::bridge_hubs::BridgeHubRuntimeType::WestendLocal | chain_spec::bridge_hubs::BridgeHubRuntimeType::WestendDevelopment => - crate::service::start_generic_aura_node::< + crate::service::start_generic_aura_lookahead_node::< RuntimeApi, AuraId, >(config, polkadot_config, collator_options, id, hwbench) @@ -772,7 +781,7 @@ pub fn run() -> Result<()> { chain_spec::bridge_hubs::BridgeHubRuntimeType::Rococo | chain_spec::bridge_hubs::BridgeHubRuntimeType::RococoLocal | chain_spec::bridge_hubs::BridgeHubRuntimeType::RococoDevelopment => - crate::service::start_generic_aura_node::< + crate::service::start_generic_aura_lookahead_node::< RuntimeApi, AuraId, >(config, polkadot_config, collator_options, id, hwbench) @@ -787,7 +796,7 @@ pub fn run() -> Result<()> { chain_spec::coretime::CoretimeRuntimeType::RococoDevelopment | chain_spec::coretime::CoretimeRuntimeType::WestendLocal | chain_spec::coretime::CoretimeRuntimeType::WestendDevelopment => - crate::service::start_generic_aura_node::< + crate::service::start_generic_aura_lookahead_node::< RuntimeApi, AuraId, >(config, polkadot_config, collator_options, id, hwbench) @@ -824,7 +833,7 @@ pub fn run() -> Result<()> { chain_spec::people::PeopleRuntimeType::Westend | chain_spec::people::PeopleRuntimeType::WestendLocal | chain_spec::people::PeopleRuntimeType::WestendDevelopment => - crate::service::start_generic_aura_node::< + crate::service::start_generic_aura_lookahead_node::< RuntimeApi, AuraId, >(config, polkadot_config, collator_options, id, hwbench) diff --git a/cumulus/polkadot-parachain/src/service.rs b/cumulus/polkadot-parachain/src/service.rs index 61b9cbbd80d..daeeadd5ece 100644 --- a/cumulus/polkadot-parachain/src/service.rs +++ b/cumulus/polkadot-parachain/src/service.rs @@ -1292,7 +1292,7 @@ where Ok(BasicQueue::new(verifier, Box::new(block_import), None, &spawner, registry)) } -/// Start an aura powered parachain node. Collectives uses this. +/// Start an aura powered parachain node. Some system chains use this. pub async fn start_generic_aura_node( parachain_config: Configuration, polkadot_config: Configuration, @@ -1386,6 +1386,106 @@ where .await } +/// Uses the lookahead collator to support async backing. +/// +/// Start an aura powered parachain node. Some system chains use this. +pub async fn start_generic_aura_lookahead_node( + parachain_config: Configuration, + polkadot_config: Configuration, + collator_options: CollatorOptions, + para_id: ParaId, + hwbench: Option, +) -> sc_service::error::Result<(TaskManager, Arc>)> +where + RuntimeApi: ConstructRuntimeApi> + Send + Sync + 'static, + RuntimeApi::RuntimeApi: sp_transaction_pool::runtime_api::TaggedTransactionQueue + + sp_api::Metadata + + sp_session::SessionKeys + + sp_api::ApiExt + + sp_offchain::OffchainWorkerApi + + sp_block_builder::BlockBuilder + + cumulus_primitives_core::CollectCollationInfo + + sp_consensus_aura::AuraApi::Pair as Pair>::Public> + + pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi + + frame_rpc_system::AccountNonceApi + + cumulus_primitives_aura::AuraUnincludedSegmentApi, + <::Pair as Pair>::Signature: + TryFrom> + std::hash::Hash + sp_runtime::traits::Member + Codec, +{ + start_node_impl::( + parachain_config, + polkadot_config, + collator_options, + CollatorSybilResistance::Resistant, // Aura + para_id, + |_| Ok(RpcModule::new(())), + aura_build_import_queue::<_, AuraId>, + |client, + block_import, + prometheus_registry, + telemetry, + task_manager, + relay_chain_interface, + transaction_pool, + sync_oracle, + keystore, + relay_chain_slot_duration, + para_id, + collator_key, + overseer_handle, + announce_block, + backend| { + let slot_duration = cumulus_client_consensus_aura::slot_duration(&*client)?; + + let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( + task_manager.spawn_handle(), + client.clone(), + transaction_pool, + prometheus_registry, + telemetry.clone(), + ); + let proposer = Proposer::new(proposer_factory); + + let collator_service = CollatorService::new( + client.clone(), + Arc::new(task_manager.spawn_handle()), + announce_block, + client.clone(), + ); + + let params = AuraParams { + create_inherent_data_providers: move |_, ()| async move { Ok(()) }, + block_import, + para_client: client.clone(), + para_backend: backend, + relay_client: relay_chain_interface, + code_hash_provider: move |block_hash| { + client.code_at(block_hash).ok().map(|c| ValidationCode::from(c).hash()) + }, + sync_oracle, + keystore, + collator_key, + para_id, + overseer_handle, + slot_duration, + relay_chain_slot_duration, + proposer, + collator_service, + authoring_duration: Duration::from_millis(1500), + reinitialize: false, + }; + + let fut = + aura::run::::Pair, _, _, _, _, _, _, _, _, _>(params); + task_manager.spawn_essential_handle().spawn("aura", None, fut); + + Ok(()) + }, + hwbench, + ) + .await +} + /// Start a shell node which should later transition into an Aura powered parachain node. Asset Hub /// uses this because at genesis, Asset Hub was on the `shell` runtime which didn't have Aura and /// needs to sync and upgrade before it can run `AuraApi` functions. @@ -1805,7 +1905,8 @@ where + sp_block_builder::BlockBuilder + cumulus_primitives_core::CollectCollationInfo + pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi - + frame_rpc_system::AccountNonceApi, + + frame_rpc_system::AccountNonceApi + + cumulus_primitives_aura::AuraUnincludedSegmentApi, RB: Fn(Arc>) -> Result, sc_service::Error>, BIQ: FnOnce( Arc>, @@ -2039,7 +2140,7 @@ pub async fn start_contracts_rococo_node( collator_key, overseer_handle, announce_block, - _backend| { + backend| { let slot_duration = cumulus_client_consensus_aura::slot_duration(&*client)?; let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( @@ -2058,11 +2159,15 @@ pub async fn start_contracts_rococo_node( client.clone(), ); - let params = BasicAuraParams { + let params = AuraParams { create_inherent_data_providers: move |_, ()| async move { Ok(()) }, block_import, - para_client: client, + para_client: client.clone(), + para_backend: backend.clone(), relay_client: relay_chain_interface, + code_hash_provider: move |block_hash| { + client.code_at(block_hash).ok().map(|c| ValidationCode::from(c).hash()) + }, sync_oracle, keystore, collator_key, @@ -2073,11 +2178,11 @@ pub async fn start_contracts_rococo_node( proposer, collator_service, // Very limited proposal time. - authoring_duration: Duration::from_millis(500), - collation_request_receiver: None, + authoring_duration: Duration::from_millis(1500), + reinitialize: false, }; - let fut = basic_aura::run::< + let fut = aura::run::< Block, sp_consensus_aura::sr25519::AuthorityPair, _, @@ -2087,6 +2192,8 @@ pub async fn start_contracts_rococo_node( _, _, _, + _, + _, >(params); task_manager.spawn_essential_handle().spawn("aura", None, fut); diff --git a/polkadot/zombienet_tests/smoke/0004-configure-broker.js b/polkadot/zombienet_tests/smoke/0004-configure-broker.js index 889861f5c52..52a32b8a7c8 100644 --- a/polkadot/zombienet_tests/smoke/0004-configure-broker.js +++ b/polkadot/zombienet_tests/smoke/0004-configure-broker.js @@ -54,9 +54,11 @@ async function run(nodeName, networkInfo, _jsArgs) { unsub(); return resolve(); } else if (result.isError) { - console.log(`Transaction Error`); + // Probably happens because of: https://github.com/paritytech/polkadot-sdk/issues/1202. + console.log(`Transaction error`); + // We ignore the error because it is very likely misleading, because of the issue mentioned above. unsub(); - return reject(); + return resolve(); } }); }); diff --git a/prdoc/pr_2949-async-backing-on-all-testnet-system-chains.prdoc b/prdoc/pr_2949-async-backing-on-all-testnet-system-chains.prdoc new file mode 100644 index 00000000000..265940815ba --- /dev/null +++ b/prdoc/pr_2949-async-backing-on-all-testnet-system-chains.prdoc @@ -0,0 +1,23 @@ +title: Enable async backing on all testnet system chains + +doc: + - audience: Runtime User + description: | + Async backing has been enabled on all testnet system chains: asset-hub-westend, + bridge-hub-westend, bridge-hub-rococo, collectives-westend, contracts-rococo, + coretime-westend, coretime-rococo, people-westend, and people-rococo. These now target 6s + block times. + For the running coretime chains, that requires updating the configuration after the runtime + upgrade but before the end of the current region. + +crates: + - name: asset-hub-westend-runtime + - name: bridge-hub-westend-runtime + - name: bridge-hub-rococo-runtime + - name: collectives-westend-runtime + - name: contracts-rococo-runtime + - name: coretime-westend-runtime + - name: coretime-rococo-runtime + - name: people-westend-runtime + - name: people-rococo-runtime + - name: polkadot-parachain-bin -- GitLab From 2ab3f03f0bb1773c3799997d541b60e6be5f67bd Mon Sep 17 00:00:00 2001 From: Clara van Staden Date: Fri, 2 Feb 2024 19:08:36 +0200 Subject: [PATCH 004/133] Removes Snowbridge parachain directory (#3186) Removes the `bridges/snowbridge/parachain` directory and moves everything up to under `snowbridge` directly. We are cleaning up our local dev env after merging our crates into the polkadot-sdk. --------- Co-authored-by: claravanstaden --- Cargo.toml | 30 +-- .../pallets/ethereum-client/Cargo.toml | 24 +-- .../pallets/ethereum-client/README.md | 0 .../pallets/ethereum-client/benchmark.md | 0 .../ethereum-client/fixtures/Cargo.toml | 10 +- .../ethereum-client/fixtures/src/lib.rs | 0 .../ethereum-client/src/benchmarking/mod.rs | 0 .../ethereum-client/src/benchmarking/util.rs | 0 .../pallets/ethereum-client/src/config/mod.rs | 0 .../pallets/ethereum-client/src/functions.rs | 0 .../pallets/ethereum-client/src/impls.rs | 0 .../pallets/ethereum-client/src/lib.rs | 0 .../pallets/ethereum-client/src/mock.rs | 0 .../pallets/ethereum-client/src/tests.rs | 0 .../pallets/ethereum-client/src/types.rs | 0 .../pallets/ethereum-client/src/weights.rs | 0 .../fixtures/execution-header-update.json | 0 .../fixtures/finalized-header-update.json | 0 .../tests/fixtures/inbound-message.json | 0 .../tests/fixtures/initial-checkpoint.json | 0 .../next-finalized-header-update.json | 0 .../fixtures/next-sync-committee-update.json | 0 .../tests/fixtures/sync-committee-update.json | 0 .../pallets/inbound-queue/Cargo.toml | 26 +-- .../pallets/inbound-queue/README.md | 0 .../pallets/inbound-queue/fixtures/Cargo.toml | 10 +- .../pallets/inbound-queue/fixtures/src/lib.rs | 0 .../fixtures/src/register_token.rs | 0 .../register_token_with_insufficient_fee.rs | 0 .../inbound-queue/fixtures/src/send_token.rs | 0 .../fixtures/src/send_token_to_penpal.rs | 0 .../inbound-queue/src/benchmarking/mod.rs | 0 .../pallets/inbound-queue/src/envelope.rs | 0 .../pallets/inbound-queue/src/lib.rs | 0 .../pallets/inbound-queue/src/mock.rs | 0 .../pallets/inbound-queue/src/test.rs | 0 .../pallets/inbound-queue/src/weights.rs | 0 .../pallets/outbound-queue/Cargo.toml | 24 +-- .../pallets/outbound-queue/README.md | 0 .../outbound-queue/merkle-tree/Cargo.toml | 6 +- .../outbound-queue/merkle-tree/README.md | 0 .../outbound-queue/merkle-tree/src/lib.rs | 0 .../outbound-queue/runtime-api/Cargo.toml | 10 +- .../outbound-queue/runtime-api/README.md | 0 .../outbound-queue/runtime-api/src/lib.rs | 0 .../pallets/outbound-queue/src/api.rs | 0 .../outbound-queue/src/benchmarking.rs | 0 .../pallets/outbound-queue/src/lib.rs | 0 .../pallets/outbound-queue/src/mock.rs | 0 .../src/process_message_impl.rs | 0 .../outbound-queue/src/send_message_impl.rs | 0 .../pallets/outbound-queue/src/test.rs | 0 .../pallets/outbound-queue/src/types.rs | 0 .../pallets/outbound-queue/src/weights.rs | 0 .../{parachain => }/pallets/system/Cargo.toml | 28 +-- .../{parachain => }/pallets/system/README.md | 0 .../pallets/system/runtime-api/Cargo.toml | 8 +- .../pallets/system/runtime-api/README.md | 0 .../pallets/system/runtime-api/src/lib.rs | 0 .../{parachain => }/pallets/system/src/api.rs | 0 .../pallets/system/src/benchmarking.rs | 0 .../{parachain => }/pallets/system/src/lib.rs | 0 .../pallets/system/src/migration.rs | 0 .../pallets/system/src/mock.rs | 0 .../pallets/system/src/tests.rs | 0 .../pallets/system/src/weights.rs | 0 bridges/snowbridge/parachain/LICENSE | 201 ------------------ bridges/snowbridge/parachain/README.md | 127 ----------- .../primitives/ethereum/.cargo/config.toml | 2 - .../primitives/beacon/Cargo.toml | 12 +- .../primitives/beacon/README.md | 0 .../primitives/beacon/src/bits.rs | 0 .../primitives/beacon/src/bls.rs | 0 .../primitives/beacon/src/config.rs | 0 .../primitives/beacon/src/lib.rs | 0 .../primitives/beacon/src/merkle_proof.rs | 0 .../primitives/beacon/src/receipt.rs | 0 .../primitives/beacon/src/serde_utils.rs | 0 .../primitives/beacon/src/ssz.rs | 0 .../primitives/beacon/src/types.rs | 0 .../primitives/beacon/src/updates.rs | 0 .../primitives/core/Cargo.toml | 20 +- .../{parachain => }/primitives/core/README.md | 0 .../primitives/core/src/inbound.rs | 0 .../primitives/core/src/lib.rs | 0 .../primitives/core/src/operating_mode.rs | 0 .../primitives/core/src/outbound.rs | 0 .../primitives/core/src/pricing.rs | 0 .../primitives/core/src/ringbuffer.rs | 0 .../primitives/core/src/tests.rs | 0 .../core/tests/fixtures/packet.scale | Bin .../primitives/core/tests/mod.rs | 0 .../primitives/ethereum/Cargo.toml | 8 +- .../primitives/ethereum/README.md | 0 .../primitives/ethereum/src/header.rs | 0 .../primitives/ethereum/src/lib.rs | 0 .../primitives/ethereum/src/log.rs | 0 .../primitives/ethereum/src/mpt.rs | 0 .../primitives/ethereum/src/receipt.rs | 0 .../primitives/router/Cargo.toml | 18 +- .../primitives/router/README.md | 0 .../primitives/router/src/inbound/mod.rs | 0 .../primitives/router/src/inbound/tests.rs | 0 .../primitives/router/src/lib.rs | 0 .../primitives/router/src/outbound/mod.rs | 0 .../primitives/router/src/outbound/tests.rs | 0 .../runtime/runtime-common/Cargo.toml | 14 +- .../runtime/runtime-common/README.md | 0 .../runtime/runtime-common/src/lib.rs | 0 .../runtime/runtime-common/src/tests.rs | 0 .../runtime/test-common/Cargo.toml | 110 +++++----- .../runtime/test-common/README.md | 0 .../runtime/test-common/src/lib.rs | 2 +- .../{parachain => }/scripts/benchmark.sh | 0 .../{parachain => }/scripts/hexliteral.sh | 0 .../{parachain => }/scripts/init.sh | 0 .../scripts/make-build-config.sh | 0 .../scripts/verify-pallets-build.sh | 0 .../bridges/bridge-hub-rococo/Cargo.toml | 12 +- .../assets/asset-hub-rococo/Cargo.toml | 2 +- .../bridge-hubs/bridge-hub-rococo/Cargo.toml | 22 +- .../runtimes/bridge-hubs/common/Cargo.toml | 2 +- 122 files changed, 199 insertions(+), 529 deletions(-) rename bridges/snowbridge/{parachain => }/pallets/ethereum-client/Cargo.toml (71%) rename bridges/snowbridge/{parachain => }/pallets/ethereum-client/README.md (100%) rename bridges/snowbridge/{parachain => }/pallets/ethereum-client/benchmark.md (100%) rename bridges/snowbridge/{parachain => }/pallets/ethereum-client/fixtures/Cargo.toml (67%) rename bridges/snowbridge/{parachain => }/pallets/ethereum-client/fixtures/src/lib.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/ethereum-client/src/benchmarking/mod.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/ethereum-client/src/benchmarking/util.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/ethereum-client/src/config/mod.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/ethereum-client/src/functions.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/ethereum-client/src/impls.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/ethereum-client/src/lib.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/ethereum-client/src/mock.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/ethereum-client/src/tests.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/ethereum-client/src/types.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/ethereum-client/src/weights.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/ethereum-client/tests/fixtures/execution-header-update.json (100%) rename bridges/snowbridge/{parachain => }/pallets/ethereum-client/tests/fixtures/finalized-header-update.json (100%) rename bridges/snowbridge/{parachain => }/pallets/ethereum-client/tests/fixtures/inbound-message.json (100%) rename bridges/snowbridge/{parachain => }/pallets/ethereum-client/tests/fixtures/initial-checkpoint.json (100%) rename bridges/snowbridge/{parachain => }/pallets/ethereum-client/tests/fixtures/next-finalized-header-update.json (100%) rename bridges/snowbridge/{parachain => }/pallets/ethereum-client/tests/fixtures/next-sync-committee-update.json (100%) rename bridges/snowbridge/{parachain => }/pallets/ethereum-client/tests/fixtures/sync-committee-update.json (100%) rename bridges/snowbridge/{parachain => }/pallets/inbound-queue/Cargo.toml (73%) rename bridges/snowbridge/{parachain => }/pallets/inbound-queue/README.md (100%) rename bridges/snowbridge/{parachain => }/pallets/inbound-queue/fixtures/Cargo.toml (67%) rename bridges/snowbridge/{parachain => }/pallets/inbound-queue/fixtures/src/lib.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/inbound-queue/fixtures/src/register_token.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/inbound-queue/fixtures/src/register_token_with_insufficient_fee.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/inbound-queue/fixtures/src/send_token.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/inbound-queue/fixtures/src/send_token_to_penpal.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/inbound-queue/src/benchmarking/mod.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/inbound-queue/src/envelope.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/inbound-queue/src/lib.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/inbound-queue/src/mock.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/inbound-queue/src/test.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/inbound-queue/src/weights.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/outbound-queue/Cargo.toml (62%) rename bridges/snowbridge/{parachain => }/pallets/outbound-queue/README.md (100%) rename bridges/snowbridge/{parachain => }/pallets/outbound-queue/merkle-tree/Cargo.toml (74%) rename bridges/snowbridge/{parachain => }/pallets/outbound-queue/merkle-tree/README.md (100%) rename bridges/snowbridge/{parachain => }/pallets/outbound-queue/merkle-tree/src/lib.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/outbound-queue/runtime-api/Cargo.toml (65%) rename bridges/snowbridge/{parachain => }/pallets/outbound-queue/runtime-api/README.md (100%) rename bridges/snowbridge/{parachain => }/pallets/outbound-queue/runtime-api/src/lib.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/outbound-queue/src/api.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/outbound-queue/src/benchmarking.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/outbound-queue/src/lib.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/outbound-queue/src/mock.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/outbound-queue/src/process_message_impl.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/outbound-queue/src/send_message_impl.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/outbound-queue/src/test.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/outbound-queue/src/types.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/outbound-queue/src/weights.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/system/Cargo.toml (63%) rename bridges/snowbridge/{parachain => }/pallets/system/README.md (100%) rename bridges/snowbridge/{parachain => }/pallets/system/runtime-api/Cargo.toml (65%) rename bridges/snowbridge/{parachain => }/pallets/system/runtime-api/README.md (100%) rename bridges/snowbridge/{parachain => }/pallets/system/runtime-api/src/lib.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/system/src/api.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/system/src/benchmarking.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/system/src/lib.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/system/src/migration.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/system/src/mock.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/system/src/tests.rs (100%) rename bridges/snowbridge/{parachain => }/pallets/system/src/weights.rs (100%) delete mode 100644 bridges/snowbridge/parachain/LICENSE delete mode 100644 bridges/snowbridge/parachain/README.md delete mode 100644 bridges/snowbridge/parachain/primitives/ethereum/.cargo/config.toml rename bridges/snowbridge/{parachain => }/primitives/beacon/Cargo.toml (72%) rename bridges/snowbridge/{parachain => }/primitives/beacon/README.md (100%) rename bridges/snowbridge/{parachain => }/primitives/beacon/src/bits.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/beacon/src/bls.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/beacon/src/config.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/beacon/src/lib.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/beacon/src/merkle_proof.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/beacon/src/receipt.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/beacon/src/serde_utils.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/beacon/src/ssz.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/beacon/src/types.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/beacon/src/updates.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/core/Cargo.toml (62%) rename bridges/snowbridge/{parachain => }/primitives/core/README.md (100%) rename bridges/snowbridge/{parachain => }/primitives/core/src/inbound.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/core/src/lib.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/core/src/operating_mode.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/core/src/outbound.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/core/src/pricing.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/core/src/ringbuffer.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/core/src/tests.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/core/tests/fixtures/packet.scale (100%) rename bridges/snowbridge/{parachain => }/primitives/core/tests/mod.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/ethereum/Cargo.toml (81%) rename bridges/snowbridge/{parachain => }/primitives/ethereum/README.md (100%) rename bridges/snowbridge/{parachain => }/primitives/ethereum/src/header.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/ethereum/src/lib.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/ethereum/src/log.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/ethereum/src/mpt.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/ethereum/src/receipt.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/router/Cargo.toml (66%) rename bridges/snowbridge/{parachain => }/primitives/router/README.md (100%) rename bridges/snowbridge/{parachain => }/primitives/router/src/inbound/mod.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/router/src/inbound/tests.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/router/src/lib.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/router/src/outbound/mod.rs (100%) rename bridges/snowbridge/{parachain => }/primitives/router/src/outbound/tests.rs (100%) rename bridges/snowbridge/{parachain => }/runtime/runtime-common/Cargo.toml (65%) rename bridges/snowbridge/{parachain => }/runtime/runtime-common/README.md (100%) rename bridges/snowbridge/{parachain => }/runtime/runtime-common/src/lib.rs (100%) rename bridges/snowbridge/{parachain => }/runtime/runtime-common/src/tests.rs (100%) rename bridges/snowbridge/{parachain => }/runtime/test-common/Cargo.toml (51%) rename bridges/snowbridge/{parachain => }/runtime/test-common/README.md (100%) rename bridges/snowbridge/{parachain => }/runtime/test-common/src/lib.rs (99%) rename bridges/snowbridge/{parachain => }/scripts/benchmark.sh (100%) rename bridges/snowbridge/{parachain => }/scripts/hexliteral.sh (100%) rename bridges/snowbridge/{parachain => }/scripts/init.sh (100%) rename bridges/snowbridge/{parachain => }/scripts/make-build-config.sh (100%) rename bridges/snowbridge/{parachain => }/scripts/verify-pallets-build.sh (100%) diff --git a/Cargo.toml b/Cargo.toml index a08c64a4031..62f8c9c8953 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,21 +36,21 @@ members = [ "bridges/primitives/test-utils", "bridges/primitives/xcm-bridge-hub", "bridges/primitives/xcm-bridge-hub-router", - "bridges/snowbridge/parachain/pallets/ethereum-client", - "bridges/snowbridge/parachain/pallets/ethereum-client/fixtures", - "bridges/snowbridge/parachain/pallets/inbound-queue", - "bridges/snowbridge/parachain/pallets/inbound-queue/fixtures", - "bridges/snowbridge/parachain/pallets/outbound-queue", - "bridges/snowbridge/parachain/pallets/outbound-queue/merkle-tree", - "bridges/snowbridge/parachain/pallets/outbound-queue/runtime-api", - "bridges/snowbridge/parachain/pallets/system", - "bridges/snowbridge/parachain/pallets/system/runtime-api", - "bridges/snowbridge/parachain/primitives/beacon", - "bridges/snowbridge/parachain/primitives/core", - "bridges/snowbridge/parachain/primitives/ethereum", - "bridges/snowbridge/parachain/primitives/router", - "bridges/snowbridge/parachain/runtime/runtime-common", - "bridges/snowbridge/parachain/runtime/test-common", + "bridges/snowbridge/pallets/ethereum-client", + "bridges/snowbridge/pallets/ethereum-client/fixtures", + "bridges/snowbridge/pallets/inbound-queue", + "bridges/snowbridge/pallets/inbound-queue/fixtures", + "bridges/snowbridge/pallets/outbound-queue", + "bridges/snowbridge/pallets/outbound-queue/merkle-tree", + "bridges/snowbridge/pallets/outbound-queue/runtime-api", + "bridges/snowbridge/pallets/system", + "bridges/snowbridge/pallets/system/runtime-api", + "bridges/snowbridge/primitives/beacon", + "bridges/snowbridge/primitives/core", + "bridges/snowbridge/primitives/ethereum", + "bridges/snowbridge/primitives/router", + "bridges/snowbridge/runtime/runtime-common", + "bridges/snowbridge/runtime/test-common", "cumulus/client/cli", "cumulus/client/collator", "cumulus/client/consensus/aura", diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/Cargo.toml b/bridges/snowbridge/pallets/ethereum-client/Cargo.toml similarity index 71% rename from bridges/snowbridge/parachain/pallets/ethereum-client/Cargo.toml rename to bridges/snowbridge/pallets/ethereum-client/Cargo.toml index c490f3af73c..2f76d5b8357 100644 --- a/bridges/snowbridge/parachain/pallets/ethereum-client/Cargo.toml +++ b/bridges/snowbridge/pallets/ethereum-client/Cargo.toml @@ -26,30 +26,30 @@ rlp = { version = "0.5.2", default-features = false } hex-literal = { version = "0.4.1", optional = true } log = { version = "0.4.20", default-features = false } -frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } -frame-support = { path = "../../../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../../../substrate/frame/system", default-features = false } -sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } -sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } -sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false } -sp-io = { path = "../../../../../substrate/primitives/io", default-features = false, optional = true } +frame-benchmarking = { path = "../../../../substrate/frame/benchmarking", default-features = false, optional = true } +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false, optional = true } snowbridge-core = { path = "../../primitives/core", default-features = false } snowbridge-ethereum = { path = "../../primitives/ethereum", default-features = false } snowbridge-pallet-ethereum-client-fixtures = { path = "./fixtures", default-features = false, optional = true } primitives = { package = "snowbridge-beacon-primitives", path = "../../primitives/beacon", default-features = false } static_assertions = { version = "1.1.0", default-features = false } -bp-runtime = { path = "../../../../primitives/runtime", default-features = false } -pallet-timestamp = { path = "../../../../../substrate/frame/timestamp", default-features = false, optional = true } +bp-runtime = { path = "../../../primitives/runtime", default-features = false } +pallet-timestamp = { path = "../../../../substrate/frame/timestamp", default-features = false, optional = true } [dev-dependencies] rand = "0.8.5" -sp-keyring = { path = "../../../../../substrate/primitives/keyring" } +sp-keyring = { path = "../../../../substrate/primitives/keyring" } serde_json = "1.0.111" hex-literal = "0.4.1" -pallet-timestamp = { path = "../../../../../substrate/frame/timestamp" } +pallet-timestamp = { path = "../../../../substrate/frame/timestamp" } snowbridge-pallet-ethereum-client-fixtures = { path = "./fixtures" } -sp-io = { path = "../../../../../substrate/primitives/io" } +sp-io = { path = "../../../../substrate/primitives/io" } serde = "1.0.195" [features] diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/README.md b/bridges/snowbridge/pallets/ethereum-client/README.md similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/README.md rename to bridges/snowbridge/pallets/ethereum-client/README.md diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/benchmark.md b/bridges/snowbridge/pallets/ethereum-client/benchmark.md similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/benchmark.md rename to bridges/snowbridge/pallets/ethereum-client/benchmark.md diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/fixtures/Cargo.toml b/bridges/snowbridge/pallets/ethereum-client/fixtures/Cargo.toml similarity index 67% rename from bridges/snowbridge/parachain/pallets/ethereum-client/fixtures/Cargo.toml rename to bridges/snowbridge/pallets/ethereum-client/fixtures/Cargo.toml index f3c26bbbe31..fd1914a7d30 100644 --- a/bridges/snowbridge/parachain/pallets/ethereum-client/fixtures/Cargo.toml +++ b/bridges/snowbridge/pallets/ethereum-client/fixtures/Cargo.toml @@ -16,11 +16,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] hex-literal = { version = "0.4.1" } -sp-core = { path = "../../../../../../substrate/primitives/core", default-features = false } -sp-std = { path = "../../../../../../substrate/primitives/std", default-features = false } -frame-benchmarking = { path = "../../../../../../substrate/frame/benchmarking", default-features = false, optional = true } -frame-support = { path = "../../../../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../../../../substrate/frame/system", default-features = false } +sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } +sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } +frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } +frame-support = { path = "../../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../../substrate/frame/system", default-features = false } snowbridge-core = { path = "../../../primitives/core", default-features = false } snowbridge-beacon-primitives = { path = "../../../primitives/beacon", default-features = false } diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/fixtures/src/lib.rs b/bridges/snowbridge/pallets/ethereum-client/fixtures/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/fixtures/src/lib.rs rename to bridges/snowbridge/pallets/ethereum-client/fixtures/src/lib.rs diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/src/benchmarking/mod.rs b/bridges/snowbridge/pallets/ethereum-client/src/benchmarking/mod.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/src/benchmarking/mod.rs rename to bridges/snowbridge/pallets/ethereum-client/src/benchmarking/mod.rs diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/src/benchmarking/util.rs b/bridges/snowbridge/pallets/ethereum-client/src/benchmarking/util.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/src/benchmarking/util.rs rename to bridges/snowbridge/pallets/ethereum-client/src/benchmarking/util.rs diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/src/config/mod.rs b/bridges/snowbridge/pallets/ethereum-client/src/config/mod.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/src/config/mod.rs rename to bridges/snowbridge/pallets/ethereum-client/src/config/mod.rs diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/src/functions.rs b/bridges/snowbridge/pallets/ethereum-client/src/functions.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/src/functions.rs rename to bridges/snowbridge/pallets/ethereum-client/src/functions.rs diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/src/impls.rs b/bridges/snowbridge/pallets/ethereum-client/src/impls.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/src/impls.rs rename to bridges/snowbridge/pallets/ethereum-client/src/impls.rs diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/src/lib.rs b/bridges/snowbridge/pallets/ethereum-client/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/src/lib.rs rename to bridges/snowbridge/pallets/ethereum-client/src/lib.rs diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/src/mock.rs b/bridges/snowbridge/pallets/ethereum-client/src/mock.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/src/mock.rs rename to bridges/snowbridge/pallets/ethereum-client/src/mock.rs diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/src/tests.rs b/bridges/snowbridge/pallets/ethereum-client/src/tests.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/src/tests.rs rename to bridges/snowbridge/pallets/ethereum-client/src/tests.rs diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/src/types.rs b/bridges/snowbridge/pallets/ethereum-client/src/types.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/src/types.rs rename to bridges/snowbridge/pallets/ethereum-client/src/types.rs diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/src/weights.rs b/bridges/snowbridge/pallets/ethereum-client/src/weights.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/src/weights.rs rename to bridges/snowbridge/pallets/ethereum-client/src/weights.rs diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/execution-header-update.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/execution-header-update.json similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/execution-header-update.json rename to bridges/snowbridge/pallets/ethereum-client/tests/fixtures/execution-header-update.json diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/finalized-header-update.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/finalized-header-update.json similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/finalized-header-update.json rename to bridges/snowbridge/pallets/ethereum-client/tests/fixtures/finalized-header-update.json diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/inbound-message.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/inbound-message.json similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/inbound-message.json rename to bridges/snowbridge/pallets/ethereum-client/tests/fixtures/inbound-message.json diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/initial-checkpoint.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/initial-checkpoint.json similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/initial-checkpoint.json rename to bridges/snowbridge/pallets/ethereum-client/tests/fixtures/initial-checkpoint.json diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/next-finalized-header-update.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/next-finalized-header-update.json similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/next-finalized-header-update.json rename to bridges/snowbridge/pallets/ethereum-client/tests/fixtures/next-finalized-header-update.json diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/next-sync-committee-update.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/next-sync-committee-update.json similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/next-sync-committee-update.json rename to bridges/snowbridge/pallets/ethereum-client/tests/fixtures/next-sync-committee-update.json diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/sync-committee-update.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update.json similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/sync-committee-update.json rename to bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update.json diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue/Cargo.toml similarity index 73% rename from bridges/snowbridge/parachain/pallets/inbound-queue/Cargo.toml rename to bridges/snowbridge/pallets/inbound-queue/Cargo.toml index 367e00ad46a..1081b162dda 100644 --- a/bridges/snowbridge/parachain/pallets/inbound-queue/Cargo.toml +++ b/bridges/snowbridge/pallets/inbound-queue/Cargo.toml @@ -25,18 +25,18 @@ alloy-sol-types = { version = "0.4.2", default-features = false } alloy-rlp = { version = "0.3.3", default-features = false, features = ["derive"] } num-traits = { version = "0.2.16", default-features = false } -frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } -frame-support = { path = "../../../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../../../substrate/frame/system", default-features = false } -pallet-balances = { path = "../../../../../substrate/frame/balances", default-features = false } -sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } -sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } -sp-io = { path = "../../../../../substrate/primitives/io", default-features = false } -sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false } +frame-benchmarking = { path = "../../../../substrate/frame/benchmarking", default-features = false, optional = true } +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } +pallet-balances = { path = "../../../../substrate/frame/balances", default-features = false } +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } -xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } -xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false } -xcm-executor = { package = "staging-xcm-executor", path = "../../../../../polkadot/xcm/xcm-executor", default-features = false } +xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../../polkadot/xcm/xcm-builder", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../../polkadot/xcm/xcm-executor", default-features = false } snowbridge-core = { path = "../../primitives/core", default-features = false } snowbridge-ethereum = { path = "../../primitives/ethereum", default-features = false } @@ -45,8 +45,8 @@ snowbridge-beacon-primitives = { path = "../../primitives/beacon", default-featu snowbridge-pallet-inbound-queue-fixtures = { path = "./fixtures", default-features = false, optional = true } [dev-dependencies] -frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking" } -sp-keyring = { path = "../../../../../substrate/primitives/keyring" } +frame-benchmarking = { path = "../../../../substrate/frame/benchmarking" } +sp-keyring = { path = "../../../../substrate/primitives/keyring" } snowbridge-pallet-ethereum-client = { path = "../ethereum-client" } hex-literal = { version = "0.4.1" } diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/README.md b/bridges/snowbridge/pallets/inbound-queue/README.md similarity index 100% rename from bridges/snowbridge/parachain/pallets/inbound-queue/README.md rename to bridges/snowbridge/pallets/inbound-queue/README.md diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/fixtures/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue/fixtures/Cargo.toml similarity index 67% rename from bridges/snowbridge/parachain/pallets/inbound-queue/fixtures/Cargo.toml rename to bridges/snowbridge/pallets/inbound-queue/fixtures/Cargo.toml index 7ed2b731519..61f1421e056 100644 --- a/bridges/snowbridge/parachain/pallets/inbound-queue/fixtures/Cargo.toml +++ b/bridges/snowbridge/pallets/inbound-queue/fixtures/Cargo.toml @@ -16,11 +16,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] hex-literal = { version = "0.4.1" } -sp-core = { path = "../../../../../../substrate/primitives/core", default-features = false } -sp-std = { path = "../../../../../../substrate/primitives/std", default-features = false } -frame-benchmarking = { path = "../../../../../../substrate/frame/benchmarking", default-features = false, optional = true } -frame-support = { path = "../../../../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../../../../substrate/frame/system", default-features = false } +sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } +sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } +frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } +frame-support = { path = "../../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../../substrate/frame/system", default-features = false } snowbridge-core = { path = "../../../primitives/core", default-features = false } snowbridge-beacon-primitives = { path = "../../../primitives/beacon", default-features = false } diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/fixtures/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/inbound-queue/fixtures/src/lib.rs rename to bridges/snowbridge/pallets/inbound-queue/fixtures/src/lib.rs diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/fixtures/src/register_token.rs b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/register_token.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/inbound-queue/fixtures/src/register_token.rs rename to bridges/snowbridge/pallets/inbound-queue/fixtures/src/register_token.rs diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/fixtures/src/register_token_with_insufficient_fee.rs b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/register_token_with_insufficient_fee.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/inbound-queue/fixtures/src/register_token_with_insufficient_fee.rs rename to bridges/snowbridge/pallets/inbound-queue/fixtures/src/register_token_with_insufficient_fee.rs diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/fixtures/src/send_token.rs b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/send_token.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/inbound-queue/fixtures/src/send_token.rs rename to bridges/snowbridge/pallets/inbound-queue/fixtures/src/send_token.rs diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/fixtures/src/send_token_to_penpal.rs b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/send_token_to_penpal.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/inbound-queue/fixtures/src/send_token_to_penpal.rs rename to bridges/snowbridge/pallets/inbound-queue/fixtures/src/send_token_to_penpal.rs diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/src/benchmarking/mod.rs b/bridges/snowbridge/pallets/inbound-queue/src/benchmarking/mod.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/inbound-queue/src/benchmarking/mod.rs rename to bridges/snowbridge/pallets/inbound-queue/src/benchmarking/mod.rs diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/src/envelope.rs b/bridges/snowbridge/pallets/inbound-queue/src/envelope.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/inbound-queue/src/envelope.rs rename to bridges/snowbridge/pallets/inbound-queue/src/envelope.rs diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/inbound-queue/src/lib.rs rename to bridges/snowbridge/pallets/inbound-queue/src/lib.rs diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue/src/mock.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/inbound-queue/src/mock.rs rename to bridges/snowbridge/pallets/inbound-queue/src/mock.rs diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/src/test.rs b/bridges/snowbridge/pallets/inbound-queue/src/test.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/inbound-queue/src/test.rs rename to bridges/snowbridge/pallets/inbound-queue/src/test.rs diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/src/weights.rs b/bridges/snowbridge/pallets/inbound-queue/src/weights.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/inbound-queue/src/weights.rs rename to bridges/snowbridge/pallets/inbound-queue/src/weights.rs diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue/Cargo.toml similarity index 62% rename from bridges/snowbridge/parachain/pallets/outbound-queue/Cargo.toml rename to bridges/snowbridge/pallets/outbound-queue/Cargo.toml index d6add7e348c..ae52fb3e5c4 100644 --- a/bridges/snowbridge/parachain/pallets/outbound-queue/Cargo.toml +++ b/bridges/snowbridge/pallets/outbound-queue/Cargo.toml @@ -20,26 +20,26 @@ codec = { version = "3.6.1", package = "parity-scale-codec", default-features = scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1", optional = true } -frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } -frame-support = { path = "../../../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../../../substrate/frame/system", default-features = false } -sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } -sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } -sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false } -sp-io = { path = "../../../../../substrate/primitives/io", default-features = false } -sp-arithmetic = { path = "../../../../../substrate/primitives/arithmetic", default-features = false } +frame-benchmarking = { path = "../../../../substrate/frame/benchmarking", default-features = false, optional = true } +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false } +sp-arithmetic = { path = "../../../../substrate/primitives/arithmetic", default-features = false } -bridge-hub-common = { path = "../../../../../cumulus/parachains/runtimes/bridge-hubs/common", default-features = false } +bridge-hub-common = { path = "../../../../cumulus/parachains/runtimes/bridge-hubs/common", default-features = false } snowbridge-core = { path = "../../primitives/core", default-features = false, features = ["serde"] } snowbridge-outbound-queue-merkle-tree = { path = "merkle-tree", default-features = false } ethabi = { package = "ethabi-decode", version = "1.0.0", default-features = false } -xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } +xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false } [dev-dependencies] -pallet-message-queue = { path = "../../../../../substrate/frame/message-queue", default-features = false } -sp-keyring = { path = "../../../../../substrate/primitives/keyring" } +pallet-message-queue = { path = "../../../../substrate/frame/message-queue", default-features = false } +sp-keyring = { path = "../../../../substrate/primitives/keyring" } hex-literal = { version = "0.4.1" } [features] diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/README.md b/bridges/snowbridge/pallets/outbound-queue/README.md similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/README.md rename to bridges/snowbridge/pallets/outbound-queue/README.md diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/merkle-tree/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue/merkle-tree/Cargo.toml similarity index 74% rename from bridges/snowbridge/parachain/pallets/outbound-queue/merkle-tree/Cargo.toml rename to bridges/snowbridge/pallets/outbound-queue/merkle-tree/Cargo.toml index 074b8031878..c185d5af706 100644 --- a/bridges/snowbridge/parachain/pallets/outbound-queue/merkle-tree/Cargo.toml +++ b/bridges/snowbridge/pallets/outbound-queue/merkle-tree/Cargo.toml @@ -18,15 +18,15 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { version = "3.1.5", package = "parity-scale-codec", default-features = false, features = ["derive"] } scale-info = { version = "2.7.0", default-features = false, features = ["derive"] } -sp-core = { path = "../../../../../../substrate/primitives/core", default-features = false } -sp-runtime = { path = "../../../../../../substrate/primitives/runtime", default-features = false } +sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } +sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false } [dev-dependencies] hex-literal = { version = "0.4.1" } env_logger = "0.9" hex = "0.4" array-bytes = "4.1" -sp-crypto-hashing = { path = "../../../../../../substrate/primitives/crypto/hashing" } +sp-crypto-hashing = { path = "../../../../../substrate/primitives/crypto/hashing" } [features] default = ["std"] diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/merkle-tree/README.md b/bridges/snowbridge/pallets/outbound-queue/merkle-tree/README.md similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/merkle-tree/README.md rename to bridges/snowbridge/pallets/outbound-queue/merkle-tree/README.md diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/merkle-tree/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue/merkle-tree/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/merkle-tree/src/lib.rs rename to bridges/snowbridge/pallets/outbound-queue/merkle-tree/src/lib.rs diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/runtime-api/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue/runtime-api/Cargo.toml similarity index 65% rename from bridges/snowbridge/parachain/pallets/outbound-queue/runtime-api/Cargo.toml rename to bridges/snowbridge/pallets/outbound-queue/runtime-api/Cargo.toml index a1c465957e1..347b3bae493 100644 --- a/bridges/snowbridge/parachain/pallets/outbound-queue/runtime-api/Cargo.toml +++ b/bridges/snowbridge/pallets/outbound-queue/runtime-api/Cargo.toml @@ -16,11 +16,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { version = "3.1.5", package = "parity-scale-codec", features = ["derive"], default-features = false } -sp-core = { path = "../../../../../../substrate/primitives/core", default-features = false } -sp-std = { path = "../../../../../../substrate/primitives/std", default-features = false } -sp-api = { path = "../../../../../../substrate/primitives/api", default-features = false } -frame-support = { path = "../../../../../../substrate/frame/support", default-features = false } -xcm = { package = "staging-xcm", path = "../../../../../../polkadot/xcm", default-features = false } +sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } +sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } +sp-api = { path = "../../../../../substrate/primitives/api", default-features = false } +frame-support = { path = "../../../../../substrate/frame/support", default-features = false } +xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } snowbridge-outbound-queue-merkle-tree = { path = "../merkle-tree", default-features = false } snowbridge-core = { path = "../../../primitives/core", default-features = false } diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/runtime-api/README.md b/bridges/snowbridge/pallets/outbound-queue/runtime-api/README.md similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/runtime-api/README.md rename to bridges/snowbridge/pallets/outbound-queue/runtime-api/README.md diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/runtime-api/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue/runtime-api/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/runtime-api/src/lib.rs rename to bridges/snowbridge/pallets/outbound-queue/runtime-api/src/lib.rs diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/api.rs b/bridges/snowbridge/pallets/outbound-queue/src/api.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/src/api.rs rename to bridges/snowbridge/pallets/outbound-queue/src/api.rs diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/benchmarking.rs b/bridges/snowbridge/pallets/outbound-queue/src/benchmarking.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/src/benchmarking.rs rename to bridges/snowbridge/pallets/outbound-queue/src/benchmarking.rs diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/src/lib.rs rename to bridges/snowbridge/pallets/outbound-queue/src/lib.rs diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/mock.rs b/bridges/snowbridge/pallets/outbound-queue/src/mock.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/src/mock.rs rename to bridges/snowbridge/pallets/outbound-queue/src/mock.rs diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/process_message_impl.rs b/bridges/snowbridge/pallets/outbound-queue/src/process_message_impl.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/src/process_message_impl.rs rename to bridges/snowbridge/pallets/outbound-queue/src/process_message_impl.rs diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/send_message_impl.rs b/bridges/snowbridge/pallets/outbound-queue/src/send_message_impl.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/src/send_message_impl.rs rename to bridges/snowbridge/pallets/outbound-queue/src/send_message_impl.rs diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/test.rs b/bridges/snowbridge/pallets/outbound-queue/src/test.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/src/test.rs rename to bridges/snowbridge/pallets/outbound-queue/src/test.rs diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/types.rs b/bridges/snowbridge/pallets/outbound-queue/src/types.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/src/types.rs rename to bridges/snowbridge/pallets/outbound-queue/src/types.rs diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/weights.rs b/bridges/snowbridge/pallets/outbound-queue/src/weights.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/src/weights.rs rename to bridges/snowbridge/pallets/outbound-queue/src/weights.rs diff --git a/bridges/snowbridge/parachain/pallets/system/Cargo.toml b/bridges/snowbridge/pallets/system/Cargo.toml similarity index 63% rename from bridges/snowbridge/parachain/pallets/system/Cargo.toml rename to bridges/snowbridge/pallets/system/Cargo.toml index da240344ba9..aa600511633 100644 --- a/bridges/snowbridge/parachain/pallets/system/Cargo.toml +++ b/bridges/snowbridge/pallets/system/Cargo.toml @@ -19,19 +19,19 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = "derive", ] } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } -frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } -frame-support = { path = "../../../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../../../substrate/frame/system", default-features = false } +frame-benchmarking = { path = "../../../../substrate/frame/benchmarking", default-features = false, optional = true } +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } log = { version = "0.4.20", default-features = false } -sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } -sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } -sp-io = { path = "../../../../../substrate/primitives/io", default-features = false } -sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false } +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } -xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } -xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false } -xcm-executor = { package = "staging-xcm-executor", path = "../../../../../polkadot/xcm/xcm-executor", default-features = false } +xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../../polkadot/xcm/xcm-builder", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../../polkadot/xcm/xcm-executor", default-features = false } ethabi = { package = "ethabi-decode", version = "1.0.0", default-features = false } snowbridge-core = { path = "../../primitives/core", default-features = false } @@ -39,10 +39,10 @@ snowbridge-core = { path = "../../primitives/core", default-features = false } [dev-dependencies] hex = "0.4.1" hex-literal = { version = "0.4.1" } -pallet-balances = { path = "../../../../../substrate/frame/balances" } -sp-keyring = { path = "../../../../../substrate/primitives/keyring" } -polkadot-primitives = { path = "../../../../../polkadot/primitives" } -pallet-message-queue = { path = "../../../../../substrate/frame/message-queue" } +pallet-balances = { path = "../../../../substrate/frame/balances" } +sp-keyring = { path = "../../../../substrate/primitives/keyring" } +polkadot-primitives = { path = "../../../../polkadot/primitives" } +pallet-message-queue = { path = "../../../../substrate/frame/message-queue" } snowbridge-pallet-outbound-queue = { path = "../outbound-queue" } [features] diff --git a/bridges/snowbridge/parachain/pallets/system/README.md b/bridges/snowbridge/pallets/system/README.md similarity index 100% rename from bridges/snowbridge/parachain/pallets/system/README.md rename to bridges/snowbridge/pallets/system/README.md diff --git a/bridges/snowbridge/parachain/pallets/system/runtime-api/Cargo.toml b/bridges/snowbridge/pallets/system/runtime-api/Cargo.toml similarity index 65% rename from bridges/snowbridge/parachain/pallets/system/runtime-api/Cargo.toml rename to bridges/snowbridge/pallets/system/runtime-api/Cargo.toml index 8f128e3a35d..355d2d29147 100644 --- a/bridges/snowbridge/parachain/pallets/system/runtime-api/Cargo.toml +++ b/bridges/snowbridge/pallets/system/runtime-api/Cargo.toml @@ -18,10 +18,10 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } -sp-core = { path = "../../../../../../substrate/primitives/core", default-features = false } -sp-std = { path = "../../../../../../substrate/primitives/std", default-features = false } -sp-api = { path = "../../../../../../substrate/primitives/api", default-features = false } -xcm = { package = "staging-xcm", path = "../../../../../../polkadot/xcm", default-features = false } +sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } +sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } +sp-api = { path = "../../../../../substrate/primitives/api", default-features = false } +xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } snowbridge-core = { path = "../../../primitives/core", default-features = false } [features] diff --git a/bridges/snowbridge/parachain/pallets/system/runtime-api/README.md b/bridges/snowbridge/pallets/system/runtime-api/README.md similarity index 100% rename from bridges/snowbridge/parachain/pallets/system/runtime-api/README.md rename to bridges/snowbridge/pallets/system/runtime-api/README.md diff --git a/bridges/snowbridge/parachain/pallets/system/runtime-api/src/lib.rs b/bridges/snowbridge/pallets/system/runtime-api/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/system/runtime-api/src/lib.rs rename to bridges/snowbridge/pallets/system/runtime-api/src/lib.rs diff --git a/bridges/snowbridge/parachain/pallets/system/src/api.rs b/bridges/snowbridge/pallets/system/src/api.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/system/src/api.rs rename to bridges/snowbridge/pallets/system/src/api.rs diff --git a/bridges/snowbridge/parachain/pallets/system/src/benchmarking.rs b/bridges/snowbridge/pallets/system/src/benchmarking.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/system/src/benchmarking.rs rename to bridges/snowbridge/pallets/system/src/benchmarking.rs diff --git a/bridges/snowbridge/parachain/pallets/system/src/lib.rs b/bridges/snowbridge/pallets/system/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/system/src/lib.rs rename to bridges/snowbridge/pallets/system/src/lib.rs diff --git a/bridges/snowbridge/parachain/pallets/system/src/migration.rs b/bridges/snowbridge/pallets/system/src/migration.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/system/src/migration.rs rename to bridges/snowbridge/pallets/system/src/migration.rs diff --git a/bridges/snowbridge/parachain/pallets/system/src/mock.rs b/bridges/snowbridge/pallets/system/src/mock.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/system/src/mock.rs rename to bridges/snowbridge/pallets/system/src/mock.rs diff --git a/bridges/snowbridge/parachain/pallets/system/src/tests.rs b/bridges/snowbridge/pallets/system/src/tests.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/system/src/tests.rs rename to bridges/snowbridge/pallets/system/src/tests.rs diff --git a/bridges/snowbridge/parachain/pallets/system/src/weights.rs b/bridges/snowbridge/pallets/system/src/weights.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/system/src/weights.rs rename to bridges/snowbridge/pallets/system/src/weights.rs diff --git a/bridges/snowbridge/parachain/LICENSE b/bridges/snowbridge/parachain/LICENSE deleted file mode 100644 index 261eeb9e9f8..00000000000 --- a/bridges/snowbridge/parachain/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/bridges/snowbridge/parachain/README.md b/bridges/snowbridge/parachain/README.md deleted file mode 100644 index a38910da316..00000000000 --- a/bridges/snowbridge/parachain/README.md +++ /dev/null @@ -1,127 +0,0 @@ -# Snowbridge · -[![codecov](https://codecov.io/gh/Snowfork/snowbridge/branch/main/graph/badge.svg?token=9hvgSws4rN)] -(https://codecov.io/gh/Snowfork/snowbridge) -![GitHub](https://img.shields.io/github/license/Snowfork/snowbridge) - -Snowbridge is a trustless bridge between Polkadot and Ethereum. For documentation, visit https://docs.snowbridge.network. - -## Components - -### Parachain - -Polkadot parachain and our pallets. See [parachain/README.md](https://github.com/Snowfork/snowbridge/blob/main/parachain/README.md). - -### Contracts - -Ethereum contracts and unit tests. See [contracts/README.md](https://github.com/Snowfork/snowbridge/blob/main/contracts/README.md) - -### Relayer - -Off-chain relayer services for relaying messages between Polkadot and Ethereum. See -[relayer/README.md](https://github.com/Snowfork/snowbridge/blob/main/relayer/README.md) - -### Local Testnet - -Scripts to provision a local testnet, running the above services to bridge between local deployments of Polkadot and -Ethereum. See [web/packages/test/README.md](https://github.com/Snowfork/snowbridge/blob/main/web/packages/test/README.md). - -### Smoke Tests - -Integration tests for our local testnet. See [smoketest/README.md](https://github.com/Snowfork/snowbridge/blob/main/smoketest/README.md). - -## Development - -We use the Nix package manager to provide a reproducible and maintainable developer environment. - -After [installing nix](https://nixos.org/download.html) Nix, enable [flakes](https://nixos.wiki/wiki/Flakes): - -```sh -mkdir -p ~/.config/nix -echo 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf -``` - -Then activate a developer shell in the root of our repo, where -[`flake.nix`](https://github.com/Snowfork/snowbridge/blob/main/flake.nix) is located: - -```sh -nix develop -``` - -Also make sure to run this initialization script once: -```sh -scripts/init.sh -``` - -### Support for code editors - -To ensure your code editor (such as VS Code) can execute tools in the nix shell, startup your editor within the -interactive shell. - -Example for VS Code: - -```sh -nix develop -code . -``` - -### Custom shells - -The developer shell is bash by default. To preserve your existing shell: - -```sh -nix develop --command $SHELL -``` - -### Automatic developer shells - -To automatically enter the developer shell whenever you open the project, install -[`direnv`](https://direnv.net/docs/installation.html) and use the template `.envrc`: - -```sh -cp .envrc.example .envrc -direnv allow -``` - -### Upgrading the Rust toolchain - -Sometimes we would like to upgrade rust toolchain. First update `parachain/rust-toolchain.toml` as required and then -update `flake.lock` running -```sh -nix flake lock --update-input rust-overlay -``` - -## Troubleshooting - -Check the contents of all `.envrc` files. - -Remove untracked files: -```sh -git clean -idx -``` - -Ensure that the current Rust toolchain is the one selected in `scripts/init.sh`. - -Ensure submodules are up-to-date: -```sh -git submodule update -``` - -Check untracked files & directories: -```sh -git clean -ndx | awk '{print $3}' -``` -After removing `node_modules` directories (eg. with `git clean above`), clear the pnpm cache: -```sh -pnpm store prune -``` - -Check Nix config in `~/.config/nix/nix.conf`. - -Run a pure developer shell (note that this removes access to your local tools): -```sh -nix develop -i --pure-eval -``` - -## Security - -The security policy and procedures can be found in SECURITY.md. diff --git a/bridges/snowbridge/parachain/primitives/ethereum/.cargo/config.toml b/bridges/snowbridge/parachain/primitives/ethereum/.cargo/config.toml deleted file mode 100644 index 4ec2f3b8620..00000000000 --- a/bridges/snowbridge/parachain/primitives/ethereum/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[target.wasm32-unknown-unknown] -runner = 'wasm-bindgen-test-runner' diff --git a/bridges/snowbridge/parachain/primitives/beacon/Cargo.toml b/bridges/snowbridge/primitives/beacon/Cargo.toml similarity index 72% rename from bridges/snowbridge/parachain/primitives/beacon/Cargo.toml rename to bridges/snowbridge/primitives/beacon/Cargo.toml index 3c2b3fcd065..b59e5191b41 100644 --- a/bridges/snowbridge/parachain/primitives/beacon/Cargo.toml +++ b/bridges/snowbridge/primitives/beacon/Cargo.toml @@ -18,12 +18,12 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } rlp = { version = "0.5", default-features = false } -frame-support = { path = "../../../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../../../substrate/frame/system", default-features = false } -sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false } -sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } -sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } -sp-io = { path = "../../../../../substrate/primitives/io", default-features = false } +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false } ssz_rs = { version = "0.9.0", default-features = false } ssz_rs_derive = { version = "0.9.0", default-features = false } diff --git a/bridges/snowbridge/parachain/primitives/beacon/README.md b/bridges/snowbridge/primitives/beacon/README.md similarity index 100% rename from bridges/snowbridge/parachain/primitives/beacon/README.md rename to bridges/snowbridge/primitives/beacon/README.md diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/bits.rs b/bridges/snowbridge/primitives/beacon/src/bits.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/beacon/src/bits.rs rename to bridges/snowbridge/primitives/beacon/src/bits.rs diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/bls.rs b/bridges/snowbridge/primitives/beacon/src/bls.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/beacon/src/bls.rs rename to bridges/snowbridge/primitives/beacon/src/bls.rs diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/config.rs b/bridges/snowbridge/primitives/beacon/src/config.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/beacon/src/config.rs rename to bridges/snowbridge/primitives/beacon/src/config.rs diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/lib.rs b/bridges/snowbridge/primitives/beacon/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/beacon/src/lib.rs rename to bridges/snowbridge/primitives/beacon/src/lib.rs diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/merkle_proof.rs b/bridges/snowbridge/primitives/beacon/src/merkle_proof.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/beacon/src/merkle_proof.rs rename to bridges/snowbridge/primitives/beacon/src/merkle_proof.rs diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/receipt.rs b/bridges/snowbridge/primitives/beacon/src/receipt.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/beacon/src/receipt.rs rename to bridges/snowbridge/primitives/beacon/src/receipt.rs diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/serde_utils.rs b/bridges/snowbridge/primitives/beacon/src/serde_utils.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/beacon/src/serde_utils.rs rename to bridges/snowbridge/primitives/beacon/src/serde_utils.rs diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/ssz.rs b/bridges/snowbridge/primitives/beacon/src/ssz.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/beacon/src/ssz.rs rename to bridges/snowbridge/primitives/beacon/src/ssz.rs diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/types.rs b/bridges/snowbridge/primitives/beacon/src/types.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/beacon/src/types.rs rename to bridges/snowbridge/primitives/beacon/src/types.rs diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/updates.rs b/bridges/snowbridge/primitives/beacon/src/updates.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/beacon/src/updates.rs rename to bridges/snowbridge/primitives/beacon/src/updates.rs diff --git a/bridges/snowbridge/parachain/primitives/core/Cargo.toml b/bridges/snowbridge/primitives/core/Cargo.toml similarity index 62% rename from bridges/snowbridge/parachain/primitives/core/Cargo.toml rename to bridges/snowbridge/primitives/core/Cargo.toml index 4e56495c1e6..f735d01a848 100644 --- a/bridges/snowbridge/parachain/primitives/core/Cargo.toml +++ b/bridges/snowbridge/primitives/core/Cargo.toml @@ -17,17 +17,17 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1" } -polkadot-parachain-primitives = { path = "../../../../../polkadot/parachain", default-features = false } -xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } -xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false } +polkadot-parachain-primitives = { path = "../../../../polkadot/parachain", default-features = false } +xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../../polkadot/xcm/xcm-builder", default-features = false } -frame-support = { path = "../../../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../../../substrate/frame/system", default-features = false } -sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false } -sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } -sp-io = { path = "../../../../../substrate/primitives/io", default-features = false } -sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } -sp-arithmetic = { path = "../../../../../substrate/primitives/arithmetic", default-features = false } +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false } +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } +sp-arithmetic = { path = "../../../../substrate/primitives/arithmetic", default-features = false } snowbridge-beacon-primitives = { path = "../beacon", default-features = false } diff --git a/bridges/snowbridge/parachain/primitives/core/README.md b/bridges/snowbridge/primitives/core/README.md similarity index 100% rename from bridges/snowbridge/parachain/primitives/core/README.md rename to bridges/snowbridge/primitives/core/README.md diff --git a/bridges/snowbridge/parachain/primitives/core/src/inbound.rs b/bridges/snowbridge/primitives/core/src/inbound.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/core/src/inbound.rs rename to bridges/snowbridge/primitives/core/src/inbound.rs diff --git a/bridges/snowbridge/parachain/primitives/core/src/lib.rs b/bridges/snowbridge/primitives/core/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/core/src/lib.rs rename to bridges/snowbridge/primitives/core/src/lib.rs diff --git a/bridges/snowbridge/parachain/primitives/core/src/operating_mode.rs b/bridges/snowbridge/primitives/core/src/operating_mode.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/core/src/operating_mode.rs rename to bridges/snowbridge/primitives/core/src/operating_mode.rs diff --git a/bridges/snowbridge/parachain/primitives/core/src/outbound.rs b/bridges/snowbridge/primitives/core/src/outbound.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/core/src/outbound.rs rename to bridges/snowbridge/primitives/core/src/outbound.rs diff --git a/bridges/snowbridge/parachain/primitives/core/src/pricing.rs b/bridges/snowbridge/primitives/core/src/pricing.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/core/src/pricing.rs rename to bridges/snowbridge/primitives/core/src/pricing.rs diff --git a/bridges/snowbridge/parachain/primitives/core/src/ringbuffer.rs b/bridges/snowbridge/primitives/core/src/ringbuffer.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/core/src/ringbuffer.rs rename to bridges/snowbridge/primitives/core/src/ringbuffer.rs diff --git a/bridges/snowbridge/parachain/primitives/core/src/tests.rs b/bridges/snowbridge/primitives/core/src/tests.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/core/src/tests.rs rename to bridges/snowbridge/primitives/core/src/tests.rs diff --git a/bridges/snowbridge/parachain/primitives/core/tests/fixtures/packet.scale b/bridges/snowbridge/primitives/core/tests/fixtures/packet.scale similarity index 100% rename from bridges/snowbridge/parachain/primitives/core/tests/fixtures/packet.scale rename to bridges/snowbridge/primitives/core/tests/fixtures/packet.scale diff --git a/bridges/snowbridge/parachain/primitives/core/tests/mod.rs b/bridges/snowbridge/primitives/core/tests/mod.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/core/tests/mod.rs rename to bridges/snowbridge/primitives/core/tests/mod.rs diff --git a/bridges/snowbridge/parachain/primitives/ethereum/Cargo.toml b/bridges/snowbridge/primitives/ethereum/Cargo.toml similarity index 81% rename from bridges/snowbridge/parachain/primitives/ethereum/Cargo.toml rename to bridges/snowbridge/primitives/ethereum/Cargo.toml index c419f3f47f7..047ebdd5669 100644 --- a/bridges/snowbridge/parachain/primitives/ethereum/Cargo.toml +++ b/bridges/snowbridge/primitives/ethereum/Cargo.toml @@ -23,10 +23,10 @@ hex-literal = { version = "0.4.1", default-features = false } parity-bytes = { version = "0.1.2", default-features = false } rlp = { version = "0.5.2", default-features = false } -sp-io = { path = "../../../../../substrate/primitives/io", default-features = false } -sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } -sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } -sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } ethabi = { package = "ethabi-decode", version = "1.0.0", default-features = false } diff --git a/bridges/snowbridge/parachain/primitives/ethereum/README.md b/bridges/snowbridge/primitives/ethereum/README.md similarity index 100% rename from bridges/snowbridge/parachain/primitives/ethereum/README.md rename to bridges/snowbridge/primitives/ethereum/README.md diff --git a/bridges/snowbridge/parachain/primitives/ethereum/src/header.rs b/bridges/snowbridge/primitives/ethereum/src/header.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/ethereum/src/header.rs rename to bridges/snowbridge/primitives/ethereum/src/header.rs diff --git a/bridges/snowbridge/parachain/primitives/ethereum/src/lib.rs b/bridges/snowbridge/primitives/ethereum/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/ethereum/src/lib.rs rename to bridges/snowbridge/primitives/ethereum/src/lib.rs diff --git a/bridges/snowbridge/parachain/primitives/ethereum/src/log.rs b/bridges/snowbridge/primitives/ethereum/src/log.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/ethereum/src/log.rs rename to bridges/snowbridge/primitives/ethereum/src/log.rs diff --git a/bridges/snowbridge/parachain/primitives/ethereum/src/mpt.rs b/bridges/snowbridge/primitives/ethereum/src/mpt.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/ethereum/src/mpt.rs rename to bridges/snowbridge/primitives/ethereum/src/mpt.rs diff --git a/bridges/snowbridge/parachain/primitives/ethereum/src/receipt.rs b/bridges/snowbridge/primitives/ethereum/src/receipt.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/ethereum/src/receipt.rs rename to bridges/snowbridge/primitives/ethereum/src/receipt.rs diff --git a/bridges/snowbridge/parachain/primitives/router/Cargo.toml b/bridges/snowbridge/primitives/router/Cargo.toml similarity index 66% rename from bridges/snowbridge/parachain/primitives/router/Cargo.toml rename to bridges/snowbridge/primitives/router/Cargo.toml index dcb09499c55..712c60c2148 100644 --- a/bridges/snowbridge/parachain/primitives/router/Cargo.toml +++ b/bridges/snowbridge/primitives/router/Cargo.toml @@ -17,16 +17,16 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } log = { version = "0.4.20", default-features = false } -frame-support = { path = "../../../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../../../substrate/frame/system", default-features = false } -sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } -sp-io = { path = "../../../../../substrate/primitives/io", default-features = false } -sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false } -sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } -xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } -xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false } -xcm-executor = { package = "staging-xcm-executor", path = "../../../../../polkadot/xcm/xcm-executor", default-features = false } +xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../../polkadot/xcm/xcm-builder", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../../polkadot/xcm/xcm-executor", default-features = false } snowbridge-core = { path = "../core", default-features = false } diff --git a/bridges/snowbridge/parachain/primitives/router/README.md b/bridges/snowbridge/primitives/router/README.md similarity index 100% rename from bridges/snowbridge/parachain/primitives/router/README.md rename to bridges/snowbridge/primitives/router/README.md diff --git a/bridges/snowbridge/parachain/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/router/src/inbound/mod.rs rename to bridges/snowbridge/primitives/router/src/inbound/mod.rs diff --git a/bridges/snowbridge/parachain/primitives/router/src/inbound/tests.rs b/bridges/snowbridge/primitives/router/src/inbound/tests.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/router/src/inbound/tests.rs rename to bridges/snowbridge/primitives/router/src/inbound/tests.rs diff --git a/bridges/snowbridge/parachain/primitives/router/src/lib.rs b/bridges/snowbridge/primitives/router/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/router/src/lib.rs rename to bridges/snowbridge/primitives/router/src/lib.rs diff --git a/bridges/snowbridge/parachain/primitives/router/src/outbound/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/mod.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/router/src/outbound/mod.rs rename to bridges/snowbridge/primitives/router/src/outbound/mod.rs diff --git a/bridges/snowbridge/parachain/primitives/router/src/outbound/tests.rs b/bridges/snowbridge/primitives/router/src/outbound/tests.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/router/src/outbound/tests.rs rename to bridges/snowbridge/primitives/router/src/outbound/tests.rs diff --git a/bridges/snowbridge/parachain/runtime/runtime-common/Cargo.toml b/bridges/snowbridge/runtime/runtime-common/Cargo.toml similarity index 65% rename from bridges/snowbridge/parachain/runtime/runtime-common/Cargo.toml rename to bridges/snowbridge/runtime/runtime-common/Cargo.toml index 1fdb10beeb9..f5b44b25585 100644 --- a/bridges/snowbridge/parachain/runtime/runtime-common/Cargo.toml +++ b/bridges/snowbridge/runtime/runtime-common/Cargo.toml @@ -14,13 +14,13 @@ workspace = true [dependencies] log = { version = "0.4.20", default-features = false } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -frame-support = { path = "../../../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../../../substrate/frame/system", default-features = false } -sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } -sp-arithmetic = { path = "../../../../../substrate/primitives/arithmetic", default-features = false } -xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } -xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false } -xcm-executor = { package = "staging-xcm-executor", path = "../../../../../polkadot/xcm/xcm-executor", default-features = false } +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-arithmetic = { path = "../../../../substrate/primitives/arithmetic", default-features = false } +xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../../polkadot/xcm/xcm-builder", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../../polkadot/xcm/xcm-executor", default-features = false } snowbridge-core = { path = "../../primitives/core", default-features = false } diff --git a/bridges/snowbridge/parachain/runtime/runtime-common/README.md b/bridges/snowbridge/runtime/runtime-common/README.md similarity index 100% rename from bridges/snowbridge/parachain/runtime/runtime-common/README.md rename to bridges/snowbridge/runtime/runtime-common/README.md diff --git a/bridges/snowbridge/parachain/runtime/runtime-common/src/lib.rs b/bridges/snowbridge/runtime/runtime-common/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/runtime/runtime-common/src/lib.rs rename to bridges/snowbridge/runtime/runtime-common/src/lib.rs diff --git a/bridges/snowbridge/parachain/runtime/runtime-common/src/tests.rs b/bridges/snowbridge/runtime/runtime-common/src/tests.rs similarity index 100% rename from bridges/snowbridge/parachain/runtime/runtime-common/src/tests.rs rename to bridges/snowbridge/runtime/runtime-common/src/tests.rs diff --git a/bridges/snowbridge/parachain/runtime/test-common/Cargo.toml b/bridges/snowbridge/runtime/test-common/Cargo.toml similarity index 51% rename from bridges/snowbridge/parachain/runtime/test-common/Cargo.toml rename to bridges/snowbridge/runtime/test-common/Cargo.toml index 8c5bb153c3b..a2994e61891 100644 --- a/bridges/snowbridge/parachain/runtime/test-common/Cargo.toml +++ b/bridges/snowbridge/runtime/test-common/Cargo.toml @@ -19,62 +19,62 @@ serde = { version = "1.0.195", optional = true, features = ["derive"] } smallvec = "1.11.0" # Substrate -frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } -frame-executive = { path = "../../../../../substrate/frame/executive", default-features = false } -frame-support = { path = "../../../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../../../substrate/frame/system", default-features = false } -frame-system-benchmarking = { path = "../../../../../substrate/frame/system/benchmarking", default-features = false, optional = true } -frame-system-rpc-runtime-api = { path = "../../../../../substrate/frame/system/rpc/runtime-api", default-features = false } -frame-try-runtime = { path = "../../../../../substrate/frame/try-runtime", default-features = false, optional = true } -pallet-aura = { path = "../../../../../substrate/frame/aura", default-features = false } -pallet-authorship = { path = "../../../../../substrate/frame/authorship", default-features = false } -pallet-balances = { path = "../../../../../substrate/frame/balances", default-features = false } -pallet-session = { path = "../../../../../substrate/frame/session", default-features = false } -pallet-multisig = { path = "../../../../../substrate/frame/multisig", default-features = false } -pallet-message-queue = { path = "../../../../../substrate/frame/message-queue", default-features = false } -pallet-timestamp = { path = "../../../../../substrate/frame/timestamp", default-features = false } -pallet-transaction-payment = { path = "../../../../../substrate/frame/transaction-payment", default-features = false } -pallet-transaction-payment-rpc-runtime-api = { path = "../../../../../substrate/frame/transaction-payment/rpc/runtime-api", default-features = false } -pallet-utility = { path = "../../../../../substrate/frame/utility", default-features = false } -sp-api = { path = "../../../../../substrate/primitives/api", default-features = false } -sp-block-builder = { path = "../../../../../substrate/primitives/block-builder", default-features = false } -sp-consensus-aura = { path = "../../../../../substrate/primitives/consensus/aura", default-features = false } -sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } -sp-genesis-builder = { path = "../../../../../substrate/primitives/genesis-builder", default-features = false } -sp-inherents = { path = "../../../../../substrate/primitives/inherents", default-features = false } -sp-io = { path = "../../../../../substrate/primitives/io", default-features = false } -sp-keyring = { path = "../../../../../substrate/primitives/keyring" } -sp-offchain = { path = "../../../../../substrate/primitives/offchain", default-features = false } -sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false } -sp-session = { path = "../../../../../substrate/primitives/session", default-features = false } -sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } -sp-storage = { path = "../../../../../substrate/primitives/storage", default-features = false } -sp-transaction-pool = { path = "../../../../../substrate/primitives/transaction-pool", default-features = false } -sp-version = { path = "../../../../../substrate/primitives/version", default-features = false } +frame-benchmarking = { path = "../../../../substrate/frame/benchmarking", default-features = false, optional = true } +frame-executive = { path = "../../../../substrate/frame/executive", default-features = false } +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } +frame-system-benchmarking = { path = "../../../../substrate/frame/system/benchmarking", default-features = false, optional = true } +frame-system-rpc-runtime-api = { path = "../../../../substrate/frame/system/rpc/runtime-api", default-features = false } +frame-try-runtime = { path = "../../../../substrate/frame/try-runtime", default-features = false, optional = true } +pallet-aura = { path = "../../../../substrate/frame/aura", default-features = false } +pallet-authorship = { path = "../../../../substrate/frame/authorship", default-features = false } +pallet-balances = { path = "../../../../substrate/frame/balances", default-features = false } +pallet-session = { path = "../../../../substrate/frame/session", default-features = false } +pallet-multisig = { path = "../../../../substrate/frame/multisig", default-features = false } +pallet-message-queue = { path = "../../../../substrate/frame/message-queue", default-features = false } +pallet-timestamp = { path = "../../../../substrate/frame/timestamp", default-features = false } +pallet-transaction-payment = { path = "../../../../substrate/frame/transaction-payment", default-features = false } +pallet-transaction-payment-rpc-runtime-api = { path = "../../../../substrate/frame/transaction-payment/rpc/runtime-api", default-features = false } +pallet-utility = { path = "../../../../substrate/frame/utility", default-features = false } +sp-api = { path = "../../../../substrate/primitives/api", default-features = false } +sp-block-builder = { path = "../../../../substrate/primitives/block-builder", default-features = false } +sp-consensus-aura = { path = "../../../../substrate/primitives/consensus/aura", default-features = false } +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } +sp-genesis-builder = { path = "../../../../substrate/primitives/genesis-builder", default-features = false } +sp-inherents = { path = "../../../../substrate/primitives/inherents", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false } +sp-keyring = { path = "../../../../substrate/primitives/keyring" } +sp-offchain = { path = "../../../../substrate/primitives/offchain", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } +sp-session = { path = "../../../../substrate/primitives/session", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-storage = { path = "../../../../substrate/primitives/storage", default-features = false } +sp-transaction-pool = { path = "../../../../substrate/primitives/transaction-pool", default-features = false } +sp-version = { path = "../../../../substrate/primitives/version", default-features = false } # Polkadot -pallet-xcm = { path = "../../../../../polkadot/xcm/pallet-xcm", default-features = false } -pallet-xcm-benchmarks = { path = "../../../../../polkadot/xcm/pallet-xcm-benchmarks", default-features = false, optional = true } -polkadot-core-primitives = { path = "../../../../../polkadot/core-primitives", default-features = false } -polkadot-parachain-primitives = { path = "../../../../../polkadot/parachain", default-features = false } -polkadot-runtime-common = { path = "../../../../../polkadot/runtime/common", default-features = false } -xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } -xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false } -xcm-executor = { package = "staging-xcm-executor", path = "../../../../../polkadot/xcm/xcm-executor", default-features = false } +pallet-xcm = { path = "../../../../polkadot/xcm/pallet-xcm", default-features = false } +pallet-xcm-benchmarks = { path = "../../../../polkadot/xcm/pallet-xcm-benchmarks", default-features = false, optional = true } +polkadot-core-primitives = { path = "../../../../polkadot/core-primitives", default-features = false } +polkadot-parachain-primitives = { path = "../../../../polkadot/parachain", default-features = false } +polkadot-runtime-common = { path = "../../../../polkadot/runtime/common", default-features = false } +xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../../polkadot/xcm/xcm-builder", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../../polkadot/xcm/xcm-executor", default-features = false } # Cumulus -cumulus-pallet-aura-ext = { path = "../../../../../cumulus/pallets/aura-ext", default-features = false } -cumulus-pallet-parachain-system = { path = "../../../../../cumulus/pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook"] } -cumulus-pallet-session-benchmarking = { path = "../../../../../cumulus/pallets/session-benchmarking", default-features = false } -cumulus-pallet-xcm = { path = "../../../../../cumulus/pallets/xcm", default-features = false } -cumulus-pallet-xcmp-queue = { path = "../../../../../cumulus/pallets/xcmp-queue", default-features = false, features = ["bridging"] } -cumulus-primitives-core = { path = "../../../../../cumulus/primitives/core", default-features = false } -cumulus-primitives-utility = { path = "../../../../../cumulus/primitives/utility", default-features = false } -pallet-collator-selection = { path = "../../../../../cumulus/pallets/collator-selection", default-features = false } -parachain-info = { package = "staging-parachain-info", path = "../../../../../cumulus/parachains/pallets/parachain-info", default-features = false } -parachains-common = { path = "../../../../../cumulus/parachains/common", default-features = false } -parachains-runtimes-test-utils = { path = "../../../../../cumulus/parachains/runtimes/test-utils", default-features = false } -assets-common = { path = "../../../../../cumulus/parachains/runtimes/assets/common", default-features = false } +cumulus-pallet-aura-ext = { path = "../../../../cumulus/pallets/aura-ext", default-features = false } +cumulus-pallet-parachain-system = { path = "../../../../cumulus/pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook"] } +cumulus-pallet-session-benchmarking = { path = "../../../../cumulus/pallets/session-benchmarking", default-features = false } +cumulus-pallet-xcm = { path = "../../../../cumulus/pallets/xcm", default-features = false } +cumulus-pallet-xcmp-queue = { path = "../../../../cumulus/pallets/xcmp-queue", default-features = false, features = ["bridging"] } +cumulus-primitives-core = { path = "../../../../cumulus/primitives/core", default-features = false } +cumulus-primitives-utility = { path = "../../../../cumulus/primitives/utility", default-features = false } +pallet-collator-selection = { path = "../../../../cumulus/pallets/collator-selection", default-features = false } +parachain-info = { package = "staging-parachain-info", path = "../../../../cumulus/parachains/pallets/parachain-info", default-features = false } +parachains-common = { path = "../../../../cumulus/parachains/common", default-features = false } +parachains-runtimes-test-utils = { path = "../../../../cumulus/parachains/runtimes/test-utils", default-features = false } +assets-common = { path = "../../../../cumulus/parachains/runtimes/assets/common", default-features = false } # Ethereum Bridge (Snowbridge) snowbridge-core = { path = "../../primitives/core", default-features = false } @@ -90,9 +90,9 @@ snowbridge-system-runtime-api = { path = "../../pallets/system/runtime-api", def [dev-dependencies] static_assertions = "1.1" -bridge-hub-test-utils = { path = "../../../../../cumulus/parachains/runtimes/bridge-hubs/test-utils" } -bridge-runtime-common = { path = "../../../../bin/runtime-common", features = ["integrity-test"] } -sp-keyring = { path = "../../../../../substrate/primitives/keyring" } +bridge-hub-test-utils = { path = "../../../../cumulus/parachains/runtimes/bridge-hubs/test-utils" } +bridge-runtime-common = { path = "../../../bin/runtime-common", features = ["integrity-test"] } +sp-keyring = { path = "../../../../substrate/primitives/keyring" } [features] default = ["std"] diff --git a/bridges/snowbridge/parachain/runtime/test-common/README.md b/bridges/snowbridge/runtime/test-common/README.md similarity index 100% rename from bridges/snowbridge/parachain/runtime/test-common/README.md rename to bridges/snowbridge/runtime/test-common/README.md diff --git a/bridges/snowbridge/parachain/runtime/test-common/src/lib.rs b/bridges/snowbridge/runtime/test-common/src/lib.rs similarity index 99% rename from bridges/snowbridge/parachain/runtime/test-common/src/lib.rs rename to bridges/snowbridge/runtime/test-common/src/lib.rs index 50e727f886d..c9bbce98e57 100644 --- a/bridges/snowbridge/parachain/runtime/test-common/src/lib.rs +++ b/bridges/snowbridge/runtime/test-common/src/lib.rs @@ -184,7 +184,7 @@ pub fn send_transfer_token_message_success( >::on_finalize(next_block_number); let included_head = >::finalize(); - let origin: ParaId = (assethub_parachain_id as u32).into(); + let origin: ParaId = assethub_parachain_id.into(); let channel_id: ChannelId = origin.into(); let nonce = snowbridge_pallet_outbound_queue::Nonce::::try_get(channel_id); diff --git a/bridges/snowbridge/parachain/scripts/benchmark.sh b/bridges/snowbridge/scripts/benchmark.sh similarity index 100% rename from bridges/snowbridge/parachain/scripts/benchmark.sh rename to bridges/snowbridge/scripts/benchmark.sh diff --git a/bridges/snowbridge/parachain/scripts/hexliteral.sh b/bridges/snowbridge/scripts/hexliteral.sh similarity index 100% rename from bridges/snowbridge/parachain/scripts/hexliteral.sh rename to bridges/snowbridge/scripts/hexliteral.sh diff --git a/bridges/snowbridge/parachain/scripts/init.sh b/bridges/snowbridge/scripts/init.sh similarity index 100% rename from bridges/snowbridge/parachain/scripts/init.sh rename to bridges/snowbridge/scripts/init.sh diff --git a/bridges/snowbridge/parachain/scripts/make-build-config.sh b/bridges/snowbridge/scripts/make-build-config.sh similarity index 100% rename from bridges/snowbridge/parachain/scripts/make-build-config.sh rename to bridges/snowbridge/scripts/make-build-config.sh diff --git a/bridges/snowbridge/parachain/scripts/verify-pallets-build.sh b/bridges/snowbridge/scripts/verify-pallets-build.sh similarity index 100% rename from bridges/snowbridge/parachain/scripts/verify-pallets-build.sh rename to bridges/snowbridge/scripts/verify-pallets-build.sh diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/Cargo.toml index ddffe7686a2..89f0d2a9ca6 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/Cargo.toml @@ -44,9 +44,9 @@ rococo-system-emulated-network = { path = "../../../networks/rococo-system" } asset-hub-rococo-runtime = { path = "../../../../../runtimes/assets/asset-hub-rococo", default-features = false } # Snowbridge -snowbridge-core = { path = "../../../../../../../bridges/snowbridge/parachain/primitives/core", default-features = false } -snowbridge-router-primitives = { path = "../../../../../../../bridges/snowbridge/parachain/primitives/router", default-features = false } -snowbridge-pallet-system = { path = "../../../../../../../bridges/snowbridge/parachain/pallets/system", default-features = false } -snowbridge-pallet-outbound-queue = { path = "../../../../../../../bridges/snowbridge/parachain/pallets/outbound-queue", default-features = false } -snowbridge-pallet-inbound-queue = { path = "../../../../../../../bridges/snowbridge/parachain/pallets/inbound-queue", default-features = false } -snowbridge-pallet-inbound-queue-fixtures = { path = "../../../../../../../bridges/snowbridge/parachain/pallets/inbound-queue/fixtures" } +snowbridge-core = { path = "../../../../../../../bridges/snowbridge/primitives/core", default-features = false } +snowbridge-router-primitives = { path = "../../../../../../../bridges/snowbridge/primitives/router", default-features = false } +snowbridge-pallet-system = { path = "../../../../../../../bridges/snowbridge/pallets/system", default-features = false } +snowbridge-pallet-outbound-queue = { path = "../../../../../../../bridges/snowbridge/pallets/outbound-queue", default-features = false } +snowbridge-pallet-inbound-queue = { path = "../../../../../../../bridges/snowbridge/pallets/inbound-queue", default-features = false } +snowbridge-pallet-inbound-queue-fixtures = { path = "../../../../../../../bridges/snowbridge/pallets/inbound-queue/fixtures" } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml index cb6445a6316..d1b302dc6d4 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml @@ -90,7 +90,7 @@ bp-asset-hub-rococo = { path = "../../../../../bridges/primitives/chain-asset-hu bp-asset-hub-westend = { path = "../../../../../bridges/primitives/chain-asset-hub-westend", default-features = false } bp-bridge-hub-rococo = { path = "../../../../../bridges/primitives/chain-bridge-hub-rococo", default-features = false } bp-bridge-hub-westend = { path = "../../../../../bridges/primitives/chain-bridge-hub-westend", default-features = false } -snowbridge-router-primitives = { path = "../../../../../bridges/snowbridge/parachain/primitives/router", default-features = false } +snowbridge-router-primitives = { path = "../../../../../bridges/snowbridge/primitives/router", default-features = false } [dev-dependencies] asset-test-utils = { path = "../test-utils" } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index 0b750d30a5f..920e7b1571c 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -107,16 +107,16 @@ pallet-xcm-bridge-hub = { path = "../../../../../bridges/modules/xcm-bridge-hub" bridge-runtime-common = { path = "../../../../../bridges/bin/runtime-common", default-features = false } # Ethereum Bridge (Snowbridge) -snowbridge-beacon-primitives = { path = "../../../../../bridges/snowbridge/parachain/primitives/beacon", default-features = false } -snowbridge-pallet-system = { path = "../../../../../bridges/snowbridge/parachain/pallets/system", default-features = false } -snowbridge-system-runtime-api = { path = "../../../../../bridges/snowbridge/parachain/pallets/system/runtime-api", default-features = false } -snowbridge-core = { path = "../../../../../bridges/snowbridge/parachain/primitives/core", default-features = false } -snowbridge-pallet-ethereum-client = { path = "../../../../../bridges/snowbridge/parachain/pallets/ethereum-client", default-features = false } -snowbridge-pallet-inbound-queue = { path = "../../../../../bridges/snowbridge/parachain/pallets/inbound-queue", default-features = false } -snowbridge-pallet-outbound-queue = { path = "../../../../../bridges/snowbridge/parachain/pallets/outbound-queue", default-features = false } -snowbridge-outbound-queue-runtime-api = { path = "../../../../../bridges/snowbridge/parachain/pallets/outbound-queue/runtime-api", default-features = false } -snowbridge-router-primitives = { path = "../../../../../bridges/snowbridge/parachain/primitives/router", default-features = false } -snowbridge-runtime-common = { path = "../../../../../bridges/snowbridge/parachain/runtime/runtime-common", default-features = false } +snowbridge-beacon-primitives = { path = "../../../../../bridges/snowbridge/primitives/beacon", default-features = false } +snowbridge-pallet-system = { path = "../../../../../bridges/snowbridge/pallets/system", default-features = false } +snowbridge-system-runtime-api = { path = "../../../../../bridges/snowbridge/pallets/system/runtime-api", default-features = false } +snowbridge-core = { path = "../../../../../bridges/snowbridge/primitives/core", default-features = false } +snowbridge-pallet-ethereum-client = { path = "../../../../../bridges/snowbridge/pallets/ethereum-client", default-features = false } +snowbridge-pallet-inbound-queue = { path = "../../../../../bridges/snowbridge/pallets/inbound-queue", default-features = false } +snowbridge-pallet-outbound-queue = { path = "../../../../../bridges/snowbridge/pallets/outbound-queue", default-features = false } +snowbridge-outbound-queue-runtime-api = { path = "../../../../../bridges/snowbridge/pallets/outbound-queue/runtime-api", default-features = false } +snowbridge-router-primitives = { path = "../../../../../bridges/snowbridge/primitives/router", default-features = false } +snowbridge-runtime-common = { path = "../../../../../bridges/snowbridge/runtime/runtime-common", default-features = false } bridge-hub-common = { path = "../common", default-features = false } @@ -127,7 +127,7 @@ bridge-runtime-common = { path = "../../../../../bridges/bin/runtime-common", fe "integrity-test", ] } sp-keyring = { path = "../../../../../substrate/primitives/keyring" } -snowbridge-runtime-test-common = { path = "../../../../../bridges/snowbridge/parachain/runtime/test-common" } +snowbridge-runtime-test-common = { path = "../../../../../bridges/snowbridge/runtime/test-common" } [features] default = ["std"] diff --git a/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml index cb873ad4bc3..a4dcd19dc9e 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml @@ -16,7 +16,7 @@ sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-fea cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } pallet-message-queue = { path = "../../../../../substrate/frame/message-queue", default-features = false } -snowbridge-core = { path = "../../../../../bridges/snowbridge/parachain/primitives/core", default-features = false } +snowbridge-core = { path = "../../../../../bridges/snowbridge/primitives/core", default-features = false } [features] default = ["std"] -- GitLab From 4d78c6eb89eac82c3b28fe973248008b16ea201c Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 2 Feb 2024 21:49:59 +0200 Subject: [PATCH 005/133] Small fixes coretime related fixes (#3188) The PR contains small fixes for: * A test `HostConfig v11` storage migration - v11 was compared with itself instead of with `v10`. * Outdated comment for `ClaimQueue` * Typos --- .../parachains/src/configuration/migration/v11.rs | 10 +++++----- polkadot/runtime/parachains/src/configuration/tests.rs | 6 +++--- polkadot/runtime/parachains/src/coretime/migration.rs | 2 +- polkadot/runtime/parachains/src/scheduler.rs | 6 ++---- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/polkadot/runtime/parachains/src/configuration/migration/v11.rs b/polkadot/runtime/parachains/src/configuration/migration/v11.rs index f4db9196b1a..a69061ac138 100644 --- a/polkadot/runtime/parachains/src/configuration/migration/v11.rs +++ b/polkadot/runtime/parachains/src/configuration/migration/v11.rs @@ -253,7 +253,7 @@ mod tests { new_test_ext(Default::default()).execute_with(|| { // Implant the v10 version in the state. - v10::ActiveConfig::::set(Some(v10)); + v10::ActiveConfig::::set(Some(v10.clone())); v10::PendingConfigs::::set(Some(pending_configs)); migrate_to_v11::(); @@ -264,7 +264,7 @@ mod tests { let mut configs_to_check = v11::PendingConfigs::::get().unwrap(); configs_to_check.push((0, v11.clone())); - for (_, v10) in configs_to_check { + for (_, v11) in configs_to_check { #[rustfmt::skip] { assert_eq!(v10.max_code_size , v11.max_code_size); @@ -286,7 +286,7 @@ mod tests { assert_eq!(v10.hrmp_max_parachain_inbound_channels , v11.hrmp_max_parachain_inbound_channels); assert_eq!(v10.hrmp_channel_max_message_size , v11.hrmp_channel_max_message_size); assert_eq!(v10.code_retention_period , v11.code_retention_period); - assert_eq!(v10.coretime_cores , v11.coretime_cores); + assert_eq!(v10.on_demand_cores , v11.coretime_cores); assert_eq!(v10.on_demand_retries , v11.on_demand_retries); assert_eq!(v10.group_rotation_frequency , v11.group_rotation_frequency); assert_eq!(v10.paras_availability_period , v11.paras_availability_period); @@ -303,8 +303,8 @@ mod tests { assert_eq!(v10.minimum_validation_upgrade_delay , v11.minimum_validation_upgrade_delay); assert_eq!(v10.async_backing_params.allowed_ancestry_len, v11.async_backing_params.allowed_ancestry_len); assert_eq!(v10.async_backing_params.max_candidate_depth , v11.async_backing_params.max_candidate_depth); - assert_eq!(v10.executor_params , v11.executor_params); - assert_eq!(v10.minimum_backing_votes , v11.minimum_backing_votes); + assert_eq!(v10.executor_params , v11.executor_params); + assert_eq!(v10.minimum_backing_votes , v11.minimum_backing_votes); }; // ; makes this a statement. `rustfmt::skip` cannot be put on an expression. } }); diff --git a/polkadot/runtime/parachains/src/configuration/tests.rs b/polkadot/runtime/parachains/src/configuration/tests.rs index c915eb12a0c..f1570017dd7 100644 --- a/polkadot/runtime/parachains/src/configuration/tests.rs +++ b/polkadot/runtime/parachains/src/configuration/tests.rs @@ -38,7 +38,7 @@ fn default_is_consistent() { fn scheduled_session_is_two_sessions_from_now() { new_test_ext(Default::default()).execute_with(|| { // The logic here is really tested only with scheduled_session = 2. It should work - // with other values, but that should receive a more rigorious testing. + // with other values, but that should receive a more rigorous testing. on_new_session(1); assert_eq!(Configuration::scheduled_session(), 3); }); @@ -136,7 +136,7 @@ fn pending_next_session_but_we_upgrade_once_more() { // update. assert_ok!(Configuration::set_validation_upgrade_cooldown(RuntimeOrigin::root(), 99)); - // This should result in yet another configiguration change scheduled. + // This should result in yet another configuration change scheduled. assert_eq!(Configuration::config(), initial_config); assert_eq!( PendingConfigs::::get(), @@ -179,7 +179,7 @@ fn scheduled_session_config_update_while_next_session_pending() { assert_ok!(Configuration::set_validation_upgrade_cooldown(RuntimeOrigin::root(), 99)); assert_ok!(Configuration::set_code_retention_period(RuntimeOrigin::root(), 98)); - // This should result in yet another configiguration change scheduled. + // This should result in yet another configuration change scheduled. assert_eq!(Configuration::config(), initial_config); assert_eq!( PendingConfigs::::get(), diff --git a/polkadot/runtime/parachains/src/coretime/migration.rs b/polkadot/runtime/parachains/src/coretime/migration.rs index e64d3fbd6a9..9bc0a20ef5b 100644 --- a/polkadot/runtime/parachains/src/coretime/migration.rs +++ b/polkadot/runtime/parachains/src/coretime/migration.rs @@ -74,7 +74,7 @@ mod v_coretime { loop { match sp_io::storage::next_key(&next_key) { - // StorageVersion is initialized before, so we need to ingore it. + // StorageVersion is initialized before, so we need to ignore it. Some(key) if &key == &storage_version_key => { next_key = key; }, diff --git a/polkadot/runtime/parachains/src/scheduler.rs b/polkadot/runtime/parachains/src/scheduler.rs index a666f568908..3dbb050fb4e 100644 --- a/polkadot/runtime/parachains/src/scheduler.rs +++ b/polkadot/runtime/parachains/src/scheduler.rs @@ -114,7 +114,7 @@ pub mod pallet { Paras(ParasEntry), } - /// Conveninece type alias for `CoreOccupied`. + /// Convenience type alias for `CoreOccupied`. pub type CoreOccupiedType = CoreOccupied>; impl CoreOccupied { @@ -145,9 +145,7 @@ pub mod pallet { pub(crate) type SessionStartBlock = StorageValue<_, BlockNumberFor, ValueQuery>; /// One entry for each availability core. The `VecDeque` represents the assignments to be - /// scheduled on that core. `None` is used to signal to not schedule the next para of the core - /// as there is one currently being scheduled. Not using `None` here would overwrite the - /// `CoreState` in the runtime API. The value contained here will not be valid after the end of + /// scheduled on that core. The value contained here will not be valid after the end of /// a block. Runtime APIs should be used to determine scheduled cores/ for the upcoming block. #[pallet::storage] #[pallet::getter(fn claimqueue)] -- GitLab From e0674cb3add94e8a3fbcf8b5d2735cf965c51391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 2 Feb 2024 20:51:56 +0100 Subject: [PATCH 006/133] Enable async backing by default for Rococo/Westend (#3162) This change is mainly for people running the local variants. They can directly start with async backing. --------- Signed-off-by: Alexandru Gheorghe Co-authored-by: Alexandru Gheorghe --- .../src/fragment_tree.rs | 3 + .../core/prospective-parachains/src/lib.rs | 16 +++++ .../src/variants/suggest_garbage_candidate.rs | 66 ++++++++++++++----- polkadot/node/service/src/chain_spec.rs | 7 +- 4 files changed, 73 insertions(+), 19 deletions(-) diff --git a/polkadot/node/core/prospective-parachains/src/fragment_tree.rs b/polkadot/node/core/prospective-parachains/src/fragment_tree.rs index 292e4ebe528..f7b97f07827 100644 --- a/polkadot/node/core/prospective-parachains/src/fragment_tree.rs +++ b/polkadot/node/core/prospective-parachains/src/fragment_tree.rs @@ -202,7 +202,10 @@ impl CandidateStorage { /// Note that an existing candidate has been backed. pub fn mark_backed(&mut self, candidate_hash: &CandidateHash) { if let Some(entry) = self.by_candidate_hash.get_mut(candidate_hash) { + gum::trace!(target: LOG_TARGET, ?candidate_hash, "Candidate marked as backed"); entry.state = CandidateState::Backed; + } else { + gum::trace!(target: LOG_TARGET, ?candidate_hash, "Candidate not found while marking as backed"); } } diff --git a/polkadot/node/core/prospective-parachains/src/lib.rs b/polkadot/node/core/prospective-parachains/src/lib.rs index dabcfb80e02..aba3d092a41 100644 --- a/polkadot/node/core/prospective-parachains/src/lib.rs +++ b/polkadot/node/core/prospective-parachains/src/lib.rs @@ -607,6 +607,14 @@ fn answer_get_backable_candidate( let Some(child_hash) = tree.select_child(&required_path, |candidate| storage.is_backed(candidate)) else { + gum::trace!( + target: LOG_TARGET, + ?required_path, + para_id = ?para, + %relay_parent, + "Could not find any backable candidate", + ); + let _ = tx.send(None); return }; @@ -621,6 +629,14 @@ fn answer_get_backable_candidate( return }; + gum::trace!( + target: LOG_TARGET, + ?relay_parent, + candidate_hash = ?child_hash, + ?candidate_relay_parent, + "Found backable candidate", + ); + let _ = tx.send(Some((child_hash, candidate_relay_parent))); } diff --git a/polkadot/node/malus/src/variants/suggest_garbage_candidate.rs b/polkadot/node/malus/src/variants/suggest_garbage_candidate.rs index 5e460f18903..22b44ddd1dc 100644 --- a/polkadot/node/malus/src/variants/suggest_garbage_candidate.rs +++ b/polkadot/node/malus/src/variants/suggest_garbage_candidate.rs @@ -22,6 +22,7 @@ #![allow(missing_docs)] +use futures::channel::oneshot; use polkadot_cli::{ service::{ AuthorityDiscoveryApi, AuxStore, BabeApi, Block, Error, ExtendedOverseerGenArgs, @@ -30,7 +31,6 @@ use polkadot_cli::{ }, validator_overseer_builder, Cli, }; -use polkadot_node_core_candidate_validation::find_validation_data; use polkadot_node_primitives::{AvailableData, BlockData, PoV}; use polkadot_node_subsystem_types::DefaultSubsystemClient; use polkadot_primitives::{CandidateDescriptor, CandidateReceipt}; @@ -82,7 +82,7 @@ where CandidateBackingMessage::Second( relay_parent, ref candidate, - ref _validation_data, + ref validation_data, ref _pov, ), } => { @@ -112,6 +112,7 @@ where let (sender, receiver) = std::sync::mpsc::channel(); let mut new_sender = subsystem_sender.clone(); let _candidate = candidate.clone(); + let validation_data = validation_data.clone(); self.spawner.spawn_blocking( "malus-get-validation-data", Some("malus"), @@ -124,22 +125,51 @@ where .unwrap() .len(); gum::trace!(target: MALUS, "Validators {}", n_validators); - match find_validation_data(&mut new_sender, &_candidate.descriptor()) - .await - { - Ok(Some((validation_data, validation_code))) => { - sender - .send(Some(( - validation_data, - validation_code, - n_validators, - ))) - .expect("channel is still open"); - }, - _ => { - sender.send(None).expect("channel is still open"); - }, - } + + let validation_code = { + let validation_code_hash = + _candidate.descriptor().validation_code_hash; + let (tx, rx) = oneshot::channel(); + new_sender + .send_message(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::ValidationCodeByHash( + validation_code_hash, + tx, + ), + )) + .await; + + let code = rx.await.expect("Querying the RuntimeApi should work"); + match code { + Err(e) => { + gum::error!( + target: MALUS, + ?validation_code_hash, + error = %e, + "Failed to fetch validation code", + ); + + sender.send(None).expect("channel is still open"); + return + }, + Ok(None) => { + gum::debug!( + target: MALUS, + ?validation_code_hash, + "Could not find validation code on chain", + ); + + sender.send(None).expect("channel is still open"); + return + }, + Ok(Some(c)) => c, + } + }; + + sender + .send(Some((validation_data, validation_code, n_validators))) + .expect("channel is still open"); }), ); diff --git a/polkadot/node/service/src/chain_spec.rs b/polkadot/node/service/src/chain_spec.rs index 1b4a97290c6..af241d1cbc5 100644 --- a/polkadot/node/service/src/chain_spec.rs +++ b/polkadot/node/service/src/chain_spec.rs @@ -120,7 +120,7 @@ pub fn wococo_config() -> Result { fn default_parachains_host_configuration( ) -> polkadot_runtime_parachains::configuration::HostConfiguration { - use polkadot_primitives::{MAX_CODE_SIZE, MAX_POV_SIZE}; + use polkadot_primitives::{AsyncBackingParams, MAX_CODE_SIZE, MAX_POV_SIZE}; polkadot_runtime_parachains::configuration::HostConfiguration { validation_upgrade_cooldown: 2u32, @@ -151,6 +151,11 @@ fn default_parachains_host_configuration( relay_vrf_modulo_samples: 2, zeroth_delay_tranche_width: 0, minimum_validation_upgrade_delay: 5, + scheduling_lookahead: 2, + async_backing_params: AsyncBackingParams { + max_candidate_depth: 3, + allowed_ancestry_len: 2, + }, ..Default::default() } } -- GitLab From 41db45a28129567c4d16735a4e51c1701c995476 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Feb 2024 21:07:10 +0000 Subject: [PATCH 007/133] Bump wasmi from 0.31.0 to 0.31.2 (#3164) Bumps [wasmi](https://github.com/paritytech/wasmi) from 0.31.0 to 0.31.2.
Release notes

Sourced from wasmi's releases.

v0.31.1 - 2023-12-01

Fixes

  • Fixed a bug, in the wasmi engine executor, that causes an out of bounds buffer write when calling or resuming a Wasm function with a high number of parameters from the host side.
Changelog

Sourced from wasmi's changelog.

Changelog

All notable changes to this project will be documented in this file.

The format is loosely based on Keep a Changelog, and this project adheres to Semantic Versioning. Additionally we have an Internal section for changes that are of interest to developers.

Dates in this file are formattes as YYYY-MM-DD.

[0.32.0-beta.5] - 2024-01-15

Note:

  • This is the beta of the upcoming v0.32.0 release. This version is not production ready yet and might contain serious bugs. Please use this only for experimentation or at your own risk.
  • Performance tests indicated that the new register-machine bytecode based Wasmi engine performance is very sensitive to hardware or OS specifics which may lead to very different performance characteristics.
    • We are working on fixing this until the stable release.
    • Measurements concluded that execution performance can be equal or sometimes even surpass Wasm3 execution performance.

Added

  • Added a new execution engine based on register-machine bytecode. (paritytech/wasmi#729)
    • The register-machine Wasmi Engine executes roughly 80-100% faster and compiles roughly 30% slower according to benchmarks conducted so far.
  • Added Module::new_unchecked API. (paritytech/wasmi#829)
    • This allows to compile a Wasm module without Wasm validation which can be useful when users know that their inputs are valid Wasm binaries.
    • This improves Wasm compilation performance for faster startup times by roughly 10-20%.
  • Added Wasm compilation modes. (paritytech/wasmi#844)
    • When using Module::new Wasmi eagerly compiles Wasm bytecode into Wasmi bytecode which is optimized for efficient execution. However, this compilation can become very costly especially for large Wasm binaries.
    • The solution to this problem is to introduce new compilation modes, namely:
      • CompilationMode::Eager: Eager compilation, what Wasmi did so far. (default)
      • CompilationMode::LazyTranslation: Eager Wasm validation and lazy Wasm translation.
      • CompilationMode::Lazy: Lazy Wasm validation and translation.
    • Benchmarks concluded that
      • CompilationMode::LazyTanslation: Usually improves startup performance by a factor of 2 to 3.
      • CompilationMode::Lazy: Usually improves startup performance by a factor of up to 27.
    • Note that CompilationMode::Lazy can lead to partially validated Wasm modules which can introduce non-determinism when using different Wasm implementations. Therefore users should know what they are doing when using CompilationMode::Lazy if this is a concern.
    • Enable lazy Wasm compilation with:
      let mut config = wasmi::Config::default();
      

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=wasmi&package-manager=cargo&previous-version=0.31.0&new-version=0.31.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 952eaee4997..d3424885c12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21168,9 +21168,9 @@ dependencies = [ [[package]] name = "wasmi" -version = "0.31.0" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f341edb80021141d4ae6468cbeefc50798716a347d4085c3811900049ea8945" +checksum = "77a8281d1d660cdf54c76a3efa9ddd0c270cada1383a995db3ccb43d166456c7" dependencies = [ "smallvec", "spin 0.9.8", @@ -21181,9 +21181,9 @@ dependencies = [ [[package]] name = "wasmi_arena" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "401c1f35e413fac1846d4843745589d9ec678977ab35a384db8ae7830525d468" +checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073" [[package]] name = "wasmi_core" -- GitLab From e349fc9ef8354eea1bafc1040c20d6fe3189e1ec Mon Sep 17 00:00:00 2001 From: Koute Date: Sat, 3 Feb 2024 13:12:12 +0900 Subject: [PATCH 008/133] Initial support for building RISC-V runtimes targeting PolkaVM (#3179) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR adds initial support for building RISC-V runtimes targeting PolkaVM. - Setting the `SUBSTRATE_RUNTIME_TARGET=riscv` environment variable will now build a RISC-V runtime instead of a WASM runtime. - This only adds support for *building* runtimes; running them will need a PolkaVM-based executor, which I will add in a future PR. - Only building the minimal runtime is supported (building the Polkadot runtime doesn't work *yet* due to one of the dependencies). - The builder now sets a `substrate_runtime` cfg flag when building the runtimes, with the idea being that instead of doing `#[cfg(not(feature = "std"))]` or `#[cfg(target_arch = "wasm32")]` to detect that we're building a runtime you'll do `#[cfg(substrate_runtime)]`. (Switching the whole codebase to use this will be done in a future PR; I deliberately didn't do this here to keep this PR minimal and reviewable.) - Further renaming of things (e.g. types, environment variables and proc macro attributes having "wasm" in their name) to be target-agnostic will also be done in a future refactoring PR (while keeping backwards compatibility where it makes sense; I don't intend to break anyone's workflow or create unnecessary churn). - This PR also fixes two bugs in the `wasm-builder` crate: * The `RUSTC` environment variable is now removed when invoking the compiler. This prevents the toolchain version from being overridden when called from a `build.rs` script. * When parsing the `rustup toolchain list` output the `(default)` is now properly stripped and not treated as part of the version. - I've also added a minimal CI job that makes sure this doesn't break in the future. (cc @paritytech/ci) cc @athei ------ Also, just a fun little tidbit: quickly comparing the size of the built runtimes it seems that the PolkaVM runtime is slightly smaller than the WASM one. (`production` build, with the `names` section substracted from the WASM's size to keep things fair, since for the PolkaVM runtime we're currently stripping out everything) - `.wasm`: 625505 bytes - `.wasm` (after wasm-opt -O3): 563205 bytes - `.wasm` (after wasm-opt -Os): 562987 bytes - `.wasm` (after wasm-opt -Oz): 536852 bytes - `.polkavm`: ~~580338 bytes~~ 550476 bytes (after enabling extra target features; I'll add those in another PR once we have an executor working) --------- Co-authored-by: Bastian Köcher --- .gitlab/pipeline/build.yml | 8 + Cargo.lock | 65 +++++- Cargo.toml | 4 + substrate/primitives/api/Cargo.toml | 6 +- .../api/proc-macro/src/impl_runtime_apis.rs | 3 +- substrate/primitives/api/src/lib.rs | 3 + substrate/primitives/io/src/lib.rs | 43 +++- .../primitives/runtime-interface/Cargo.toml | 3 + .../bare_function_interface.rs | 9 +- .../host_function_interface.rs | 2 +- .../primitives/runtime-interface/src/lib.rs | 3 + .../runtime-interface/src/polkavm.rs | 30 +++ substrate/utils/wasm-builder/Cargo.toml | 1 + substrate/utils/wasm-builder/src/builder.rs | 33 ++- substrate/utils/wasm-builder/src/lib.rs | 111 +++++++-- .../utils/wasm-builder/src/prerequisites.rs | 219 +++++++++++------- .../utils/wasm-builder/src/wasm_project.rs | 205 ++++++++++++---- 17 files changed, 578 insertions(+), 170 deletions(-) create mode 100644 substrate/primitives/runtime-interface/src/polkavm.rs diff --git a/.gitlab/pipeline/build.yml b/.gitlab/pipeline/build.yml index 20aa4a5c2a2..002206e328c 100644 --- a/.gitlab/pipeline/build.yml +++ b/.gitlab/pipeline/build.yml @@ -329,6 +329,14 @@ build-linux-substrate: # - printf '\n# building node-template\n\n' # - ./scripts/ci/node-template-release.sh ./artifacts/substrate/substrate-node-template.tar.gz +build-minimal-runtime-polkavm: + stage: build + extends: + - .docker-env + - .common-refs + script: + - SUBSTRATE_RUNTIME_TARGET=riscv cargo check -p minimal-runtime + .build-subkey: stage: build extends: diff --git a/Cargo.lock b/Cargo.lock index d3424885c12..663d8ccdd39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9571,7 +9571,7 @@ dependencies = [ "anyhow", "frame-system", "parity-wasm", - "polkavm-linker", + "polkavm-linker 0.5.0", "sp-runtime", "tempfile", "toml 0.8.8", @@ -9633,7 +9633,7 @@ dependencies = [ "bitflags 1.3.2", "parity-scale-codec", "paste", - "polkavm-derive", + "polkavm-derive 0.5.0", "scale-info", ] @@ -13711,28 +13711,65 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88b4e215c80fe876147f3d58158d5dfeae7dabdd6047e175af77095b78d0035c" +[[package]] +name = "polkavm-common" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c99f7eee94e7be43ba37eef65ad0ee8cbaf89b7c00001c3f6d2be985cb1817" + [[package]] name = "polkavm-derive" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6380dbe1fb03ecc74ad55d841cfc75480222d153ba69ddcb00977866cbdabdb8" dependencies = [ - "polkavm-derive-impl", + "polkavm-derive-impl 0.5.0", "syn 2.0.48", ] +[[package]] +name = "polkavm-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79fa916f7962348bd1bb1a65a83401675e6fc86c51a0fdbcf92a3108e58e6125" +dependencies = [ + "polkavm-derive-impl-macro", +] + [[package]] name = "polkavm-derive-impl" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc8211b3365bbafb2fb32057d68b0e1ca55d079f5cf6f9da9b98079b94b3987d" dependencies = [ - "polkavm-common", + "polkavm-common 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "polkavm-derive-impl" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c10b2654a8a10a83c260bfb93e97b262cf0017494ab94a65d389e0eda6de6c9c" +dependencies = [ + "polkavm-common 0.8.0", "proc-macro2", "quote", "syn 2.0.48", ] +[[package]] +name = "polkavm-derive-impl-macro" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e85319a0d5129dc9f021c62607e0804f5fb777a05cdda44d750ac0732def66" +dependencies = [ + "polkavm-derive-impl 0.8.0", + "syn 2.0.48", +] + [[package]] name = "polkavm-linker" version = "0.5.0" @@ -13743,7 +13780,22 @@ dependencies = [ "hashbrown 0.14.3", "log", "object 0.32.2", - "polkavm-common", + "polkavm-common 0.5.0", + "regalloc2 0.9.3", + "rustc-demangle", +] + +[[package]] +name = "polkavm-linker" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc03593918a5890f96c276fb1e34ab77002bea1f9136cdcb55107c241011ab7" +dependencies = [ + "gimli 0.28.0", + "hashbrown 0.14.3", + "log", + "object 0.32.2", + "polkavm-common 0.8.0", "regalloc2 0.9.3", "rustc-demangle", ] @@ -17945,6 +17997,7 @@ dependencies = [ "sp-externalities 0.25.0", "sp-metadata-ir", "sp-runtime", + "sp-runtime-interface 24.0.0", "sp-state-machine", "sp-std 14.0.0", "sp-test-primitives", @@ -18632,6 +18685,7 @@ dependencies = [ "bytes", "impl-trait-for-tuples", "parity-scale-codec", + "polkavm-derive 0.8.0", "primitive-types", "rustversion", "sp-core", @@ -19647,6 +19701,7 @@ dependencies = [ "console", "filetime", "parity-wasm", + "polkavm-linker 0.8.1", "sp-maybe-compressed-blob", "strum 0.24.1", "tempfile", diff --git a/Cargo.toml b/Cargo.toml index 62f8c9c8953..e807171b24c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -532,6 +532,10 @@ stable_sort_primitive = { level = "allow", priority = 2 } # prefer st extra-unused-type-parameters = { level = "allow", priority = 2 } # stylistic default_constructed_unit_structs = { level = "allow", priority = 2 } # stylistic +[workspace.dependencies] +polkavm-linker = "0.8.1" +polkavm-derive = "0.8.0" + [profile.release] # Polkadot runtime requires unwinding. panic = "unwind" diff --git a/substrate/primitives/api/Cargo.toml b/substrate/primitives/api/Cargo.toml index a1438316289..cd882c7a050 100644 --- a/substrate/primitives/api/Cargo.toml +++ b/substrate/primitives/api/Cargo.toml @@ -21,13 +21,16 @@ sp-api-proc-macro = { path = "proc-macro", default-features = false } sp-core = { path = "../core", default-features = false } sp-std = { path = "../std", default-features = false } sp-runtime = { path = "../runtime", default-features = false } +sp-runtime-interface = { path = "../runtime-interface", default-features = false } sp-externalities = { path = "../externalities", default-features = false, optional = true } sp-version = { path = "../version", default-features = false } sp-state-machine = { path = "../state-machine", default-features = false, optional = true } sp-trie = { path = "../trie", default-features = false, optional = true } hash-db = { version = "0.16.0", optional = true } thiserror = { version = "1.0.48", optional = true } -scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.10.0", default-features = false, features = [ + "derive", +] } sp-metadata-ir = { path = "../metadata-ir", default-features = false, optional = true } log = { version = "0.4.17", default-features = false } @@ -46,6 +49,7 @@ std = [ "sp-externalities", "sp-externalities?/std", "sp-metadata-ir?/std", + "sp-runtime-interface/std", "sp-runtime/std", "sp-state-machine/std", "sp-std/std", diff --git a/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs b/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs index fd81fdb624c..79378968437 100644 --- a/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs +++ b/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs @@ -237,7 +237,8 @@ fn generate_wasm_interface(impls: &[ItemImpl]) -> Result { #c::std_disabled! { #( #attrs )* #[no_mangle] - pub unsafe fn #fn_name(input_data: *mut u8, input_len: usize) -> u64 { + #[cfg_attr(any(target_arch = "riscv32", target_arch = "riscv64"), #c::__private::polkavm_export(abi = #c::__private::polkavm_abi))] + pub unsafe extern fn #fn_name(input_data: *mut u8, input_len: usize) -> u64 { let mut #input = if input_len == 0 { &[0u8; 0] } else { diff --git a/substrate/primitives/api/src/lib.rs b/substrate/primitives/api/src/lib.rs index 0a9b334a96f..190de1ab3fd 100644 --- a/substrate/primitives/api/src/lib.rs +++ b/substrate/primitives/api/src/lib.rs @@ -105,6 +105,9 @@ pub mod __private { }; pub use sp_std::{mem, slice, vec}; pub use sp_version::{create_apis_vec, ApiId, ApisVec, RuntimeVersion}; + + #[cfg(all(any(target_arch = "riscv32", target_arch = "riscv64"), substrate_runtime))] + pub use sp_runtime_interface::polkavm::{polkavm_abi, polkavm_export}; } #[cfg(feature = "std")] diff --git a/substrate/primitives/io/src/lib.rs b/substrate/primitives/io/src/lib.rs index 20ea56bc26c..6d34199416a 100644 --- a/substrate/primitives/io/src/lib.rs +++ b/substrate/primitives/io/src/lib.rs @@ -1737,20 +1737,20 @@ mod tracing_setup { pub use tracing_setup::init_tracing; -/// Allocator used by Substrate when executing the Wasm runtime. -#[cfg(all(target_arch = "wasm32", not(feature = "std")))] -struct WasmAllocator; +/// Allocator used by Substrate from within the runtime. +#[cfg(substrate_runtime)] +struct RuntimeAllocator; -#[cfg(all(target_arch = "wasm32", not(feature = "disable_allocator"), not(feature = "std")))] +#[cfg(all(not(feature = "disable_allocator"), substrate_runtime))] #[global_allocator] -static ALLOCATOR: WasmAllocator = WasmAllocator; +static ALLOCATOR: RuntimeAllocator = RuntimeAllocator; -#[cfg(all(target_arch = "wasm32", not(feature = "std")))] +#[cfg(substrate_runtime)] mod allocator_impl { use super::*; use core::alloc::{GlobalAlloc, Layout}; - unsafe impl GlobalAlloc for WasmAllocator { + unsafe impl GlobalAlloc for RuntimeAllocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { allocator::malloc(layout.size() as u32) } @@ -1761,8 +1761,27 @@ mod allocator_impl { } } -/// A default panic handler for WASM environment. -#[cfg(all(not(feature = "disable_panic_handler"), not(feature = "std")))] +/// Crashes the execution of the program. +/// +/// Equivalent to the WASM `unreachable` instruction, RISC-V `unimp` instruction, +/// or just the `unreachable!()` macro everywhere else. +pub fn unreachable() -> ! { + #[cfg(target_family = "wasm")] + { + core::arch::wasm32::unreachable(); + } + + #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] + unsafe { + core::arch::asm!("unimp", options(noreturn)); + } + + #[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64", target_family = "wasm")))] + unreachable!(); +} + +/// A default panic handler for the runtime environment. +#[cfg(all(not(feature = "disable_panic_handler"), substrate_runtime))] #[panic_handler] #[no_mangle] pub fn panic(info: &core::panic::PanicInfo) -> ! { @@ -1774,11 +1793,11 @@ pub fn panic(info: &core::panic::PanicInfo) -> ! { #[cfg(not(feature = "improved_panic_error_reporting"))] { logging::log(LogLevel::Error, "runtime", message.as_bytes()); - core::arch::wasm32::unreachable(); + unreachable(); } } -/// A default OOM handler for WASM environment. +/// A default OOM handler for the runtime environment. #[cfg(all(not(feature = "disable_oom"), enable_alloc_error_handler))] #[alloc_error_handler] pub fn oom(_: core::alloc::Layout) -> ! { @@ -1789,7 +1808,7 @@ pub fn oom(_: core::alloc::Layout) -> ! { #[cfg(not(feature = "improved_panic_error_reporting"))] { logging::log(LogLevel::Error, "runtime", b"Runtime memory exhausted. Aborting"); - core::arch::wasm32::unreachable(); + unreachable(); } } diff --git a/substrate/primitives/runtime-interface/Cargo.toml b/substrate/primitives/runtime-interface/Cargo.toml index 6e046567d14..b4fab17eeb7 100644 --- a/substrate/primitives/runtime-interface/Cargo.toml +++ b/substrate/primitives/runtime-interface/Cargo.toml @@ -29,6 +29,9 @@ primitive-types = { version = "0.12.0", default-features = false } sp-storage = { path = "../storage", default-features = false } impl-trait-for-tuples = "0.2.2" +[target.'cfg(all(any(target_arch = "riscv32", target_arch = "riscv64"), substrate_runtime))'.dependencies] +polkavm-derive = { workspace = true } + [dev-dependencies] sp-runtime-interface-test-wasm = { path = "test-wasm" } sp-state-machine = { path = "../state-machine" } diff --git a/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/bare_function_interface.rs b/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/bare_function_interface.rs index 77a29bec380..32455b39eed 100644 --- a/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/bare_function_interface.rs +++ b/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/bare_function_interface.rs @@ -109,7 +109,12 @@ fn function_no_std_impl( }; let maybe_unreachable = if method.should_trap_on_return() { quote! { - ; core::arch::wasm32::unreachable(); + ; + #[cfg(target_family = "wasm")] + { core::arch::wasm32::unreachable(); } + + #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] + unsafe { core::arch::asm!("unimp", options(noreturn)); } } } else { quote! {} @@ -118,7 +123,7 @@ fn function_no_std_impl( let attrs = method.attrs.iter().filter(|a| !a.path().is_ident("version")); let cfg_wasm_only = if is_wasm_only { - quote! { #[cfg(target_arch = "wasm32")] } + quote! { #[cfg(substrate_runtime)] } } else { quote! {} }; diff --git a/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs b/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs index 77a9e56eecb..fc985157cdb 100644 --- a/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs +++ b/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs @@ -116,8 +116,8 @@ fn generate_extern_host_function( #(#cfg_attrs)* #[doc = #doc_string] pub fn #function ( #( #args ),* ) #return_value { + #[cfg_attr(any(target_arch = "riscv32", target_arch = "riscv64"), #crate_::polkavm::polkavm_import(abi = #crate_::polkavm::polkavm_abi))] extern "C" { - /// The extern function. pub fn #ext_function ( #( #arg_names: <#arg_types as #crate_::RIType>::FFIType ),* ) #ffi_return_value; diff --git a/substrate/primitives/runtime-interface/src/lib.rs b/substrate/primitives/runtime-interface/src/lib.rs index 1f1638880bb..8b0edf1ec81 100644 --- a/substrate/primitives/runtime-interface/src/lib.rs +++ b/substrate/primitives/runtime-interface/src/lib.rs @@ -376,6 +376,9 @@ pub use sp_externalities::{ #[doc(hidden)] pub use codec; +#[cfg(all(any(target_arch = "riscv32", target_arch = "riscv64"), substrate_runtime))] +pub mod polkavm; + #[cfg(feature = "std")] pub mod host; pub(crate) mod impls; diff --git a/substrate/primitives/runtime-interface/src/polkavm.rs b/substrate/primitives/runtime-interface/src/polkavm.rs new file mode 100644 index 00000000000..484a269fd14 --- /dev/null +++ b/substrate/primitives/runtime-interface/src/polkavm.rs @@ -0,0 +1,30 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub use polkavm_derive::{polkavm_export, polkavm_import}; + +#[polkavm_derive::polkavm_define_abi(allow_extra_input_registers)] +pub mod polkavm_abi {} + +impl self::polkavm_abi::FromHost for *mut u8 { + type Regs = (u32,); + + #[inline] + fn from_host((value,): Self::Regs) -> Self { + value as *mut u8 + } +} diff --git a/substrate/utils/wasm-builder/Cargo.toml b/substrate/utils/wasm-builder/Cargo.toml index f01adbc03d6..7abd1a20284 100644 --- a/substrate/utils/wasm-builder/Cargo.toml +++ b/substrate/utils/wasm-builder/Cargo.toml @@ -26,3 +26,4 @@ sp-maybe-compressed-blob = { path = "../../primitives/maybe-compressed-blob" } filetime = "0.2.16" wasm-opt = "0.116" parity-wasm = "0.45" +polkavm-linker = { workspace = true } diff --git a/substrate/utils/wasm-builder/src/builder.rs b/substrate/utils/wasm-builder/src/builder.rs index 9c1655d8562..d2aaff448bc 100644 --- a/substrate/utils/wasm-builder/src/builder.rs +++ b/substrate/utils/wasm-builder/src/builder.rs @@ -21,6 +21,8 @@ use std::{ process, }; +use crate::RuntimeTarget; + /// Returns the manifest dir from the `CARGO_MANIFEST_DIR` env. fn get_manifest_dir() -> PathBuf { env::var("CARGO_MANIFEST_DIR") @@ -49,6 +51,8 @@ impl WasmBuilderSelectProject { project_cargo_toml: get_manifest_dir().join("Cargo.toml"), features_to_enable: Vec::new(), disable_runtime_version_section_check: false, + export_heap_base: false, + import_memory: false, } } @@ -65,6 +69,8 @@ impl WasmBuilderSelectProject { project_cargo_toml: path, features_to_enable: Vec::new(), disable_runtime_version_section_check: false, + export_heap_base: false, + import_memory: false, }) } else { Err("Project path must point to the `Cargo.toml` of the project") @@ -97,6 +103,11 @@ pub struct WasmBuilder { features_to_enable: Vec, /// Should the builder not check that the `runtime_version` section exists in the wasm binary? disable_runtime_version_section_check: bool, + + /// Whether `__heap_base` should be exported (WASM-only). + export_heap_base: bool, + /// Whether `--import-memory` should be added to the link args (WASM-only). + import_memory: bool, } impl WasmBuilder { @@ -109,7 +120,7 @@ impl WasmBuilder { /// /// This adds `-Clink-arg=--export=__heap_base` to `RUST_FLAGS`. pub fn export_heap_base(mut self) -> Self { - self.rust_flags.push("-Clink-arg=--export=__heap_base".into()); + self.export_heap_base = true; self } @@ -127,7 +138,7 @@ impl WasmBuilder { /// /// This adds `-C link-arg=--import-memory` to `RUST_FLAGS`. pub fn import_memory(mut self) -> Self { - self.rust_flags.push("-C link-arg=--import-memory".into()); + self.import_memory = true; self } @@ -159,7 +170,18 @@ impl WasmBuilder { } /// Build the WASM binary. - pub fn build(self) { + pub fn build(mut self) { + let target = crate::runtime_target(); + if target == RuntimeTarget::Wasm { + if self.export_heap_base { + self.rust_flags.push("-Clink-arg=--export=__heap_base".into()); + } + + if self.import_memory { + self.rust_flags.push("-C link-arg=--import-memory".into()); + } + } + let out_dir = PathBuf::from(env::var("OUT_DIR").expect("`OUT_DIR` is set by cargo!")); let file_path = out_dir.join(self.file_name.clone().unwrap_or_else(|| "wasm_binary.rs".into())); @@ -175,6 +197,7 @@ impl WasmBuilder { } build_project( + target, file_path, self.project_cargo_toml, self.rust_flags.into_iter().map(|f| format!("{} ", f)).collect(), @@ -248,6 +271,7 @@ fn generate_rerun_if_changed_instructions() { /// `check_for_runtime_version_section` - Should the wasm binary be checked for the /// `runtime_version` section? fn build_project( + target: RuntimeTarget, file_name: PathBuf, project_cargo_toml: PathBuf, default_rustflags: String, @@ -255,7 +279,7 @@ fn build_project( wasm_binary_name: Option, check_for_runtime_version_section: bool, ) { - let cargo_cmd = match crate::prerequisites::check() { + let cargo_cmd = match crate::prerequisites::check(target) { Ok(cmd) => cmd, Err(err_msg) => { eprintln!("{}", err_msg); @@ -264,6 +288,7 @@ fn build_project( }; let (wasm_binary, bloaty) = crate::wasm_project::create_and_compile( + target, &project_cargo_toml, &default_rustflags, cargo_cmd, diff --git a/substrate/utils/wasm-builder/src/lib.rs b/substrate/utils/wasm-builder/src/lib.rs index ec85fd1ffdd..5cde48c0950 100644 --- a/substrate/utils/wasm-builder/src/lib.rs +++ b/substrate/utils/wasm-builder/src/lib.rs @@ -113,6 +113,7 @@ //! wasm32-unknown-unknown --toolchain nightly-2020-02-20`. use std::{ + collections::BTreeSet, env, fs, io::BufRead, path::{Path, PathBuf}, @@ -164,6 +165,9 @@ const WASM_BUILD_WORKSPACE_HINT: &str = "WASM_BUILD_WORKSPACE_HINT"; /// Environment variable to set whether we'll build `core`/`std`. const WASM_BUILD_STD: &str = "WASM_BUILD_STD"; +/// The target to use for the runtime. Valid values are `wasm` (default) or `riscv`. +const RUNTIME_TARGET: &str = "SUBSTRATE_RUNTIME_TARGET"; + /// Write to the given `file` if the `content` is different. fn write_file_if_changed(file: impl AsRef, content: impl AsRef) { if fs::read_to_string(file.as_ref()).ok().as_deref() != Some(content.as_ref()) { @@ -185,7 +189,7 @@ fn copy_file_if_changed(src: PathBuf, dst: PathBuf) { } /// Get a cargo command that should be used to invoke the compilation. -fn get_cargo_command() -> CargoCommand { +fn get_cargo_command(target: RuntimeTarget) -> CargoCommand { let env_cargo = CargoCommand::new(&env::var("CARGO").expect("`CARGO` env variable is always set by cargo")); let default_cargo = CargoCommand::new("cargo"); @@ -196,35 +200,33 @@ fn get_cargo_command() -> CargoCommand { wasm_toolchain.map(|t| CargoCommand::new_with_args("rustup", &["run", &t, "cargo"])) { cmd - } else if env_cargo.supports_substrate_wasm_env() { + } else if env_cargo.supports_substrate_runtime_env(target) { env_cargo - } else if default_cargo.supports_substrate_wasm_env() { + } else if default_cargo.supports_substrate_runtime_env(target) { default_cargo } else { // If no command before provided us with a cargo that supports our Substrate wasm env, we // try to search one with rustup. If that fails as well, we return the default cargo and let // the prequisities check fail. - get_rustup_command().unwrap_or(default_cargo) + get_rustup_command(target).unwrap_or(default_cargo) } } -/// Get the newest rustup command that supports our Substrate wasm env. +/// Get the newest rustup command that supports compiling a runtime. /// /// Stable versions are always favored over nightly versions even if the nightly versions are /// newer. -fn get_rustup_command() -> Option { - let host = format!("-{}", env::var("HOST").expect("`HOST` is always set by cargo")); - +fn get_rustup_command(target: RuntimeTarget) -> Option { let output = Command::new("rustup").args(&["toolchain", "list"]).output().ok()?.stdout; let lines = output.as_slice().lines(); let mut versions = Vec::new(); for line in lines.filter_map(|l| l.ok()) { - let rustup_version = line.trim_end_matches(&host); - + // Split by a space to get rid of e.g. " (default)" at the end. + let rustup_version = line.split(" ").next().unwrap(); let cmd = CargoCommand::new_with_args("rustup", &["run", &rustup_version, "cargo"]); - if !cmd.supports_substrate_wasm_env() { + if !cmd.supports_substrate_runtime_env(target) { continue } @@ -247,22 +249,26 @@ struct CargoCommand { program: String, args: Vec, version: Option, + target_list: Option>, } impl CargoCommand { fn new(program: &str) -> Self { let version = Self::extract_version(program, &[]); + let target_list = Self::extract_target_list(program, &[]); - CargoCommand { program: program.into(), args: Vec::new(), version } + CargoCommand { program: program.into(), args: Vec::new(), version, target_list } } fn new_with_args(program: &str, args: &[&str]) -> Self { let version = Self::extract_version(program, args); + let target_list = Self::extract_target_list(program, args); CargoCommand { program: program.into(), args: args.iter().map(ToString::to_string).collect(), version, + target_list, } } @@ -283,6 +289,23 @@ impl CargoCommand { Version::extract(&version) } + fn extract_target_list(program: &str, args: &[&str]) -> Option> { + // This is technically an unstable option, but we don't care because we only need this + // to build RISC-V runtimes, and those currently require a specific nightly toolchain + // anyway, so it's totally fine for this to fail in other cases. + let list = Command::new(program) + .args(args) + .args(&["rustc", "-Z", "unstable-options", "--print", "target-list"]) + // Make sure if we're called from within a `build.rs` the host toolchain won't override + // a rustup toolchain we've picked. + .env_remove("RUSTC") + .output() + .ok() + .and_then(|o| String::from_utf8(o.stdout).ok())?; + + Some(list.trim().split("\n").map(ToString::to_string).collect()) + } + /// Returns the version of this cargo command or `None` if it failed to extract the version. fn version(&self) -> Option { self.version @@ -294,12 +317,29 @@ impl CargoCommand { env::var("RUSTC_BOOTSTRAP").is_ok() } + /// Check if the supplied cargo command supports our runtime environment. + fn supports_substrate_runtime_env(&self, target: RuntimeTarget) -> bool { + match target { + RuntimeTarget::Wasm => self.supports_substrate_runtime_env_wasm(), + RuntimeTarget::Riscv => self.supports_substrate_runtime_env_riscv(), + } + } + + /// Check if the supplied cargo command supports our RISC-V runtime environment. + fn supports_substrate_runtime_env_riscv(&self) -> bool { + let Some(target_list) = self.target_list.as_ref() else { return false }; + // This is our custom target which currently doesn't exist on any upstream toolchain, + // so if it exists it's guaranteed to be our custom toolchain and have have everything + // we need, so any further version checks are unnecessary at this point. + target_list.contains("riscv32ema-unknown-none-elf") + } + /// Check if the supplied cargo command supports our Substrate wasm environment. /// /// This means that either the cargo version is at minimum 1.68.0 or this is a nightly cargo. /// /// Assumes that cargo version matches the rustc version. - fn supports_substrate_wasm_env(&self) -> bool { + fn supports_substrate_runtime_env_wasm(&self) -> bool { // `RUSTC_BOOTSTRAP` tells a stable compiler to behave like a nightly. So, when this env // variable is set, we can assume that whatever rust compiler we have, it is a nightly // compiler. For "more" information, see: @@ -365,5 +405,48 @@ fn get_bool_environment_variable(name: &str) -> Option { /// Returns whether we need to also compile the standard library when compiling the runtime. fn build_std_required() -> bool { - crate::get_bool_environment_variable(crate::WASM_BUILD_STD).unwrap_or(true) + let default = runtime_target() == RuntimeTarget::Wasm; + + crate::get_bool_environment_variable(crate::WASM_BUILD_STD).unwrap_or(default) +} + +#[derive(Copy, Clone, PartialEq, Eq)] +enum RuntimeTarget { + Wasm, + Riscv, +} + +impl RuntimeTarget { + fn rustc_target(self) -> &'static str { + match self { + RuntimeTarget::Wasm => "wasm32-unknown-unknown", + RuntimeTarget::Riscv => "riscv32ema-unknown-none-elf", + } + } + + fn build_subdirectory(self) -> &'static str { + // Keep the build directories separate so that when switching between + // the targets we won't trigger unnecessary rebuilds. + match self { + RuntimeTarget::Wasm => "wbuild", + RuntimeTarget::Riscv => "rbuild", + } + } +} + +fn runtime_target() -> RuntimeTarget { + let Some(value) = env::var_os(RUNTIME_TARGET) else { + return RuntimeTarget::Wasm; + }; + + if value == "wasm" { + RuntimeTarget::Wasm + } else if value == "riscv" { + RuntimeTarget::Riscv + } else { + build_helper::warning!( + "the '{RUNTIME_TARGET}' environment variable has an invalid value; it must be either 'wasm' or 'riscv'" + ); + std::process::exit(1); + } } diff --git a/substrate/utils/wasm-builder/src/prerequisites.rs b/substrate/utils/wasm-builder/src/prerequisites.rs index 99eb6ee1f18..a601e3210dd 100644 --- a/substrate/utils/wasm-builder/src/prerequisites.rs +++ b/substrate/utils/wasm-builder/src/prerequisites.rs @@ -15,14 +15,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{write_file_if_changed, CargoCommand, CargoCommandVersioned}; +use crate::{write_file_if_changed, CargoCommand, CargoCommandVersioned, RuntimeTarget}; use console::style; -use std::{fs, path::Path}; +use std::{ + fs, + path::{Path, PathBuf}, + process::Command, +}; + use tempfile::tempdir; -/// Print an error message. -fn print_error_message(message: &str) -> String { +/// Colorizes an error message, if color output is enabled. +fn colorize_error_message(message: &str) -> String { if super::color_output_enabled() { style(message).red().bold().to_string() } else { @@ -30,121 +35,165 @@ fn print_error_message(message: &str) -> String { } } +/// Colorizes an auxiliary message, if color output is enabled. +fn colorize_aux_message(message: &str) -> String { + if super::color_output_enabled() { + style(message).yellow().bold().to_string() + } else { + message.into() + } +} + /// Checks that all prerequisites are installed. /// /// Returns the versioned cargo command on success. -pub(crate) fn check() -> Result { - let cargo_command = crate::get_cargo_command(); - - if !cargo_command.supports_substrate_wasm_env() { - return Err(print_error_message( - "Cannot compile the WASM runtime: no compatible Rust compiler found!\n\ - Install at least Rust 1.68.0 or a recent nightly version.", - )) - } +pub(crate) fn check(target: RuntimeTarget) -> Result { + let cargo_command = crate::get_cargo_command(target); + match target { + RuntimeTarget::Wasm => { + if !cargo_command.supports_substrate_runtime_env(target) { + return Err(colorize_error_message( + "Cannot compile a WASM runtime: no compatible Rust compiler found!\n\ + Install at least Rust 1.68.0 or a recent nightly version.", + )); + } - check_wasm_toolchain_installed(cargo_command) + check_wasm_toolchain_installed(cargo_command) + }, + RuntimeTarget::Riscv => { + if !cargo_command.supports_substrate_runtime_env(target) { + return Err(colorize_error_message( + "Cannot compile a RISC-V runtime: no compatible Rust compiler found!\n\ + Install a toolchain from here and try again: https://github.com/paritytech/rustc-rv32e-toolchain/", + )); + } + + let dummy_crate = DummyCrate::new(&cargo_command, target); + let version = dummy_crate.get_rustc_version(); + Ok(CargoCommandVersioned::new(cargo_command, version)) + }, + } } -/// Creates a minimal dummy crate at the given path and returns the manifest path. -fn create_minimal_crate(project_dir: &Path) -> std::path::PathBuf { - fs::create_dir_all(project_dir.join("src")).expect("Creating src dir does not fail; qed"); - - let manifest_path = project_dir.join("Cargo.toml"); - write_file_if_changed( - &manifest_path, - r#" - [package] - name = "wasm-test" - version = "1.0.0" - edition = "2021" - - [workspace] - "#, - ); - - write_file_if_changed(project_dir.join("src/main.rs"), "fn main() {}"); - manifest_path +struct DummyCrate<'a> { + cargo_command: &'a CargoCommand, + temp: tempfile::TempDir, + manifest_path: PathBuf, + target: RuntimeTarget, } -fn check_wasm_toolchain_installed( - cargo_command: CargoCommand, -) -> Result { - let temp = tempdir().expect("Creating temp dir does not fail; qed"); - let manifest_path = create_minimal_crate(temp.path()).display().to_string(); +impl<'a> DummyCrate<'a> { + /// Creates a minimal dummy crate. + fn new(cargo_command: &'a CargoCommand, target: RuntimeTarget) -> Self { + let temp = tempdir().expect("Creating temp dir does not fail; qed"); + let project_dir = temp.path(); + fs::create_dir_all(project_dir.join("src")).expect("Creating src dir does not fail; qed"); + + let manifest_path = project_dir.join("Cargo.toml"); + write_file_if_changed( + &manifest_path, + r#" + [package] + name = "dummy-crate" + version = "1.0.0" + edition = "2021" + + [workspace] + "#, + ); + + write_file_if_changed(project_dir.join("src/main.rs"), "fn main() {}"); + DummyCrate { cargo_command, temp, manifest_path, target } + } - let prepare_command = |subcommand| { - let mut cmd = cargo_command.command(); + fn prepare_command(&self, subcommand: &str) -> Command { + let mut cmd = self.cargo_command.command(); // Chdir to temp to avoid including project's .cargo/config.toml // by accident - it can happen in some CI environments. - cmd.current_dir(&temp); - cmd.args(&[ - subcommand, - "--target=wasm32-unknown-unknown", - "--manifest-path", - &manifest_path, - ]); + cmd.current_dir(&self.temp); + cmd.arg(subcommand) + .arg(format!("--target={}", self.target.rustc_target())) + .args(&["--manifest-path", &self.manifest_path.display().to_string()]); if super::color_output_enabled() { cmd.arg("--color=always"); } // manually set the `CARGO_TARGET_DIR` to prevent a cargo deadlock - let target_dir = temp.path().join("target").display().to_string(); + let target_dir = self.temp.path().join("target").display().to_string(); cmd.env("CARGO_TARGET_DIR", &target_dir); // Make sure the host's flags aren't used here, e.g. if an alternative linker is specified // in the RUSTFLAGS then the check we do here will break unless we clear these. cmd.env_remove("CARGO_ENCODED_RUSTFLAGS"); cmd.env_remove("RUSTFLAGS"); + // Make sure if we're called from within a `build.rs` the host toolchain won't override a + // rustup toolchain we've picked. + cmd.env_remove("RUSTC"); cmd - }; - - let err_msg = - print_error_message("Rust WASM toolchain is not properly installed; please install it!"); - let build_result = prepare_command("build").output().map_err(|_| err_msg.clone())?; - if !build_result.status.success() { - return match String::from_utf8(build_result.stderr) { - Ok(ref err) if err.contains("the `wasm32-unknown-unknown` target may not be installed") => - Err(print_error_message("Cannot compile the WASM runtime: the `wasm32-unknown-unknown` target is not installed!\n\ - You can install it with `rustup target add wasm32-unknown-unknown` if you're using `rustup`.")), + } - // Apparently this can happen when we're running on a non Tier 1 platform. - Ok(ref err) if err.contains("linker `rust-lld` not found") => - Err(print_error_message("Cannot compile the WASM runtime: `rust-lld` not found!")), + fn get_rustc_version(&self) -> String { + let mut run_cmd = self.prepare_command("rustc"); + run_cmd.args(&["-q", "--", "--version"]); + run_cmd + .output() + .ok() + .and_then(|o| String::from_utf8(o.stdout).ok()) + .unwrap_or_else(|| "unknown rustc version".into()) + } - Ok(ref err) => Err(format!( - "{}\n\n{}\n{}\n{}{}\n", - err_msg, - style("Further error information:").yellow().bold(), - style("-".repeat(60)).yellow().bold(), - err, - style("-".repeat(60)).yellow().bold(), - )), - - Err(_) => Err(err_msg), - }; + fn get_sysroot(&self) -> Option { + let mut sysroot_cmd = self.prepare_command("rustc"); + sysroot_cmd.args(&["-q", "--", "--print", "sysroot"]); + sysroot_cmd.output().ok().and_then(|o| String::from_utf8(o.stdout).ok()) } - let mut run_cmd = prepare_command("rustc"); - run_cmd.args(&["-q", "--", "--version"]); + fn try_build(&self) -> Result<(), Option> { + let Ok(result) = self.prepare_command("build").output() else { return Err(None) }; + if !result.status.success() { + return Err(Some(String::from_utf8_lossy(&result.stderr).into())); + } + Ok(()) + } +} - let version = run_cmd - .output() - .ok() - .and_then(|o| String::from_utf8(o.stdout).ok()) - .unwrap_or_else(|| "unknown rustc version".into()); +fn check_wasm_toolchain_installed( + cargo_command: CargoCommand, +) -> Result { + let dummy_crate = DummyCrate::new(&cargo_command, RuntimeTarget::Wasm); + + if let Err(error) = dummy_crate.try_build() { + let basic_error_message = colorize_error_message( + "Rust WASM toolchain is not properly installed; please install it!", + ); + return match error { + None => Err(basic_error_message), + Some(error) if error.contains("the `wasm32-unknown-unknown` target may not be installed") => { + Err(colorize_error_message("Cannot compile the WASM runtime: the `wasm32-unknown-unknown` target is not installed!\n\ + You can install it with `rustup target add wasm32-unknown-unknown` if you're using `rustup`.")) + }, + // Apparently this can happen when we're running on a non Tier 1 platform. + Some(ref error) if error.contains("linker `rust-lld` not found") => + Err(colorize_error_message("Cannot compile the WASM runtime: `rust-lld` not found!")), + Some(error) => Err(format!( + "{}\n\n{}\n{}\n{}{}\n", + basic_error_message, + colorize_aux_message("Further error information:"), + colorize_aux_message(&"-".repeat(60)), + error, + colorize_aux_message(&"-".repeat(60)), + )) + } + } + let version = dummy_crate.get_rustc_version(); if crate::build_std_required() { - let mut sysroot_cmd = prepare_command("rustc"); - sysroot_cmd.args(&["-q", "--", "--print", "sysroot"]); - if let Some(sysroot) = - sysroot_cmd.output().ok().and_then(|o| String::from_utf8(o.stdout).ok()) - { + if let Some(sysroot) = dummy_crate.get_sysroot() { let src_path = Path::new(sysroot.trim()).join("lib").join("rustlib").join("src").join("rust"); if !src_path.exists() { - return Err(print_error_message( + return Err(colorize_error_message( "Cannot compile the WASM runtime: no standard library sources found!\n\ You can install them with `rustup component add rust-src` if you're using `rustup`.", )) diff --git a/substrate/utils/wasm-builder/src/wasm_project.rs b/substrate/utils/wasm-builder/src/wasm_project.rs index ded6b2188b0..99e072f2682 100644 --- a/substrate/utils/wasm-builder/src/wasm_project.rs +++ b/substrate/utils/wasm-builder/src/wasm_project.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{write_file_if_changed, CargoCommandVersioned, OFFLINE}; +use crate::{write_file_if_changed, CargoCommandVersioned, RuntimeTarget, OFFLINE}; use build_helper::rerun_if_changed; use cargo_metadata::{DependencyKind, Metadata, MetadataCommand}; @@ -112,6 +112,7 @@ fn crate_metadata(cargo_manifest: &Path) -> Metadata { /// /// The path to the compact runtime binary and the bloaty runtime binary. pub(crate) fn create_and_compile( + target: RuntimeTarget, project_cargo_toml: &Path, default_rustflags: &str, cargo_cmd: CargoCommandVersioned, @@ -120,11 +121,12 @@ pub(crate) fn create_and_compile( check_for_runtime_version_section: bool, ) -> (Option, WasmBinaryBloaty) { let runtime_workspace_root = get_wasm_workspace_root(); - let runtime_workspace = runtime_workspace_root.join("wbuild"); + let runtime_workspace = runtime_workspace_root.join(target.build_subdirectory()); let crate_metadata = crate_metadata(project_cargo_toml); let project = create_project( + target, project_cargo_toml, &runtime_workspace, &crate_metadata, @@ -132,13 +134,58 @@ pub(crate) fn create_and_compile( features_to_enable, ); - let build_config = BuildConfiguration::detect(&project); + let build_config = BuildConfiguration::detect(target, &project); // Build the bloaty runtime blob - build_bloaty_blob(&build_config.blob_build_profile, &project, default_rustflags, cargo_cmd); + let raw_blob_path = build_bloaty_blob( + target, + &build_config.blob_build_profile, + &project, + default_rustflags, + cargo_cmd, + ); + let (final_blob_binary, bloaty_blob_binary) = match target { + RuntimeTarget::Wasm => compile_wasm( + project_cargo_toml, + &project, + bloaty_blob_out_name_override, + check_for_runtime_version_section, + &build_config, + ), + RuntimeTarget::Riscv => { + let out_name = bloaty_blob_out_name_override + .unwrap_or_else(|| get_blob_name(target, project_cargo_toml)); + let out_path = project.join(format!("{out_name}.polkavm")); + fs::copy(raw_blob_path, &out_path).expect("copying the runtime blob should never fail"); + (None, WasmBinaryBloaty(out_path)) + }, + }; + + generate_rerun_if_changed_instructions( + project_cargo_toml, + &project, + &runtime_workspace, + final_blob_binary.as_ref(), + &bloaty_blob_binary, + ); + + if let Err(err) = adjust_mtime(&bloaty_blob_binary, final_blob_binary.as_ref()) { + build_helper::warning!("Error while adjusting the mtime of the blob binaries: {}", err) + } + + (final_blob_binary, bloaty_blob_binary) +} + +fn compile_wasm( + project_cargo_toml: &Path, + project: &Path, + bloaty_blob_out_name_override: Option, + check_for_runtime_version_section: bool, + build_config: &BuildConfiguration, +) -> (Option, WasmBinaryBloaty) { // Get the name of the bloaty runtime blob. - let bloaty_blob_default_name = get_blob_name(project_cargo_toml); + let bloaty_blob_default_name = get_blob_name(RuntimeTarget::Wasm, project_cargo_toml); let bloaty_blob_out_name = bloaty_blob_out_name_override.unwrap_or_else(|| bloaty_blob_default_name.clone()); @@ -183,19 +230,6 @@ pub(crate) fn create_and_compile( }); let final_blob_binary = compact_compressed_blob_path.or(compact_blob_path); - - generate_rerun_if_changed_instructions( - project_cargo_toml, - &project, - &runtime_workspace, - final_blob_binary.as_ref(), - &bloaty_blob_binary, - ); - - if let Err(err) = adjust_mtime(&bloaty_blob_binary, final_blob_binary.as_ref()) { - build_helper::warning!("Error while adjusting the mtime of the blob binaries: {}", err) - } - (final_blob_binary, bloaty_blob_binary) } @@ -314,8 +348,12 @@ fn get_crate_name(cargo_manifest: &Path) -> String { } /// Returns the name for the blob binary. -fn get_blob_name(cargo_manifest: &Path) -> String { - get_crate_name(cargo_manifest).replace('-', "_") +fn get_blob_name(target: RuntimeTarget, cargo_manifest: &Path) -> String { + let crate_name = get_crate_name(cargo_manifest); + match target { + RuntimeTarget::Wasm => crate_name.replace('-', "_"), + RuntimeTarget::Riscv => crate_name, + } } /// Returns the root path of the wasm workspace. @@ -336,6 +374,7 @@ fn get_wasm_workspace_root() -> PathBuf { } fn create_project_cargo_toml( + target: RuntimeTarget, wasm_workspace: &Path, workspace_root_path: &Path, crate_name: &str, @@ -396,17 +435,18 @@ fn create_project_cargo_toml( } let mut package = Table::new(); - package.insert("name".into(), format!("{}-wasm", crate_name).into()); + package.insert("name".into(), format!("{}-blob", crate_name).into()); package.insert("version".into(), "1.0.0".into()); package.insert("edition".into(), "2021".into()); wasm_workspace_toml.insert("package".into(), package.into()); - let mut lib = Table::new(); - lib.insert("name".into(), wasm_binary.into()); - lib.insert("crate-type".into(), vec!["cdylib".to_string()].into()); - - wasm_workspace_toml.insert("lib".into(), lib.into()); + if target == RuntimeTarget::Wasm { + let mut lib = Table::new(); + lib.insert("name".into(), wasm_binary.into()); + lib.insert("crate-type".into(), vec!["cdylib".to_string()].into()); + wasm_workspace_toml.insert("lib".into(), lib.into()); + } let mut dependencies = Table::new(); @@ -527,6 +567,7 @@ fn has_runtime_wasm_feature_declared( /// /// The path to the created wasm project. fn create_project( + target: RuntimeTarget, project_cargo_toml: &Path, wasm_workspace: &Path, crate_metadata: &Metadata, @@ -535,7 +576,7 @@ fn create_project( ) -> PathBuf { let crate_name = get_crate_name(project_cargo_toml); let crate_path = project_cargo_toml.parent().expect("Parent path exists; qed"); - let wasm_binary = get_blob_name(project_cargo_toml); + let wasm_binary = get_blob_name(target, project_cargo_toml); let wasm_project_folder = wasm_workspace.join(&crate_name); fs::create_dir_all(wasm_project_folder.join("src")) @@ -552,6 +593,7 @@ fn create_project( enabled_features.extend(features_to_enable.into_iter()); create_project_cargo_toml( + target, &wasm_project_folder, workspace_root_path, &crate_name, @@ -560,10 +602,20 @@ fn create_project( enabled_features.into_iter(), ); - write_file_if_changed( - wasm_project_folder.join("src/lib.rs"), - "#![no_std] pub use wasm_project::*;", - ); + match target { + RuntimeTarget::Wasm => { + write_file_if_changed( + wasm_project_folder.join("src/lib.rs"), + "#![no_std] pub use wasm_project::*;", + ); + }, + RuntimeTarget::Riscv => { + write_file_if_changed( + wasm_project_folder.join("src/main.rs"), + "#![no_std] #![no_main] pub use wasm_project::*;", + ); + }, + } if let Some(crate_lock_file) = find_cargo_lock(project_cargo_toml) { // Use the `Cargo.lock` of the main project. @@ -641,14 +693,15 @@ impl BuildConfiguration { /// # Note /// /// Can be overriden by setting [`crate::WASM_BUILD_TYPE_ENV`]. - fn detect(wasm_project: &Path) -> Self { + fn detect(target: RuntimeTarget, wasm_project: &Path) -> Self { let (name, overriden) = if let Ok(name) = env::var(crate::WASM_BUILD_TYPE_ENV) { (name, true) } else { // First go backwards to the beginning of the target directory. - // Then go forwards to find the "wbuild" directory. + // Then go forwards to find the build subdirectory. // We need to go backwards first because when starting from the root there - // might be a chance that someone has a "wbuild" directory somewhere in the path. + // might be a chance that someone has a directory somewhere in the path with the same + // name. let name = wasm_project .components() .rev() @@ -656,9 +709,9 @@ impl BuildConfiguration { .collect::>() .iter() .rev() - .take_while(|c| c.as_os_str() != "wbuild") + .take_while(|c| c.as_os_str() != target.build_subdirectory()) .last() - .expect("We put the wasm project within a `target/.../wbuild` path; qed") + .expect("We put the runtime project within a `target/.../[rw]build` path; qed") .as_os_str() .to_str() .expect("All our profile directory names are ascii; qed") @@ -711,22 +764,34 @@ fn offline_build() -> bool { /// Build the project and create the bloaty runtime blob. fn build_bloaty_blob( + target: RuntimeTarget, blob_build_profile: &Profile, project: &Path, default_rustflags: &str, cargo_cmd: CargoCommandVersioned, -) { +) -> PathBuf { let manifest_path = project.join("Cargo.toml"); let mut build_cmd = cargo_cmd.command(); - let rustflags = format!( - "-C target-cpu=mvp -C target-feature=-sign-ext -C link-arg=--export-table {} {}", - default_rustflags, - env::var(crate::WASM_BUILD_RUSTFLAGS_ENV).unwrap_or_default(), - ); + let mut rustflags = String::new(); + match target { + RuntimeTarget::Wasm => { + rustflags.push_str( + "-C target-cpu=mvp -C target-feature=-sign-ext -C link-arg=--export-table ", + ); + }, + RuntimeTarget::Riscv => { + rustflags.push_str("-C target-feature=+lui-addi-fusion -C relocation-model=pie -C link-arg=--emit-relocs -C link-arg=--unique "); + }, + } + + rustflags.push_str(default_rustflags); + rustflags.push_str(" --cfg substrate_runtime "); + rustflags.push_str(&env::var(crate::WASM_BUILD_RUSTFLAGS_ENV).unwrap_or_default()); build_cmd - .args(&["rustc", "--target=wasm32-unknown-unknown"]) + .arg("rustc") + .arg(format!("--target={}", target.rustc_target())) .arg(format!("--manifest-path={}", manifest_path.display())) .env("RUSTFLAGS", rustflags) // Manually set the `CARGO_TARGET_DIR` to prevent a cargo deadlock (cargo locks a target dir @@ -737,6 +802,9 @@ fn build_bloaty_blob( // our own `RUSTFLAGS` and thus, we need to remove this. Otherwise cargo favors this // env variable. .env_remove("CARGO_ENCODED_RUSTFLAGS") + // Make sure if we're called from within a `build.rs` the host toolchain won't override a + // rustup toolchain we've picked. + .env_remove("RUSTC") // We don't want to call ourselves recursively .env(crate::SKIP_BUILD_ENV, ""); @@ -778,6 +846,52 @@ fn build_bloaty_blob( if build_cmd.status().map(|s| s.success()).is_err() { process::exit(1); } + + let blob_name = get_blob_name(target, &manifest_path); + let target_directory = project + .join("target") + .join(target.rustc_target()) + .join(blob_build_profile.directory()); + match target { + RuntimeTarget::Riscv => { + let elf_path = target_directory.join(&blob_name); + let elf_metadata = match elf_path.metadata() { + Ok(path) => path, + Err(error) => + panic!("internal error: couldn't read the metadata of {elf_path:?}: {error}"), + }; + + let polkavm_path = target_directory.join(format!("{}.polkavm", blob_name)); + if polkavm_path + .metadata() + .map(|polkavm_metadata| { + polkavm_metadata.modified().unwrap() >= elf_metadata.modified().unwrap() + }) + .unwrap_or(true) + { + let blob_bytes = + std::fs::read(elf_path).expect("binary always exists after its built"); + + let mut config = polkavm_linker::Config::default(); + config.set_strip(true); // TODO: This shouldn't always be done. + + let program = match polkavm_linker::program_from_elf(config, &blob_bytes) { + Ok(program) => program, + Err(error) => { + println!("Failed to link the runtime blob; this is probably a bug!"); + println!("Linking error: {error}"); + process::exit(1); + }, + }; + + std::fs::write(&polkavm_path, program.as_bytes()) + .expect("writing the blob to a file always works"); + } + + polkavm_path + }, + RuntimeTarget::Wasm => target_directory.join(format!("{}.wasm", blob_name)), + } } fn compact_wasm( @@ -786,7 +900,7 @@ fn compact_wasm( cargo_manifest: &Path, out_name: &str, ) -> Option { - let default_out_name = get_blob_name(cargo_manifest); + let default_out_name = get_blob_name(RuntimeTarget::Wasm, cargo_manifest); let in_path = project .join("target/wasm32-unknown-unknown") .join(inner_profile.directory()) @@ -973,6 +1087,7 @@ fn generate_rerun_if_changed_instructions( println!("cargo:rerun-if-env-changed={}", crate::WASM_TARGET_DIRECTORY); println!("cargo:rerun-if-env-changed={}", crate::WASM_BUILD_TOOLCHAIN); println!("cargo:rerun-if-env-changed={}", crate::WASM_BUILD_STD); + println!("cargo:rerun-if-env-changed={}", crate::RUNTIME_TARGET); } /// Track files and paths related to the given package to rerun `build.rs` on any relevant change. @@ -1018,7 +1133,7 @@ fn copy_blob_to_target_directory(cargo_manifest: &Path, blob_binary: &WasmBinary fs::copy( blob_binary.wasm_binary_path(), - target_dir.join(format!("{}.wasm", get_blob_name(cargo_manifest))), + target_dir.join(format!("{}.wasm", get_blob_name(RuntimeTarget::Wasm, cargo_manifest))), ) .expect("Copies blob binary to `WASM_TARGET_DIRECTORY`."); } -- GitLab From 966a886439f4c5ef0bc81f32b8a189d758a2505f Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Sat, 3 Feb 2024 12:50:55 +0100 Subject: [PATCH 009/133] Contracts: Stabilize `caller_is_root` API (#3154) Can this API be marked stable? Implemented in [solang here](https://github.com/hyperledger/solang/pull/1620) --------- Signed-off-by: Cyrill Leutwiler --- prdoc/pr_3154.prdoc | 9 +++++++++ substrate/frame/contracts/src/wasm/runtime.rs | 1 - substrate/frame/contracts/uapi/src/host.rs | 3 --- 3 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 prdoc/pr_3154.prdoc diff --git a/prdoc/pr_3154.prdoc b/prdoc/pr_3154.prdoc new file mode 100644 index 00000000000..2dfc1b27810 --- /dev/null +++ b/prdoc/pr_3154.prdoc @@ -0,0 +1,9 @@ +title: "Contracts: Stabilize caller_is_root API" + +doc: + - audience: Runtime Dev + description: | + Removed the `#[unstable]` attrribute on `caller_is_root` host function. + +crates: + - name: pallet-contracts diff --git a/substrate/frame/contracts/src/wasm/runtime.rs b/substrate/frame/contracts/src/wasm/runtime.rs index 3e49b68f86b..3af0d04a3ad 100644 --- a/substrate/frame/contracts/src/wasm/runtime.rs +++ b/substrate/frame/contracts/src/wasm/runtime.rs @@ -1542,7 +1542,6 @@ pub mod env { /// Checks whether the caller of the current contract is root. /// See [`pallet_contracts_uapi::HostFn::caller_is_root`]. - #[unstable] fn caller_is_root(ctx: _, _memory: _) -> Result { ctx.charge_gas(RuntimeCosts::CallerIsRoot)?; Ok(ctx.ext.caller_is_root() as u32) diff --git a/substrate/frame/contracts/uapi/src/host.rs b/substrate/frame/contracts/uapi/src/host.rs index 21350bc4eb3..c8b9ae8b2de 100644 --- a/substrate/frame/contracts/uapi/src/host.rs +++ b/substrate/frame/contracts/uapi/src/host.rs @@ -277,9 +277,6 @@ pub trait HostFn { /// /// A return value of `true` indicates that this contract is being called by a root origin, /// and `false` indicates that the caller is a signed origin. - #[deprecated( - note = "Unstable function. Behaviour can change without further notice. Use only for testing." - )] fn caller_is_root() -> u32; /// Clear the value at the given key in the contract storage. -- GitLab From 12e5e19c2033383fc914bcc286ad16ae565d4734 Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Sat, 3 Feb 2024 22:47:05 +0200 Subject: [PATCH 010/133] Expose internal functions used by `spawn_tasks` (#3166) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows to build a custom version of `spawn_tasks` with less copy-paste required. Resolves https://github.com/paritytech/polkadot-sdk/issues/2110 --------- Co-authored-by: Bastian Köcher --- prdoc/pr_3166.prdoc | 9 +++++++++ substrate/client/service/src/builder.rs | 11 +++++++---- substrate/client/service/src/lib.rs | 2 +- 3 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 prdoc/pr_3166.prdoc diff --git a/prdoc/pr_3166.prdoc b/prdoc/pr_3166.prdoc new file mode 100644 index 00000000000..adf7f50e4e9 --- /dev/null +++ b/prdoc/pr_3166.prdoc @@ -0,0 +1,9 @@ +title: Expose internal functions used by `spawn_tasks` + +doc: + - audience: Node Dev + description: | + This allows to build a custom version of `spawn_tasks` with less copy-paste required + +crates: + - name: sc-service diff --git a/substrate/client/service/src/builder.rs b/substrate/client/service/src/builder.rs index bc848e8d4b2..31d63c6a81d 100644 --- a/substrate/client/service/src/builder.rs +++ b/substrate/client/service/src/builder.rs @@ -460,7 +460,7 @@ where spawn_handle.spawn( "on-transaction-imported", Some("transaction-pool"), - transaction_notifications( + propagate_transaction_notifications( transaction_pool.clone(), tx_handler_controller, telemetry.clone(), @@ -532,7 +532,8 @@ where Ok(rpc_handlers) } -async fn transaction_notifications( +/// Returns a future that forwards imported transactions to the transaction networking protocol. +pub async fn propagate_transaction_notifications( transaction_pool: Arc, tx_handler_controller: sc_network_transactions::TransactionsHandlerController< ::Hash, @@ -560,7 +561,8 @@ async fn transaction_notifications( .await; } -fn init_telemetry( +/// Initialize telemetry with provided configuration and return telemetry handle +pub fn init_telemetry( config: &mut Configuration, network: Network, client: Arc, @@ -598,7 +600,8 @@ where Ok(telemetry.handle()) } -fn gen_rpc_module( +/// Generate RPC module using provided configuration +pub fn gen_rpc_module( deny_unsafe: DenyUnsafe, spawn_handle: SpawnTaskHandle, client: Arc, diff --git a/substrate/client/service/src/lib.rs b/substrate/client/service/src/lib.rs index 875cb9ca79e..a76e1544bfb 100644 --- a/substrate/client/service/src/lib.rs +++ b/substrate/client/service/src/lib.rs @@ -368,7 +368,7 @@ mod waiting { } /// Starts RPC servers. -fn start_rpc_servers( +pub fn start_rpc_servers( config: &Configuration, gen_rpc_module: R, rpc_id_provider: Option>, -- GitLab From 90849b66b96a77c8e6c6533c58bf5ae89bcea6fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 07:37:13 +0100 Subject: [PATCH 011/133] Bump paritytech/review-bot from 2.3.0 to 2.4.0 (#3119) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [paritytech/review-bot](https://github.com/paritytech/review-bot) from 2.3.0 to 2.4.0.
Release notes

Sourced from paritytech/review-bot's releases.

v2.4.0

What's Changed

Full Changelog: https://github.com/paritytech/review-bot/compare/v2.3.1...v2.4.0

v2.3.1

What's Changed

Full Changelog: https://github.com/paritytech/review-bot/compare/v2.3.0...v2.3.1

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=paritytech/review-bot&package-manager=github_actions&previous-version=2.3.0&new-version=2.4.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/review-bot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/review-bot.yml b/.github/workflows/review-bot.yml index 0a7e80f007c..5b036115b23 100644 --- a/.github/workflows/review-bot.yml +++ b/.github/workflows/review-bot.yml @@ -23,7 +23,7 @@ jobs: app_id: ${{ secrets.REVIEW_APP_ID }} private_key: ${{ secrets.REVIEW_APP_KEY }} - name: "Evaluates PR reviews and assigns reviewers" - uses: paritytech/review-bot@v2.3.0 + uses: paritytech/review-bot@v2.4.0 with: repo-token: ${{ steps.app_token.outputs.token }} team-token: ${{ steps.app_token.outputs.token }} -- GitLab From f9f886886b62667b5eaf0bd7e7ebbcf7f5362bf1 Mon Sep 17 00:00:00 2001 From: Alexandru Gheorghe <49718502+alexggh@users.noreply.github.com> Date: Mon, 5 Feb 2024 08:46:22 +0200 Subject: [PATCH 012/133] Introduce approval-voting/distribution benchmark (#2621) ## Summary Built on top of the tooling and ideas introduced in https://github.com/paritytech/polkadot-sdk/pull/2528, this PR introduces a synthetic benchmark for measuring and assessing the performance characteristics of the approval-voting and approval-distribution subsystems. Currently this allows, us to simulate the behaviours of these systems based on the following dimensions: ``` TestConfiguration: # Test 1 - objective: !ApprovalsTest last_considered_tranche: 89 min_coalesce: 1 max_coalesce: 6 enable_assignments_v2: true send_till_tranche: 60 stop_when_approved: false coalesce_tranche_diff: 12 workdir_prefix: "/tmp" num_no_shows_per_candidate: 0 approval_distribution_expected_tof: 6.0 approval_distribution_cpu_ms: 3.0 approval_voting_cpu_ms: 4.30 n_validators: 500 n_cores: 100 n_included_candidates: 100 min_pov_size: 1120 max_pov_size: 5120 peer_bandwidth: 524288000000 bandwidth: 524288000000 latency: min_latency: secs: 0 nanos: 1000000 max_latency: secs: 0 nanos: 100000000 error: 0 num_blocks: 10 ``` ## The approach 1. We build a real overseer with the real implementations for approval-voting and approval-distribution subsystems. 2. For a given network size, for each validator we pre-computed all potential assignments and approvals it would send, because this a computation heavy operation this will be cached on a file on disk and be re-used if the generation parameters don't change. 3. The messages will be sent accordingly to the configured parameters and those are split into 3 main benchmarking scenarios. ## Benchmarking scenarios ### Best case scenario *approvals_throughput_best_case.yaml* It send to the approval-distribution only the minimum required tranche to gathered the needed_approvals, so that a candidate is approved. ### Behaviour in the presence of no-shows *approvals_no_shows.yaml* It sends the tranche needed to approve a candidate when we have a maximum of *num_no_shows_per_candidate* tranches with no-shows for each candidate. ### Maximum throughput *approvals_throughput.yaml* It sends all the tranches for each block and measures the used CPU and necessary network bandwidth. by the approval-voting and approval-distribution subsystem. ## How to run it ``` cargo run -p polkadot-subsystem-bench --release -- test-sequence --path polkadot/node/subsystem-bench/examples/approvals_throughput.yaml ``` ## Evaluating performance ### Use the real subsystems metrics If you follow the steps in https://github.com/paritytech/polkadot-sdk/tree/master/polkadot/node/subsystem-bench#install-grafana for installing locally prometheus and grafana, all real metrics for the `approval-distribution`, `approval-voting` and overseer are available. E.g: Screenshot 2023-12-05 at 11 07 46 Screenshot 2023-12-05 at 11 09 42 Screenshot 2023-12-05 at 11 10 15 Screenshot 2023-12-05 at 11 10 52 ### Profile with pyroscope 1. Setup pyroscope following the steps in https://github.com/paritytech/polkadot-sdk/tree/master/polkadot/node/subsystem-bench#install-pyroscope, then run any of the benchmark scenario with `--profile` as the arguments. 2. Open the pyroscope dashboard in grafana, e.g: Screenshot 2024-01-09 at 17 09 58 ### Useful logs 1. Network bandwidth requirements: ``` Payload bytes received from peers: 503993 KiB total, 50399 KiB/block Payload bytes sent to peers: 629971 KiB total, 62997 KiB/block ``` 2. Cpu usage by the approval-distribution/approval-voting subsystems. ``` approval-distribution CPU usage 84.061s approval-distribution CPU usage per block 8.406s approval-voting CPU usage 96.532s approval-voting CPU usage per block 9.653s ``` 3. Time passed until a given block is approved ``` Chain selection approved after 3500 ms hash=0x0101010101010101010101010101010101010101010101010101010101010101 Chain selection approved after 4500 ms hash=0x0202020202020202020202020202020202020202020202020202020202020202 ``` ### Using benchmark to quantify improvements from https://github.com/paritytech/polkadot-sdk/pull/1178 + https://github.com/paritytech/polkadot-sdk/pull/1191 Using a versi-node we compare the scenarios where all new optimisations are disabled with a scenarios where tranche0 assignments are sent in a single message and a conservative simulation where the coalescing of approvals gives us just 50% reduction in the number of messages we send. Overall, what we see is a speedup of around 30-40% in the time it takes to process the necessary messages and a 30-40% reduction in the necessary bandwidth. #### Best case scenario comparison(minimum required tranches sent). Unoptimised ``` Number of blocks: 10 Payload bytes received from peers: 53289 KiB total, 5328 KiB/block Payload bytes sent to peers: 52489 KiB total, 5248 KiB/block approval-distribution CPU usage 6.732s approval-distribution CPU usage per block 0.673s approval-voting CPU usage 9.523s approval-voting CPU usage per block 0.952s ``` vs Optimisation enabled ``` Number of blocks: 10 Payload bytes received from peers: 32141 KiB total, 3214 KiB/block Payload bytes sent to peers: 37314 KiB total, 3731 KiB/block approval-distribution CPU usage 4.658s approval-distribution CPU usage per block 0.466s approval-voting CPU usage 6.236s approval-voting CPU usage per block 0.624s ``` #### Worst case all tranches sent, very unlikely happens when sharding breaks. Unoptimised ``` Number of blocks: 10 Payload bytes received from peers: 746393 KiB total, 74639 KiB/block Payload bytes sent to peers: 729151 KiB total, 72915 KiB/block approval-distribution CPU usage 118.681s approval-distribution CPU usage per block 11.868s approval-voting CPU usage 124.118s approval-voting CPU usage per block 12.412s ``` vs optimised ``` Number of blocks: 10 Payload bytes received from peers: 503993 KiB total, 50399 KiB/block Payload bytes sent to peers: 629971 KiB total, 62997 KiB/block approval-distribution CPU usage 84.061s approval-distribution CPU usage per block 8.406s approval-voting CPU usage 96.532s approval-voting CPU usage per block 9.653s ``` ## TODOs [x] Polish implementation. [x] Use what we have so far to evaluate https://github.com/paritytech/polkadot-sdk/pull/1191 before merging. [x] List of features and additional dimensions we want to use for benchmarking. [x] Run benchmark on hardware similar with versi and kusama nodes. [ ] Add benchmark to be run in CI for catching regression in performance. [ ] Rebase on latest changes for network emulation. --------- Signed-off-by: Andrei Sandu Signed-off-by: Alexandru Gheorghe Co-authored-by: Andrei Sandu Co-authored-by: Andrei Sandu <54316454+sandreim@users.noreply.github.com> --- Cargo.lock | 15 +- .../node/core/approval-voting/src/criteria.rs | 8 +- polkadot/node/core/approval-voting/src/lib.rs | 41 +- .../node/core/approval-voting/src/tests.rs | 4 +- .../node/core/approval-voting/src/time.rs | 24 +- polkadot/node/subsystem-bench/Cargo.toml | 13 + .../examples/approvals_no_shows.yaml | 18 + .../examples/approvals_throughput.yaml | 19 + .../approvals_throughput_best_case.yaml | 18 + ...s_throughput_no_optimisations_enabled.yaml | 18 + .../subsystem-bench/src/approval/helpers.rs | 207 ++++ .../src/approval/message_generator.rs | 686 +++++++++++ .../src/approval/mock_chain_selection.rs | 66 + .../node/subsystem-bench/src/approval/mod.rs | 1059 +++++++++++++++++ .../src/approval/test_message.rs | 304 +++++ .../src/availability/av_store_helpers.rs | 16 +- .../subsystem-bench/src/availability/mod.rs | 26 +- polkadot/node/subsystem-bench/src/cli.rs | 4 + .../subsystem-bench/src/core/configuration.rs | 92 +- .../node/subsystem-bench/src/core/display.rs | 9 +- .../subsystem-bench/src/core/environment.rs | 33 +- .../node/subsystem-bench/src/core/keyring.rs | 8 +- .../src/core/mock/chain_api.rs | 48 +- .../node/subsystem-bench/src/core/mock/mod.rs | 18 +- .../src/core/mock/network_bridge.rs | 41 +- .../src/core/mock/runtime_api.rs | 114 +- polkadot/node/subsystem-bench/src/core/mod.rs | 2 + .../node/subsystem-bench/src/core/network.rs | 57 +- .../subsystem-bench/src/subsystem-bench.rs | 16 +- 29 files changed, 2857 insertions(+), 127 deletions(-) create mode 100644 polkadot/node/subsystem-bench/examples/approvals_no_shows.yaml create mode 100644 polkadot/node/subsystem-bench/examples/approvals_throughput.yaml create mode 100644 polkadot/node/subsystem-bench/examples/approvals_throughput_best_case.yaml create mode 100644 polkadot/node/subsystem-bench/examples/approvals_throughput_no_optimisations_enabled.yaml create mode 100644 polkadot/node/subsystem-bench/src/approval/helpers.rs create mode 100644 polkadot/node/subsystem-bench/src/approval/message_generator.rs create mode 100644 polkadot/node/subsystem-bench/src/approval/mock_chain_selection.rs create mode 100644 polkadot/node/subsystem-bench/src/approval/mod.rs create mode 100644 polkadot/node/subsystem-bench/src/approval/test_message.rs diff --git a/Cargo.lock b/Cargo.lock index 663d8ccdd39..540bcc327a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13467,6 +13467,7 @@ version = "1.0.0" dependencies = [ "assert_matches", "async-trait", + "bincode", "bitvec", "clap 4.4.18", "clap-num", @@ -13475,16 +13476,19 @@ dependencies = [ "env_logger 0.9.3", "futures", "futures-timer", + "hex", "itertools 0.11.0", "kvdb-memorydb", "log", "orchestra", "parity-scale-codec", "paste", + "polkadot-approval-distribution", "polkadot-availability-bitfield-distribution", "polkadot-availability-distribution", "polkadot-availability-recovery", "polkadot-erasure-coding", + "polkadot-node-core-approval-voting", "polkadot-node-core-av-store", "polkadot-node-core-chain-api", "polkadot-node-metrics", @@ -13501,17 +13505,24 @@ dependencies = [ "pyroscope", "pyroscope_pprofrs", "rand", + "rand_chacha 0.3.1", + "rand_core 0.6.4", "rand_distr", "sc-keystore", "sc-network", "sc-service", + "schnorrkel 0.9.1", "serde", "serde_yaml", + "sha1", "sp-application-crypto", "sp-consensus", + "sp-consensus-babe", "sp-core", "sp-keyring", "sp-keystore", + "sp-runtime", + "sp-timestamp", "substrate-prometheus-endpoint", "tokio", "tracing-gum", @@ -17208,9 +17219,9 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", diff --git a/polkadot/node/core/approval-voting/src/criteria.rs b/polkadot/node/core/approval-voting/src/criteria.rs index 1af61e72d7a..1ebea2641b6 100644 --- a/polkadot/node/core/approval-voting/src/criteria.rs +++ b/polkadot/node/core/approval-voting/src/criteria.rs @@ -55,11 +55,11 @@ pub struct OurAssignment { } impl OurAssignment { - pub(crate) fn cert(&self) -> &AssignmentCertV2 { + pub fn cert(&self) -> &AssignmentCertV2 { &self.cert } - pub(crate) fn tranche(&self) -> DelayTranche { + pub fn tranche(&self) -> DelayTranche { self.tranche } @@ -225,7 +225,7 @@ fn assigned_core_transcript(core_index: CoreIndex) -> Transcript { /// Information about the world assignments are being produced in. #[derive(Clone, Debug)] -pub(crate) struct Config { +pub struct Config { /// The assignment public keys for validators. assignment_keys: Vec, /// The groups of validators assigned to each core. @@ -321,7 +321,7 @@ impl AssignmentCriteria for RealAssignmentCriteria { /// different times. The idea is that most assignments are never triggered and fall by the wayside. /// /// This will not assign to anything the local validator was part of the backing group for. -pub(crate) fn compute_assignments( +pub fn compute_assignments( keystore: &LocalKeystore, relay_vrf_story: RelayVRFStory, config: &Config, diff --git a/polkadot/node/core/approval-voting/src/lib.rs b/polkadot/node/core/approval-voting/src/lib.rs index 3161d6186a1..456ae319787 100644 --- a/polkadot/node/core/approval-voting/src/lib.rs +++ b/polkadot/node/core/approval-voting/src/lib.rs @@ -92,11 +92,11 @@ use time::{slot_number_to_tick, Clock, ClockExt, DelayedApprovalTimer, SystemClo mod approval_checking; pub mod approval_db; mod backend; -mod criteria; +pub mod criteria; mod import; mod ops; mod persisted_entries; -mod time; +pub mod time; use crate::{ approval_checking::{Check, TranchesToApproveResult}, @@ -159,6 +159,7 @@ pub struct ApprovalVotingSubsystem { db: Arc, mode: Mode, metrics: Metrics, + clock: Box, } #[derive(Clone)] @@ -444,6 +445,25 @@ impl ApprovalVotingSubsystem { keystore: Arc, sync_oracle: Box, metrics: Metrics, + ) -> Self { + ApprovalVotingSubsystem::with_config_and_clock( + config, + db, + keystore, + sync_oracle, + metrics, + Box::new(SystemClock {}), + ) + } + + /// Create a new approval voting subsystem with the given keystore, config, and database. + pub fn with_config_and_clock( + config: Config, + db: Arc, + keystore: Arc, + sync_oracle: Box, + metrics: Metrics, + clock: Box, ) -> Self { ApprovalVotingSubsystem { keystore, @@ -452,6 +472,7 @@ impl ApprovalVotingSubsystem { db_config: DatabaseConfig { col_approval_data: config.col_approval_data }, mode: Mode::Syncing(sync_oracle), metrics, + clock, } } @@ -493,15 +514,10 @@ fn db_sanity_check(db: Arc, config: DatabaseConfig) -> SubsystemRe impl ApprovalVotingSubsystem { fn start(self, ctx: Context) -> SpawnedSubsystem { let backend = DbBackend::new(self.db.clone(), self.db_config); - let future = run::( - ctx, - self, - Box::new(SystemClock), - Box::new(RealAssignmentCriteria), - backend, - ) - .map_err(|e| SubsystemError::with_origin("approval-voting", e)) - .boxed(); + let future = + run::(ctx, self, Box::new(RealAssignmentCriteria), backend) + .map_err(|e| SubsystemError::with_origin("approval-voting", e)) + .boxed(); SpawnedSubsystem { name: "approval-voting-subsystem", future } } @@ -909,7 +925,6 @@ enum Action { async fn run( mut ctx: Context, mut subsystem: ApprovalVotingSubsystem, - clock: Box, assignment_criteria: Box, mut backend: B, ) -> SubsystemResult<()> @@ -923,7 +938,7 @@ where let mut state = State { keystore: subsystem.keystore, slot_duration_millis: subsystem.slot_duration_millis, - clock, + clock: subsystem.clock, assignment_criteria, spans: HashMap::new(), }; diff --git a/polkadot/node/core/approval-voting/src/tests.rs b/polkadot/node/core/approval-voting/src/tests.rs index 7a0bde6a55e..9220e84a255 100644 --- a/polkadot/node/core/approval-voting/src/tests.rs +++ b/polkadot/node/core/approval-voting/src/tests.rs @@ -549,7 +549,7 @@ fn test_harness>( let subsystem = run( context, - ApprovalVotingSubsystem::with_config( + ApprovalVotingSubsystem::with_config_and_clock( Config { col_approval_data: test_constants::TEST_CONFIG.col_approval_data, slot_duration_millis: SLOT_DURATION_MILLIS, @@ -558,8 +558,8 @@ fn test_harness>( Arc::new(keystore), sync_oracle, Metrics::default(), + clock.clone(), ), - clock.clone(), assignment_criteria, backend, ); diff --git a/polkadot/node/core/approval-voting/src/time.rs b/polkadot/node/core/approval-voting/src/time.rs index 61091f3c34c..99dfbe07678 100644 --- a/polkadot/node/core/approval-voting/src/time.rs +++ b/polkadot/node/core/approval-voting/src/time.rs @@ -33,14 +33,14 @@ use std::{ }; use polkadot_primitives::{Hash, ValidatorIndex}; -const TICK_DURATION_MILLIS: u64 = 500; +pub const TICK_DURATION_MILLIS: u64 = 500; /// A base unit of time, starting from the Unix epoch, split into half-second intervals. -pub(crate) type Tick = u64; +pub type Tick = u64; /// A clock which allows querying of the current tick as well as /// waiting for a tick to be reached. -pub(crate) trait Clock { +pub trait Clock { /// Yields the current tick. fn tick_now(&self) -> Tick; @@ -49,7 +49,7 @@ pub(crate) trait Clock { } /// Extension methods for clocks. -pub(crate) trait ClockExt { +pub trait ClockExt { fn tranche_now(&self, slot_duration_millis: u64, base_slot: Slot) -> DelayTranche; } @@ -61,7 +61,8 @@ impl ClockExt for C { } /// A clock which uses the actual underlying system clock. -pub(crate) struct SystemClock; +#[derive(Clone)] +pub struct SystemClock; impl Clock for SystemClock { /// Yields the current tick. @@ -93,11 +94,22 @@ fn tick_to_time(tick: Tick) -> SystemTime { } /// assumes `slot_duration_millis` evenly divided by tick duration. -pub(crate) fn slot_number_to_tick(slot_duration_millis: u64, slot: Slot) -> Tick { +pub fn slot_number_to_tick(slot_duration_millis: u64, slot: Slot) -> Tick { let ticks_per_slot = slot_duration_millis / TICK_DURATION_MILLIS; u64::from(slot) * ticks_per_slot } +/// Converts a tick to the slot number. +pub fn tick_to_slot_number(slot_duration_millis: u64, tick: Tick) -> Slot { + let ticks_per_slot = slot_duration_millis / TICK_DURATION_MILLIS; + (tick / ticks_per_slot).into() +} + +/// Converts a tranche from a slot to the tick number. +pub fn tranche_to_tick(slot_duration_millis: u64, slot: Slot, tranche: u32) -> Tick { + slot_number_to_tick(slot_duration_millis, slot) + tranche as u64 +} + /// A list of delayed futures that gets triggered when the waiting time has expired and it is /// time to sign the candidate. /// We have a timer per relay-chain block. diff --git a/polkadot/node/subsystem-bench/Cargo.toml b/polkadot/node/subsystem-bench/Cargo.toml index f7866f99363..136eccbf685 100644 --- a/polkadot/node/subsystem-bench/Cargo.toml +++ b/polkadot/node/subsystem-bench/Cargo.toml @@ -38,6 +38,9 @@ sp-core = { path = "../../../substrate/primitives/core" } clap = { version = "4.4.18", features = ["derive"] } futures = "0.3.21" futures-timer = "3.0.2" +bincode = "1.3.3" +sha1 = "0.10.6" +hex = "0.4.3" gum = { package = "tracing-gum", path = "../gum" } polkadot-erasure-coding = { package = "polkadot-erasure-coding", path = "../../erasure-coding" } log = "0.4.17" @@ -64,6 +67,16 @@ prometheus_endpoint = { package = "substrate-prometheus-endpoint", path = "../.. prometheus = { version = "0.13.0", default-features = false } serde = "1.0.195" serde_yaml = "0.9" + +polkadot-node-core-approval-voting = { path = "../core/approval-voting" } +polkadot-approval-distribution = { path = "../network/approval-distribution" } +sp-consensus-babe = { path = "../../../substrate/primitives/consensus/babe" } +sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } +sp-timestamp = { path = "../../../substrate/primitives/timestamp" } + +schnorrkel = { version = "0.9.1", default-features = false } +rand_core = "0.6.2" # should match schnorrkel +rand_chacha = { version = "0.3.1" } paste = "1.0.14" orchestra = { version = "0.3.5", default-features = false, features = ["futures_channel"] } pyroscope = "0.5.7" diff --git a/polkadot/node/subsystem-bench/examples/approvals_no_shows.yaml b/polkadot/node/subsystem-bench/examples/approvals_no_shows.yaml new file mode 100644 index 00000000000..758c7fbbf11 --- /dev/null +++ b/polkadot/node/subsystem-bench/examples/approvals_no_shows.yaml @@ -0,0 +1,18 @@ +TestConfiguration: +# Test 1 +- objective: !ApprovalVoting + last_considered_tranche: 89 + coalesce_mean: 3.0 + coalesce_std_dev: 1.0 + stop_when_approved: true + coalesce_tranche_diff: 12 + workdir_prefix: "/tmp/" + enable_assignments_v2: true + num_no_shows_per_candidate: 10 + n_validators: 500 + n_cores: 100 + min_pov_size: 1120 + max_pov_size: 5120 + peer_bandwidth: 524288000000 + bandwidth: 524288000000 + num_blocks: 10 diff --git a/polkadot/node/subsystem-bench/examples/approvals_throughput.yaml b/polkadot/node/subsystem-bench/examples/approvals_throughput.yaml new file mode 100644 index 00000000000..9eeeefc53a4 --- /dev/null +++ b/polkadot/node/subsystem-bench/examples/approvals_throughput.yaml @@ -0,0 +1,19 @@ +TestConfiguration: +# Test 1 +- objective: !ApprovalVoting + coalesce_mean: 3.0 + coalesce_std_dev: 1.0 + enable_assignments_v2: true + last_considered_tranche: 89 + stop_when_approved: false + coalesce_tranche_diff: 12 + workdir_prefix: "/tmp" + num_no_shows_per_candidate: 0 + n_validators: 500 + n_cores: 100 + n_included_candidates: 100 + min_pov_size: 1120 + max_pov_size: 5120 + peer_bandwidth: 524288000000 + bandwidth: 524288000000 + num_blocks: 10 diff --git a/polkadot/node/subsystem-bench/examples/approvals_throughput_best_case.yaml b/polkadot/node/subsystem-bench/examples/approvals_throughput_best_case.yaml new file mode 100644 index 00000000000..370bb31a5c4 --- /dev/null +++ b/polkadot/node/subsystem-bench/examples/approvals_throughput_best_case.yaml @@ -0,0 +1,18 @@ +TestConfiguration: +# Test 1 +- objective: !ApprovalVoting + coalesce_mean: 3.0 + coalesce_std_dev: 1.0 + enable_assignments_v2: true + last_considered_tranche: 89 + stop_when_approved: true + coalesce_tranche_diff: 12 + workdir_prefix: "/tmp/" + num_no_shows_per_candidate: 0 + n_validators: 500 + n_cores: 100 + min_pov_size: 1120 + max_pov_size: 5120 + peer_bandwidth: 524288000000 + bandwidth: 524288000000 + num_blocks: 10 diff --git a/polkadot/node/subsystem-bench/examples/approvals_throughput_no_optimisations_enabled.yaml b/polkadot/node/subsystem-bench/examples/approvals_throughput_no_optimisations_enabled.yaml new file mode 100644 index 00000000000..30b9ac8dc50 --- /dev/null +++ b/polkadot/node/subsystem-bench/examples/approvals_throughput_no_optimisations_enabled.yaml @@ -0,0 +1,18 @@ +TestConfiguration: +# Test 1 +- objective: !ApprovalVoting + coalesce_mean: 1.0 + coalesce_std_dev: 0.0 + enable_assignments_v2: false + last_considered_tranche: 89 + stop_when_approved: false + coalesce_tranche_diff: 12 + workdir_prefix: "/tmp/" + num_no_shows_per_candidate: 0 + n_validators: 500 + n_cores: 100 + min_pov_size: 1120 + max_pov_size: 5120 + peer_bandwidth: 524288000000 + bandwidth: 524288000000 + num_blocks: 10 diff --git a/polkadot/node/subsystem-bench/src/approval/helpers.rs b/polkadot/node/subsystem-bench/src/approval/helpers.rs new file mode 100644 index 00000000000..7cbe3ba7594 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/approval/helpers.rs @@ -0,0 +1,207 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +use crate::core::configuration::TestAuthorities; +use itertools::Itertools; +use polkadot_node_core_approval_voting::time::{Clock, SystemClock, Tick}; +use polkadot_node_network_protocol::{ + grid_topology::{SessionGridTopology, TopologyPeerInfo}, + View, +}; +use polkadot_node_subsystem_types::messages::{ + network_bridge_event::NewGossipTopology, ApprovalDistributionMessage, NetworkBridgeEvent, +}; +use polkadot_overseer::AllMessages; +use polkadot_primitives::{ + BlockNumber, CandidateEvent, CandidateReceipt, CoreIndex, GroupIndex, Hash, Header, + Id as ParaId, Slot, ValidatorIndex, +}; +use polkadot_primitives_test_helpers::dummy_candidate_receipt_bad_sig; +use rand::{seq::SliceRandom, SeedableRng}; +use rand_chacha::ChaCha20Rng; +use sc_network::PeerId; +use sp_consensus_babe::{ + digests::{CompatibleDigestItem, PreDigest, SecondaryVRFPreDigest}, + AllowedSlots, BabeEpochConfiguration, Epoch as BabeEpoch, VrfSignature, VrfTranscript, +}; +use sp_core::crypto::VrfSecret; +use sp_keyring::sr25519::Keyring as Sr25519Keyring; +use sp_runtime::{Digest, DigestItem}; +use std::sync::{atomic::AtomicU64, Arc}; + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +/// A fake system clock used for driving the approval voting and make +/// it process blocks, assignments and approvals from the past. +#[derive(Clone)] +pub struct PastSystemClock { + /// The real system clock + real_system_clock: SystemClock, + /// The difference in ticks between the real system clock and the current clock. + delta_ticks: Arc, +} + +impl PastSystemClock { + /// Creates a new fake system clock with `delta_ticks` between the real time and the fake one. + pub fn new(real_system_clock: SystemClock, delta_ticks: Arc) -> Self { + PastSystemClock { real_system_clock, delta_ticks } + } +} + +impl Clock for PastSystemClock { + fn tick_now(&self) -> Tick { + self.real_system_clock.tick_now() - + self.delta_ticks.load(std::sync::atomic::Ordering::SeqCst) + } + + fn wait( + &self, + tick: Tick, + ) -> std::pin::Pin + Send + 'static>> { + self.real_system_clock + .wait(tick + self.delta_ticks.load(std::sync::atomic::Ordering::SeqCst)) + } +} + +/// Helper function to generate a babe epoch for this benchmark. +/// It does not change for the duration of the test. +pub fn generate_babe_epoch(current_slot: Slot, authorities: TestAuthorities) -> BabeEpoch { + let authorities = authorities + .validator_babe_id + .into_iter() + .enumerate() + .map(|(index, public)| (public, index as u64)) + .collect_vec(); + BabeEpoch { + epoch_index: 1, + start_slot: current_slot.saturating_sub(1u64), + duration: 200, + authorities, + randomness: [0xde; 32], + config: BabeEpochConfiguration { c: (1, 4), allowed_slots: AllowedSlots::PrimarySlots }, + } +} + +/// Generates a topology to be used for this benchmark. +pub fn generate_topology(test_authorities: &TestAuthorities) -> SessionGridTopology { + let keyrings = test_authorities + .validator_authority_id + .clone() + .into_iter() + .zip(test_authorities.peer_ids.clone()) + .collect_vec(); + + let topology = keyrings + .clone() + .into_iter() + .enumerate() + .map(|(index, (discovery_id, peer_id))| TopologyPeerInfo { + peer_ids: vec![peer_id], + validator_index: ValidatorIndex(index as u32), + discovery_id, + }) + .collect_vec(); + let shuffled = (0..keyrings.len()).collect_vec(); + + SessionGridTopology::new(shuffled, topology) +} + +/// Generates new session topology message. +pub fn generate_new_session_topology( + test_authorities: &TestAuthorities, + test_node: ValidatorIndex, +) -> Vec { + let topology = generate_topology(test_authorities); + + let event = NetworkBridgeEvent::NewGossipTopology(NewGossipTopology { + session: 1, + topology, + local_index: Some(test_node), + }); + vec![AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NetworkBridgeUpdate(event))] +} + +/// Generates a peer view change for the passed `block_hash` +pub fn generate_peer_view_change_for(block_hash: Hash, peer_id: PeerId) -> AllMessages { + let network = NetworkBridgeEvent::PeerViewChange(peer_id, View::new([block_hash], 0)); + + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NetworkBridgeUpdate(network)) +} + +/// Helper function to create a a signature for the block header. +fn garbage_vrf_signature() -> VrfSignature { + let transcript = VrfTranscript::new(b"test-garbage", &[]); + Sr25519Keyring::Alice.pair().vrf_sign(&transcript.into()) +} + +/// Helper function to create a block header. +pub fn make_header(parent_hash: Hash, slot: Slot, number: u32) -> Header { + let digest = + { + let mut digest = Digest::default(); + let vrf_signature = garbage_vrf_signature(); + digest.push(DigestItem::babe_pre_digest(PreDigest::SecondaryVRF( + SecondaryVRFPreDigest { authority_index: 0, slot, vrf_signature }, + ))); + digest + }; + + Header { + digest, + extrinsics_root: Default::default(), + number, + state_root: Default::default(), + parent_hash, + } +} + +/// Helper function to create a candidate receipt. +fn make_candidate(para_id: ParaId, hash: &Hash) -> CandidateReceipt { + let mut r = dummy_candidate_receipt_bad_sig(*hash, Some(Default::default())); + r.descriptor.para_id = para_id; + r +} + +/// Helper function to create a list of candidates that are included in the block +pub fn make_candidates( + block_hash: Hash, + block_number: BlockNumber, + num_cores: u32, + num_candidates: u32, +) -> Vec { + let seed = [block_number as u8; 32]; + let mut rand_chacha = ChaCha20Rng::from_seed(seed); + let mut candidates = (0..num_cores) + .map(|core| { + CandidateEvent::CandidateIncluded( + make_candidate(ParaId::from(core), &block_hash), + Vec::new().into(), + CoreIndex(core), + GroupIndex(core), + ) + }) + .collect_vec(); + let (candidates, _) = candidates.partial_shuffle(&mut rand_chacha, num_candidates as usize); + candidates + .iter_mut() + .map(|val| val.clone()) + .sorted_by(|a, b| match (a, b) { + ( + CandidateEvent::CandidateIncluded(_, _, core_a, _), + CandidateEvent::CandidateIncluded(_, _, core_b, _), + ) => core_a.0.cmp(&core_b.0), + (_, _) => todo!("Should not happen"), + }) + .collect_vec() +} diff --git a/polkadot/node/subsystem-bench/src/approval/message_generator.rs b/polkadot/node/subsystem-bench/src/approval/message_generator.rs new file mode 100644 index 00000000000..4318dcdf890 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/approval/message_generator.rs @@ -0,0 +1,686 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use std::{ + cmp::max, + collections::{BTreeMap, HashSet}, + fs, + io::Write, + path::{Path, PathBuf}, + time::Duration, +}; + +use futures::SinkExt; +use itertools::Itertools; +use parity_scale_codec::Encode; +use polkadot_node_core_approval_voting::{ + criteria::{compute_assignments, Config}, + time::tranche_to_tick, +}; +use polkadot_node_network_protocol::grid_topology::{ + GridNeighbors, RandomRouting, RequiredRouting, SessionGridTopology, +}; +use polkadot_node_primitives::approval::{ + self, + v2::{CoreBitfield, IndirectAssignmentCertV2, IndirectSignedApprovalVoteV2}, +}; +use polkadot_primitives::{ + vstaging::ApprovalVoteMultipleCandidates, CandidateEvent, CandidateHash, CandidateIndex, + CoreIndex, SessionInfo, Slot, ValidatorId, ValidatorIndex, ASSIGNMENT_KEY_TYPE_ID, +}; +use rand::{seq::SliceRandom, RngCore, SeedableRng}; +use rand_chacha::ChaCha20Rng; +use rand_distr::{Distribution, Normal}; +use sc_keystore::LocalKeystore; +use sc_network::PeerId; +use sha1::Digest; +use sp_application_crypto::AppCrypto; +use sp_consensus_babe::SlotDuration; +use sp_keystore::Keystore; +use sp_timestamp::Timestamp; + +use super::{ + test_message::{MessagesBundle, TestMessageInfo}, + ApprovalTestState, ApprovalsOptions, BlockTestData, +}; +use crate::{ + approval::{ + helpers::{generate_babe_epoch, generate_topology}, + GeneratedState, BUFFER_FOR_GENERATION_MILLIS, LOG_TARGET, SLOT_DURATION_MILLIS, + }, + core::{ + configuration::{TestAuthorities, TestConfiguration, TestObjective}, + mock::session_info_for_peers, + NODE_UNDER_TEST, + }, +}; +use polkadot_node_network_protocol::v3 as protocol_v3; +use polkadot_primitives::Hash; +use sc_service::SpawnTaskHandle; +/// A generator of messages coming from a given Peer/Validator +pub struct PeerMessagesGenerator { + /// The grid neighbors of the node under test. + pub topology_node_under_test: GridNeighbors, + /// The topology of the network for the epoch under test. + pub topology: SessionGridTopology, + /// The validator index for this object generates the messages. + pub validator_index: ValidatorIndex, + /// An array of pre-generated random samplings, that is used to determine, which nodes would + /// send a given assignment, to the node under test because of the random samplings. + /// As an optimization we generate this sampling at the begining of the test and just pick + /// one randomly, because always taking the samples would be too expensive for benchamrk. + pub random_samplings: Vec>, + /// Channel for sending the generated messages to the aggregator + pub tx_messages: futures::channel::mpsc::UnboundedSender<(Hash, Vec)>, + /// The list of test authorities + pub test_authorities: TestAuthorities, + //// The session info used for the test. + pub session_info: SessionInfo, + /// The blocks used for testing + pub blocks: Vec, + /// Approval options params. + pub options: ApprovalsOptions, +} + +impl PeerMessagesGenerator { + /// Generates messages by spawning a blocking task in the background which begins creating + /// the assignments/approvals and peer view changes at the begining of each block. + pub fn generate_messages(mut self, spawn_task_handle: &SpawnTaskHandle) { + spawn_task_handle.spawn("generate-messages", "generate-messages", async move { + for block_info in &self.blocks { + let assignments = self.generate_assignments(block_info); + + let bytes = self.validator_index.0.to_be_bytes(); + let seed = [ + bytes[0], bytes[1], bytes[2], bytes[3], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + + let mut rand_chacha = ChaCha20Rng::from_seed(seed); + let approvals = issue_approvals( + assignments, + block_info.hash, + &self.test_authorities.validator_public, + block_info.candidates.clone(), + &self.options, + &mut rand_chacha, + self.test_authorities.keyring.keystore_ref(), + ); + + self.tx_messages + .send((block_info.hash, approvals)) + .await + .expect("Should not fail"); + } + }) + } + + // Builds the messages finger print corresponding to this configuration. + // When the finger print exists already on disk the messages are not re-generated. + fn messages_fingerprint( + configuration: &TestConfiguration, + options: &ApprovalsOptions, + ) -> String { + let mut fingerprint = options.fingerprint(); + let mut exclude_objective = configuration.clone(); + // The objective contains the full content of `ApprovalOptions`, we don't want to put all of + // that in fingerprint, so execlute it because we add it manually see above. + exclude_objective.objective = TestObjective::Unimplemented; + let configuration_bytes = bincode::serialize(&exclude_objective).unwrap(); + fingerprint.extend(configuration_bytes); + let mut sha1 = sha1::Sha1::new(); + sha1.update(fingerprint); + let result = sha1.finalize(); + hex::encode(result) + } + + /// Generate all messages(Assignments & Approvals) needed for approving `blocks``. + pub fn generate_messages_if_needed( + configuration: &TestConfiguration, + test_authorities: &TestAuthorities, + options: &ApprovalsOptions, + spawn_task_handle: &SpawnTaskHandle, + ) -> PathBuf { + let path_name = format!( + "{}/{}", + options.workdir_prefix, + Self::messages_fingerprint(configuration, options) + ); + + let path = Path::new(&path_name); + if path.exists() { + return path.to_path_buf(); + } + + gum::info!("Generate message because file does not exist"); + let delta_to_first_slot_under_test = Timestamp::new(BUFFER_FOR_GENERATION_MILLIS); + let initial_slot = Slot::from_timestamp( + (*Timestamp::current() - *delta_to_first_slot_under_test).into(), + SlotDuration::from_millis(SLOT_DURATION_MILLIS), + ); + + let babe_epoch = generate_babe_epoch(initial_slot, test_authorities.clone()); + let session_info = session_info_for_peers(configuration, test_authorities); + let blocks = ApprovalTestState::generate_blocks_information( + configuration, + &babe_epoch, + initial_slot, + ); + + gum::info!(target: LOG_TARGET, "Generate messages"); + let topology = generate_topology(test_authorities); + + let random_samplings = random_samplings_to_node( + ValidatorIndex(NODE_UNDER_TEST), + test_authorities.validator_public.len(), + test_authorities.validator_public.len() * 2, + ); + + let topology_node_under_test = + topology.compute_grid_neighbors_for(ValidatorIndex(NODE_UNDER_TEST)).unwrap(); + + let (tx, mut rx) = futures::channel::mpsc::unbounded(); + + // Spawn a thread to generate the messages for each validator, so that we speed up the + // generation. + for current_validator_index in 1..test_authorities.validator_public.len() { + let peer_message_source = PeerMessagesGenerator { + topology_node_under_test: topology_node_under_test.clone(), + topology: topology.clone(), + validator_index: ValidatorIndex(current_validator_index as u32), + test_authorities: test_authorities.clone(), + session_info: session_info.clone(), + blocks: blocks.clone(), + tx_messages: tx.clone(), + random_samplings: random_samplings.clone(), + options: options.clone(), + }; + + peer_message_source.generate_messages(spawn_task_handle); + } + + std::mem::drop(tx); + + let seed = [0x32; 32]; + let mut rand_chacha = ChaCha20Rng::from_seed(seed); + + let mut all_messages: BTreeMap> = BTreeMap::new(); + // Receive all messages and sort them by Tick they have to be sent. + loop { + match rx.try_next() { + Ok(Some((block_hash, messages))) => + for message in messages { + let block_info = blocks + .iter() + .find(|val| val.hash == block_hash) + .expect("Should find blocks"); + let tick_to_send = tranche_to_tick( + SLOT_DURATION_MILLIS, + block_info.slot, + message.tranche_to_send(), + ); + let to_add = all_messages.entry(tick_to_send).or_default(); + to_add.push(message); + }, + Ok(None) => break, + Err(_) => { + std::thread::sleep(Duration::from_millis(50)); + }, + } + } + let all_messages = all_messages + .into_iter() + .flat_map(|(_, mut messages)| { + // Shuffle the messages inside the same tick, so that we don't priorites messages + // for older nodes. we try to simulate the same behaviour as in real world. + messages.shuffle(&mut rand_chacha); + messages + }) + .collect_vec(); + + gum::info!("Generated a number of {:} unique messages", all_messages.len()); + + let generated_state = GeneratedState { all_messages: Some(all_messages), initial_slot }; + + let mut messages_file = fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(path) + .unwrap(); + + messages_file + .write_all(&generated_state.encode()) + .expect("Could not update message file"); + path.to_path_buf() + } + + /// Generates assignments for the given `current_validator_index` + /// Returns a list of assignments to be sent sorted by tranche. + fn generate_assignments(&self, block_info: &BlockTestData) -> Vec { + let config = Config::from(&self.session_info); + + let leaving_cores = block_info + .candidates + .clone() + .into_iter() + .map(|candidate_event| { + if let CandidateEvent::CandidateIncluded(candidate, _, core_index, group_index) = + candidate_event + { + (candidate.hash(), core_index, group_index) + } else { + todo!("Variant is never created in this benchmark") + } + }) + .collect_vec(); + + let mut assignments_by_tranche = BTreeMap::new(); + + let bytes = self.validator_index.0.to_be_bytes(); + let seed = [ + bytes[0], bytes[1], bytes[2], bytes[3], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + let mut rand_chacha = ChaCha20Rng::from_seed(seed); + + let to_be_sent_by = neighbours_that_would_sent_message( + &self.test_authorities.peer_ids, + self.validator_index.0, + &self.topology_node_under_test, + &self.topology, + ); + + let leaving_cores = leaving_cores + .clone() + .into_iter() + .filter(|(_, core_index, _group_index)| core_index.0 != self.validator_index.0) + .collect_vec(); + + let store = LocalKeystore::in_memory(); + let _public = store + .sr25519_generate_new( + ASSIGNMENT_KEY_TYPE_ID, + Some(self.test_authorities.key_seeds[self.validator_index.0 as usize].as_str()), + ) + .expect("should not fail"); + let assignments = compute_assignments( + &store, + block_info.relay_vrf_story.clone(), + &config, + leaving_cores.clone(), + self.options.enable_assignments_v2, + ); + + let random_sending_nodes = self + .random_samplings + .get(rand_chacha.next_u32() as usize % self.random_samplings.len()) + .unwrap(); + let random_sending_peer_ids = random_sending_nodes + .iter() + .map(|validator| (*validator, self.test_authorities.peer_ids[validator.0 as usize])) + .collect_vec(); + + let mut unique_assignments = HashSet::new(); + for (core_index, assignment) in assignments { + let assigned_cores = match &assignment.cert().kind { + approval::v2::AssignmentCertKindV2::RelayVRFModuloCompact { core_bitfield } => + core_bitfield.iter_ones().map(|val| CoreIndex::from(val as u32)).collect_vec(), + approval::v2::AssignmentCertKindV2::RelayVRFDelay { core_index } => + vec![*core_index], + approval::v2::AssignmentCertKindV2::RelayVRFModulo { sample: _ } => + vec![core_index], + }; + + let bitfiled: CoreBitfield = assigned_cores.clone().try_into().unwrap(); + + // For the cases where tranch0 assignments are in a single certificate we need to make + // sure we create a single message. + if unique_assignments.insert(bitfiled) { + let this_tranche_assignments = + assignments_by_tranche.entry(assignment.tranche()).or_insert_with(Vec::new); + + this_tranche_assignments.push(( + IndirectAssignmentCertV2 { + block_hash: block_info.hash, + validator: self.validator_index, + cert: assignment.cert().clone(), + }, + block_info + .candidates + .iter() + .enumerate() + .filter(|(_index, candidate)| { + if let CandidateEvent::CandidateIncluded(_, _, core, _) = candidate { + assigned_cores.contains(core) + } else { + panic!("Should not happen"); + } + }) + .map(|(index, _)| index as u32) + .collect_vec() + .try_into() + .unwrap(), + to_be_sent_by + .iter() + .chain(random_sending_peer_ids.iter()) + .copied() + .collect::>(), + assignment.tranche(), + )); + } + } + + assignments_by_tranche + .into_values() + .flat_map(|assignments| assignments.into_iter()) + .map(|assignment| { + let msg = protocol_v3::ApprovalDistributionMessage::Assignments(vec![( + assignment.0, + assignment.1, + )]); + TestMessageInfo { + msg, + sent_by: assignment + .2 + .into_iter() + .map(|(validator_index, _)| validator_index) + .collect_vec(), + tranche: assignment.3, + block_hash: block_info.hash, + } + }) + .collect_vec() + } +} + +/// A list of random samplings that we use to determine which nodes should send a given message to +/// the node under test. +/// We can not sample every time for all the messages because that would be too expensive to +/// perform, so pre-generate a list of samples for a given network size. +/// - result[i] give us as a list of random nodes that would send a given message to the node under +/// test. +fn random_samplings_to_node( + node_under_test: ValidatorIndex, + num_validators: usize, + num_samplings: usize, +) -> Vec> { + let seed = [7u8; 32]; + let mut rand_chacha = ChaCha20Rng::from_seed(seed); + + (0..num_samplings) + .map(|_| { + (0..num_validators) + .filter(|sending_validator_index| { + *sending_validator_index != NODE_UNDER_TEST as usize + }) + .flat_map(|sending_validator_index| { + let mut validators = (0..num_validators).collect_vec(); + validators.shuffle(&mut rand_chacha); + + let mut random_routing = RandomRouting::default(); + validators + .into_iter() + .flat_map(|validator_to_send| { + if random_routing.sample(num_validators, &mut rand_chacha) { + random_routing.inc_sent(); + if validator_to_send == node_under_test.0 as usize { + Some(ValidatorIndex(sending_validator_index as u32)) + } else { + None + } + } else { + None + } + }) + .collect_vec() + }) + .collect_vec() + }) + .collect_vec() +} + +/// Helper function to randomly determine how many approvals we coalesce together in a single +/// message. +fn coalesce_approvals_len( + coalesce_mean: f32, + coalesce_std_dev: f32, + rand_chacha: &mut ChaCha20Rng, +) -> usize { + max( + 1, + Normal::new(coalesce_mean, coalesce_std_dev) + .expect("normal distribution parameters are good") + .sample(rand_chacha) + .round() as i32, + ) as usize +} + +/// Helper function to create approvals signatures for all assignments passed as arguments. +/// Returns a list of Approvals messages that need to be sent. +fn issue_approvals( + assignments: Vec, + block_hash: Hash, + validator_ids: &[ValidatorId], + candidates: Vec, + options: &ApprovalsOptions, + rand_chacha: &mut ChaCha20Rng, + store: &LocalKeystore, +) -> Vec { + let mut queued_to_sign: Vec = Vec::new(); + let mut num_coalesce = + coalesce_approvals_len(options.coalesce_mean, options.coalesce_std_dev, rand_chacha); + let result = assignments + .iter() + .enumerate() + .map(|(_index, message)| match &message.msg { + protocol_v3::ApprovalDistributionMessage::Assignments(assignments) => { + let mut approvals_to_create = Vec::new(); + + let current_validator_index = queued_to_sign + .first() + .map(|msg| msg.validator_index) + .unwrap_or(ValidatorIndex(99999)); + + // Invariant for this benchmark. + assert_eq!(assignments.len(), 1); + + let assignment = assignments.first().unwrap(); + + let earliest_tranche = queued_to_sign + .first() + .map(|val| val.assignment.tranche) + .unwrap_or(message.tranche); + + if queued_to_sign.len() >= num_coalesce || + (!queued_to_sign.is_empty() && + current_validator_index != assignment.0.validator) || + message.tranche - earliest_tranche >= options.coalesce_tranche_diff + { + approvals_to_create.push(TestSignInfo::sign_candidates( + &mut queued_to_sign, + validator_ids, + block_hash, + num_coalesce, + store, + )); + num_coalesce = coalesce_approvals_len( + options.coalesce_mean, + options.coalesce_std_dev, + rand_chacha, + ); + } + + // If more that one candidate was in the assignment queue all of them for issuing + // approvals + for candidate_index in assignment.1.iter_ones() { + let candidate = candidates.get(candidate_index).unwrap(); + if let CandidateEvent::CandidateIncluded(candidate, _, _, _) = candidate { + queued_to_sign.push(TestSignInfo { + candidate_hash: candidate.hash(), + candidate_index: candidate_index as CandidateIndex, + validator_index: assignment.0.validator, + assignment: message.clone(), + }); + } else { + todo!("Other enum variants are not used in this benchmark"); + } + } + approvals_to_create + }, + _ => { + todo!("Other enum variants are not used in this benchmark"); + }, + }) + .collect_vec(); + + let mut messages = result.into_iter().flatten().collect_vec(); + + if !queued_to_sign.is_empty() { + messages.push(TestSignInfo::sign_candidates( + &mut queued_to_sign, + validator_ids, + block_hash, + num_coalesce, + store, + )); + } + messages +} + +/// Helper struct to gather information about more than one candidate an sign it in a single +/// approval message. +struct TestSignInfo { + /// The candidate hash + candidate_hash: CandidateHash, + /// The candidate index + candidate_index: CandidateIndex, + /// The validator sending the assignments + validator_index: ValidatorIndex, + /// The assignments convering this candidate + assignment: TestMessageInfo, +} + +impl TestSignInfo { + /// Helper function to create a signture for all candidates in `to_sign` parameter. + /// Returns a TestMessage + fn sign_candidates( + to_sign: &mut Vec, + validator_ids: &[ValidatorId], + block_hash: Hash, + num_coalesce: usize, + store: &LocalKeystore, + ) -> MessagesBundle { + let current_validator_index = to_sign.first().map(|val| val.validator_index).unwrap(); + let tranche_approval_can_be_sent = + to_sign.iter().map(|val| val.assignment.tranche).max().unwrap(); + let validator_id = validator_ids.get(current_validator_index.0 as usize).unwrap().clone(); + + let unique_assignments: HashSet = + to_sign.iter().map(|info| info.assignment.clone()).collect(); + + let mut to_sign = to_sign + .drain(..) + .sorted_by(|val1, val2| val1.candidate_index.cmp(&val2.candidate_index)) + .peekable(); + + let mut bundle = MessagesBundle { + assignments: unique_assignments.into_iter().collect_vec(), + approvals: Vec::new(), + }; + + while to_sign.peek().is_some() { + let to_sign = to_sign.by_ref().take(num_coalesce).collect_vec(); + + let hashes = to_sign.iter().map(|val| val.candidate_hash).collect_vec(); + let candidate_indices = to_sign.iter().map(|val| val.candidate_index).collect_vec(); + + let sent_by = to_sign + .iter() + .flat_map(|val| val.assignment.sent_by.iter()) + .copied() + .collect::>(); + + let payload = ApprovalVoteMultipleCandidates(&hashes).signing_payload(1); + + let signature = store + .sr25519_sign(ValidatorId::ID, &validator_id.clone().into(), &payload[..]) + .unwrap() + .unwrap() + .into(); + let indirect = IndirectSignedApprovalVoteV2 { + block_hash, + candidate_indices: candidate_indices.try_into().unwrap(), + validator: current_validator_index, + signature, + }; + let msg = protocol_v3::ApprovalDistributionMessage::Approvals(vec![indirect]); + + bundle.approvals.push(TestMessageInfo { + msg, + sent_by: sent_by.into_iter().collect_vec(), + tranche: tranche_approval_can_be_sent, + block_hash, + }); + } + bundle + } +} + +/// Determine what neighbours would send a given message to the node under test. +fn neighbours_that_would_sent_message( + peer_ids: &[PeerId], + current_validator_index: u32, + topology_node_under_test: &GridNeighbors, + topology: &SessionGridTopology, +) -> Vec<(ValidatorIndex, PeerId)> { + let topology_originator = topology + .compute_grid_neighbors_for(ValidatorIndex(current_validator_index)) + .unwrap(); + + let originator_y = topology_originator.validator_indices_y.iter().find(|validator| { + topology_node_under_test.required_routing_by_index(**validator, false) == + RequiredRouting::GridY + }); + + assert!(originator_y != Some(&ValidatorIndex(NODE_UNDER_TEST))); + + let originator_x = topology_originator.validator_indices_x.iter().find(|validator| { + topology_node_under_test.required_routing_by_index(**validator, false) == + RequiredRouting::GridX + }); + + assert!(originator_x != Some(&ValidatorIndex(NODE_UNDER_TEST))); + + let is_neighbour = topology_originator + .validator_indices_x + .contains(&ValidatorIndex(NODE_UNDER_TEST)) || + topology_originator + .validator_indices_y + .contains(&ValidatorIndex(NODE_UNDER_TEST)); + + let mut to_be_sent_by = originator_y + .into_iter() + .chain(originator_x) + .map(|val| (*val, peer_ids[val.0 as usize])) + .collect_vec(); + + if is_neighbour { + to_be_sent_by.push((ValidatorIndex(current_validator_index), peer_ids[0])); + } + + to_be_sent_by +} diff --git a/polkadot/node/subsystem-bench/src/approval/mock_chain_selection.rs b/polkadot/node/subsystem-bench/src/approval/mock_chain_selection.rs new file mode 100644 index 00000000000..ab23a8ced48 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/approval/mock_chain_selection.rs @@ -0,0 +1,66 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use crate::approval::{LOG_TARGET, SLOT_DURATION_MILLIS}; + +use super::{ApprovalTestState, PastSystemClock}; +use futures::FutureExt; +use polkadot_node_core_approval_voting::time::{slot_number_to_tick, Clock, TICK_DURATION_MILLIS}; +use polkadot_node_subsystem::{overseer, SpawnedSubsystem, SubsystemError}; +use polkadot_node_subsystem_types::messages::ChainSelectionMessage; + +/// Mock ChainSelection subsystem used to answer request made by the approval-voting subsystem, +/// during benchmark. All the necessary information to answer the requests is stored in the `state` +pub struct MockChainSelection { + pub state: ApprovalTestState, + pub clock: PastSystemClock, +} + +#[overseer::subsystem(ChainSelection, error=SubsystemError, prefix=self::overseer)] +impl MockChainSelection { + fn start(self, ctx: Context) -> SpawnedSubsystem { + let future = self.run(ctx).map(|_| Ok(())).boxed(); + + SpawnedSubsystem { name: "mock-chain-subsystem", future } + } +} + +#[overseer::contextbounds(ChainSelection, prefix = self::overseer)] +impl MockChainSelection { + async fn run(self, mut ctx: Context) { + loop { + let msg = ctx.recv().await.expect("Should not fail"); + match msg { + orchestra::FromOrchestra::Signal(_) => {}, + orchestra::FromOrchestra::Communication { msg } => + if let ChainSelectionMessage::Approved(hash) = msg { + let block_info = self.state.get_info_by_hash(hash); + let approved_number = block_info.block_number; + + block_info.approved.store(true, std::sync::atomic::Ordering::SeqCst); + self.state + .last_approved_block + .store(approved_number, std::sync::atomic::Ordering::SeqCst); + + let approved_in_tick = self.clock.tick_now() - + slot_number_to_tick(SLOT_DURATION_MILLIS, block_info.slot); + + gum::info!(target: LOG_TARGET, ?hash, "Chain selection approved after {:} ms", approved_in_tick * TICK_DURATION_MILLIS); + }, + } + } + } +} diff --git a/polkadot/node/subsystem-bench/src/approval/mod.rs b/polkadot/node/subsystem-bench/src/approval/mod.rs new file mode 100644 index 00000000000..3544ce74711 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/approval/mod.rs @@ -0,0 +1,1059 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use self::{ + helpers::{make_candidates, make_header}, + test_message::{MessagesBundle, TestMessageInfo}, +}; +use crate::{ + approval::{ + helpers::{ + generate_babe_epoch, generate_new_session_topology, generate_peer_view_change_for, + PastSystemClock, + }, + message_generator::PeerMessagesGenerator, + mock_chain_selection::MockChainSelection, + }, + core::{ + configuration::{TestAuthorities, TestConfiguration}, + environment::{TestEnvironment, TestEnvironmentDependencies, MAX_TIME_OF_FLIGHT}, + mock::{ + dummy_builder, + network_bridge::{MockNetworkBridgeRx, MockNetworkBridgeTx}, + AlwaysSupportsParachains, ChainApiState, MockChainApi, MockRuntimeApi, TestSyncOracle, + }, + network::{ + new_network, HandleNetworkMessage, NetworkEmulatorHandle, NetworkInterface, + NetworkInterfaceReceiver, + }, + NODE_UNDER_TEST, + }, +}; +use colored::Colorize; +use futures::channel::oneshot; +use itertools::Itertools; +use orchestra::TimeoutExt; +use overseer::{metrics::Metrics as OverseerMetrics, MetricsTrait}; +use parity_scale_codec::{Decode, Encode}; +use polkadot_approval_distribution::ApprovalDistribution; +use polkadot_node_core_approval_voting::{ + time::{slot_number_to_tick, tick_to_slot_number, Clock, ClockExt, SystemClock}, + ApprovalVotingSubsystem, Config as ApprovalVotingConfig, Metrics as ApprovalVotingMetrics, +}; +use polkadot_node_network_protocol::v3 as protocol_v3; +use polkadot_node_primitives::approval::{self, v1::RelayVRFStory}; +use polkadot_node_subsystem::{overseer, AllMessages, Overseer, OverseerConnector, SpawnGlue}; +use polkadot_node_subsystem_test_helpers::mock::new_block_import_info; +use polkadot_node_subsystem_types::messages::{ApprovalDistributionMessage, ApprovalVotingMessage}; +use polkadot_node_subsystem_util::metrics::Metrics; +use polkadot_overseer::Handle as OverseerHandleReal; +use polkadot_primitives::{ + BlockNumber, CandidateEvent, CandidateIndex, CandidateReceipt, Hash, Header, Slot, + ValidatorIndex, +}; +use prometheus::Registry; +use sc_keystore::LocalKeystore; +use sc_service::SpawnTaskHandle; +use serde::{Deserialize, Serialize}; +use sp_consensus_babe::Epoch as BabeEpoch; +use sp_core::H256; +use std::{ + cmp::max, + collections::{HashMap, HashSet}, + fs, + io::Read, + ops::Sub, + sync::{ + atomic::{AtomicBool, AtomicU32, AtomicU64}, + Arc, + }, + time::{Duration, Instant}, +}; +use tokio::time::sleep; + +mod helpers; +mod message_generator; +mod mock_chain_selection; +mod test_message; + +pub const LOG_TARGET: &str = "subsystem-bench::approval"; +const DATA_COL: u32 = 0; +pub(crate) const NUM_COLUMNS: u32 = 1; +pub(crate) const SLOT_DURATION_MILLIS: u64 = 6000; +pub(crate) const TEST_CONFIG: ApprovalVotingConfig = ApprovalVotingConfig { + col_approval_data: DATA_COL, + slot_duration_millis: SLOT_DURATION_MILLIS, +}; + +/// Start generating messages for a slot into the future, so that the +/// generation nevers falls behind the current slot. +const BUFFER_FOR_GENERATION_MILLIS: u64 = 30_000; + +/// Parameters specific to the approvals benchmark +#[derive(Debug, Clone, Serialize, Deserialize, clap::Parser)] +#[clap(rename_all = "kebab-case")] +#[allow(missing_docs)] +pub struct ApprovalsOptions { + #[clap(short, long, default_value_t = 89)] + /// The last considered tranche for which we send the message. + pub last_considered_tranche: u32, + #[clap(short, long, default_value_t = 1.0)] + /// Min candidates to be signed in a single approval. + pub coalesce_mean: f32, + #[clap(short, long, default_value_t = 1.0)] + /// Max candidate to be signed in a single approval. + pub coalesce_std_dev: f32, + /// The maximum tranche diff between approvals coalesced toghther. + pub coalesce_tranche_diff: u32, + #[clap(short, long, default_value_t = false)] + /// Enable assignments v2. + pub enable_assignments_v2: bool, + #[clap(short, long, default_value_t = true)] + /// Sends messages only till block is approved. + pub stop_when_approved: bool, + #[clap(short, long)] + /// Work directory. + #[clap(short, long, default_value_t = format!("/tmp"))] + pub workdir_prefix: String, + /// The number of no shows per candidate + #[clap(short, long, default_value_t = 0)] + pub num_no_shows_per_candidate: u32, +} + +impl ApprovalsOptions { + // Generates a fingerprint use to determine if messages need to be re-generated. + fn fingerprint(&self) -> Vec { + let mut bytes = Vec::new(); + bytes.extend(self.coalesce_mean.to_be_bytes()); + bytes.extend(self.coalesce_std_dev.to_be_bytes()); + bytes.extend(self.coalesce_tranche_diff.to_be_bytes()); + bytes.extend((self.enable_assignments_v2 as i32).to_be_bytes()); + bytes + } +} + +/// Information about a block. It is part of test state and it is used by the mock +/// subsystems to be able to answer the calls approval-voting and approval-distribution +/// do into the outside world. +#[derive(Clone, Debug)] +struct BlockTestData { + /// The slot this block occupies, see implementer's guide to understand what a slot + /// is in the context of polkadot. + slot: Slot, + /// The hash of the block. + hash: Hash, + /// The block number. + block_number: BlockNumber, + /// The list of candidates included in this block. + candidates: Vec, + /// The block header. + header: Header, + /// The vrf story for the given block. + relay_vrf_story: RelayVRFStory, + /// If the block has been approved by the approval-voting subsystem. + /// This set on `true` when ChainSelectionMessage::Approved is received inside the chain + /// selection mock subsystem. + approved: Arc, + /// The total number of candidates before this block. + total_candidates_before: u64, + /// The votes we sent. + /// votes[validator_index][candidate_index] tells if validator sent vote for candidate. + /// We use this to mark the test as succesfull if GetApprovalSignatures returns all the votes + /// from here. + votes: Arc>>, +} + +/// Candidate information used during the test to decide if more messages are needed. +#[derive(Debug)] +struct CandidateTestData { + /// The configured maximum number of no-shows for this candidate. + max_no_shows: u32, + /// The last tranche where we had a no-show. + last_tranche_with_no_show: u32, + /// The number of sent assignments. + sent_assignment: u32, + /// The number of no-shows. + num_no_shows: u32, + /// The maximum tranche were we covered the needed approvals + max_tranche: u32, + /// Minimum needed votes to approve candidate. + needed_approvals: u32, +} + +impl CandidateTestData { + /// If message in this tranche needs to be sent. + fn should_send_tranche(&self, tranche: u32) -> bool { + self.sent_assignment <= self.needed_approvals || + tranche <= self.max_tranche + self.num_no_shows + } + + /// Sets max tranche + fn set_max_tranche(&mut self, tranche: u32) { + self.max_tranche = max(tranche, self.max_tranche); + } + + /// Records no-show for candidate. + fn record_no_show(&mut self, tranche: u32) { + self.num_no_shows += 1; + self.last_tranche_with_no_show = max(tranche, self.last_tranche_with_no_show); + } + + /// Marks an assignment sent. + fn mark_sent_assignment(&mut self, tranche: u32) { + if self.sent_assignment < self.needed_approvals { + self.set_max_tranche(tranche); + } + + self.sent_assignment += 1; + } + + /// Tells if a message in this tranche should be a no-show. + fn should_no_show(&self, tranche: u32) -> bool { + (self.num_no_shows < self.max_no_shows && self.last_tranche_with_no_show < tranche) || + (tranche == 0 && self.num_no_shows == 0 && self.max_no_shows > 0) + } +} + +/// Test state that is pre-generated and loaded from a file that matches the fingerprint +/// of the TestConfiguration. +#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] +struct GeneratedState { + /// All assignments and approvals + all_messages: Option>, + /// The first slot in the test. + initial_slot: Slot, +} + +/// Approval test state used by all mock subsystems to be able to answer messages emitted +/// by the approval-voting and approval-distribution-subystems. +/// +/// This gets cloned across all mock subsystems, so if there is any information that gets +/// updated between subsystems, they would have to be wrapped in Arc's. +#[derive(Clone)] +pub struct ApprovalTestState { + /// The main test configuration + configuration: TestConfiguration, + /// The specific test configurations passed when starting the benchmark. + options: ApprovalsOptions, + /// The list of blocks used for testing. + blocks: Vec, + /// The babe epoch used during testing. + babe_epoch: BabeEpoch, + /// The pre-generated state. + generated_state: GeneratedState, + /// The test authorities + test_authorities: TestAuthorities, + /// Last approved block number. + last_approved_block: Arc, + /// Total sent messages from peers to node + total_sent_messages_to_node: Arc, + /// Total sent messages from test node to other peers + total_sent_messages_from_node: Arc, + /// Total unique sent messages. + total_unique_messages: Arc, + /// Approval voting metrics. + approval_voting_metrics: ApprovalVotingMetrics, + /// The delta ticks from the tick the messages were generated to the the time we start this + /// message. + delta_tick_from_generated: Arc, +} + +impl ApprovalTestState { + /// Build a new `ApprovalTestState` object out of the configurations passed when the benchmark + /// was tested. + fn new( + configuration: &TestConfiguration, + options: ApprovalsOptions, + dependencies: &TestEnvironmentDependencies, + ) -> Self { + let test_authorities = configuration.generate_authorities(); + let start = Instant::now(); + + let messages_path = PeerMessagesGenerator::generate_messages_if_needed( + configuration, + &test_authorities, + &options, + &dependencies.task_manager.spawn_handle(), + ); + + let mut messages_file = + fs::OpenOptions::new().read(true).open(messages_path.as_path()).unwrap(); + let mut messages_bytes = Vec::::with_capacity(2000000); + + messages_file + .read_to_end(&mut messages_bytes) + .expect("Could not initialize list of messages"); + let generated_state: GeneratedState = + Decode::decode(&mut messages_bytes.as_slice()).expect("Could not decode messages"); + + gum::info!( + "It took {:?} ms to load {:?} unique messages", + start.elapsed().as_millis(), + generated_state.all_messages.as_ref().map(|val| val.len()).unwrap_or_default() + ); + + let babe_epoch = + generate_babe_epoch(generated_state.initial_slot, test_authorities.clone()); + let blocks = Self::generate_blocks_information( + configuration, + &babe_epoch, + generated_state.initial_slot, + ); + + let state = ApprovalTestState { + blocks, + babe_epoch: babe_epoch.clone(), + generated_state, + test_authorities, + last_approved_block: Arc::new(AtomicU32::new(0)), + total_sent_messages_to_node: Arc::new(AtomicU64::new(0)), + total_sent_messages_from_node: Arc::new(AtomicU64::new(0)), + total_unique_messages: Arc::new(AtomicU64::new(0)), + options, + approval_voting_metrics: ApprovalVotingMetrics::try_register(&dependencies.registry) + .unwrap(), + delta_tick_from_generated: Arc::new(AtomicU64::new(630720000)), + configuration: configuration.clone(), + }; + + gum::info!("Built testing state"); + + state + } + + /// Generates the blocks and the information about the blocks that will be used + /// to drive this test. + fn generate_blocks_information( + configuration: &TestConfiguration, + babe_epoch: &BabeEpoch, + initial_slot: Slot, + ) -> Vec { + let mut per_block_heads: Vec = Vec::new(); + let mut prev_candidates = 0; + for block_number in 1..=configuration.num_blocks { + let block_hash = Hash::repeat_byte(block_number as u8); + let parent_hash = + per_block_heads.last().map(|val| val.hash).unwrap_or(Hash::repeat_byte(0xde)); + let slot_for_block = initial_slot + (block_number as u64 - 1); + + let header = make_header(parent_hash, slot_for_block, block_number as u32); + + let unsafe_vrf = approval::v1::babe_unsafe_vrf_info(&header) + .expect("Can not continue without vrf generator"); + let relay_vrf_story = unsafe_vrf + .compute_randomness( + &babe_epoch.authorities, + &babe_epoch.randomness, + babe_epoch.epoch_index, + ) + .expect("Can not continue without vrf story"); + let block_info = BlockTestData { + slot: slot_for_block, + block_number: block_number as BlockNumber, + hash: block_hash, + header, + candidates: make_candidates( + block_hash, + block_number as BlockNumber, + configuration.n_cores as u32, + configuration.n_cores as u32, + ), + relay_vrf_story, + approved: Arc::new(AtomicBool::new(false)), + total_candidates_before: prev_candidates, + votes: Arc::new( + (0..configuration.n_validators) + .map(|_| { + (0..configuration.n_cores).map(|_| AtomicBool::new(false)).collect_vec() + }) + .collect_vec(), + ), + }; + prev_candidates += block_info.candidates.len() as u64; + per_block_heads.push(block_info) + } + per_block_heads + } + + /// Starts the generation of messages(Assignments & Approvals) needed for approving blocks. + async fn start_message_production( + &mut self, + network_emulator: &NetworkEmulatorHandle, + overseer_handle: OverseerHandleReal, + env: &TestEnvironment, + registry: Registry, + ) -> oneshot::Receiver<()> { + gum::info!(target: LOG_TARGET, "Start assignments/approvals production"); + + let (producer_tx, producer_rx) = oneshot::channel(); + let peer_message_source = PeerMessageProducer { + network: network_emulator.clone(), + overseer_handle: overseer_handle.clone(), + state: self.clone(), + options: self.options.clone(), + notify_done: producer_tx, + registry, + }; + + peer_message_source + .produce_messages(env, self.generated_state.all_messages.take().unwrap()); + producer_rx + } + + // Generates a ChainApiState used for driving MockChainApi + fn build_chain_api_state(&self) -> ChainApiState { + ChainApiState { + block_headers: self + .blocks + .iter() + .map(|block| (block.hash, block.header.clone())) + .collect(), + } + } + + // Builds a map with the list of candidate events per-block. + fn candidate_events_by_block(&self) -> HashMap> { + self.blocks.iter().map(|block| (block.hash, block.candidates.clone())).collect() + } + + // Builds a map with the list of candidate hashes per-block. + fn candidate_hashes_by_block(&self) -> HashMap> { + self.blocks + .iter() + .map(|block| { + ( + block.hash, + block + .candidates + .iter() + .map(|candidate_event| match candidate_event { + CandidateEvent::CandidateBacked(_, _, _, _) => todo!(), + CandidateEvent::CandidateIncluded(receipt, _, _, _) => receipt.clone(), + CandidateEvent::CandidateTimedOut(_, _, _) => todo!(), + }) + .collect_vec(), + ) + }) + .collect() + } +} + +impl ApprovalTestState { + /// Returns test data for the given hash + fn get_info_by_hash(&self, requested_hash: Hash) -> &BlockTestData { + self.blocks + .iter() + .find(|block| block.hash == requested_hash) + .expect("Mocks should not use unknown hashes") + } + + /// Returns test data for the given slot + fn get_info_by_slot(&self, slot: Slot) -> Option<&BlockTestData> { + self.blocks.iter().find(|block| block.slot == slot) + } +} + +impl HandleNetworkMessage for ApprovalTestState { + fn handle( + &self, + _message: crate::core::network::NetworkMessage, + _node_sender: &mut futures::channel::mpsc::UnboundedSender< + crate::core::network::NetworkMessage, + >, + ) -> Option { + self.total_sent_messages_from_node + .as_ref() + .fetch_add(1, std::sync::atomic::Ordering::SeqCst); + None + } +} + +/// A generator of messages coming from a given Peer/Validator +struct PeerMessageProducer { + /// The state state used to know what messages to generate. + state: ApprovalTestState, + /// Configuration options, passed at the beginning of the test. + options: ApprovalsOptions, + /// A reference to the network emulator + network: NetworkEmulatorHandle, + /// A handle to the overseer, used for sending messages to the node + /// under test. + overseer_handle: OverseerHandleReal, + /// Channel for producer to notify main loop it finished sending + /// all messages and they have been processed. + notify_done: oneshot::Sender<()>, + /// The metrics registry. + registry: Registry, +} + +impl PeerMessageProducer { + /// Generates messages by spawning a blocking task in the background which begins creating + /// the assignments/approvals and peer view changes at the begining of each block. + fn produce_messages(mut self, env: &TestEnvironment, all_messages: Vec) { + env.spawn_blocking("produce-messages", async move { + let mut initialized_blocks = HashSet::new(); + let mut per_candidate_data: HashMap<(Hash, CandidateIndex), CandidateTestData> = + self.initialize_candidates_test_data(); + let mut skipped_messages: Vec = Vec::new(); + let mut re_process_skipped = false; + + let system_clock = + PastSystemClock::new(SystemClock {}, self.state.delta_tick_from_generated.clone()); + let mut all_messages = all_messages.into_iter().peekable(); + + while all_messages.peek().is_some() { + let current_slot = + tick_to_slot_number(SLOT_DURATION_MILLIS, system_clock.tick_now()); + let block_to_initialize = self + .state + .blocks + .iter() + .filter(|block_info| { + block_info.slot <= current_slot && + !initialized_blocks.contains(&block_info.hash) + }) + .cloned() + .collect_vec(); + for block_info in block_to_initialize { + if !TestEnvironment::metric_lower_than( + &self.registry, + "polkadot_parachain_imported_candidates_total", + (block_info.total_candidates_before + block_info.candidates.len() as u64 - + 1) as f64, + ) { + initialized_blocks.insert(block_info.hash); + self.initialize_block(&block_info).await; + } + } + + let mut maybe_need_skip = if re_process_skipped { + skipped_messages.clone().into_iter().peekable() + } else { + vec![].into_iter().peekable() + }; + + let progressing_iterator = if !re_process_skipped { + &mut all_messages + } else { + re_process_skipped = false; + skipped_messages.clear(); + &mut maybe_need_skip + }; + + while progressing_iterator + .peek() + .map(|bundle| { + self.time_to_process_message( + bundle, + current_slot, + &initialized_blocks, + &system_clock, + &per_candidate_data, + ) + }) + .unwrap_or_default() + { + let bundle = progressing_iterator.next().unwrap(); + re_process_skipped = self.process_message( + bundle, + &mut per_candidate_data, + &mut skipped_messages, + ) || re_process_skipped; + } + // Sleep, so that we don't busy wait in this loop when don't have anything to send. + sleep(Duration::from_millis(50)).await; + } + + gum::info!( + "All messages sent max_tranche {:?} last_tranche_with_no_show {:?}", + per_candidate_data.values().map(|data| data.max_tranche).max(), + per_candidate_data.values().map(|data| data.last_tranche_with_no_show).max() + ); + sleep(Duration::from_secs(6)).await; + // Send an empty GetApprovalSignatures as the last message + // so when the approval-distribution answered to it, we know it doesn't have anything + // else to process. + let (tx, rx) = oneshot::channel(); + let msg = ApprovalDistributionMessage::GetApprovalSignatures(HashSet::new(), tx); + self.send_overseer_message( + AllMessages::ApprovalDistribution(msg), + ValidatorIndex(0), + None, + ) + .await; + rx.await.expect("Failed to get signatures"); + self.notify_done.send(()).expect("Failed to notify main loop"); + gum::info!("All messages processed "); + }); + } + + // Processes a single message bundle and queue the messages to be sent by the peers that would + // send the message in our simulation. + pub fn process_message( + &mut self, + bundle: MessagesBundle, + per_candidate_data: &mut HashMap<(Hash, CandidateIndex), CandidateTestData>, + skipped_messages: &mut Vec, + ) -> bool { + let mut reprocess_skipped = false; + let block_info = self + .state + .get_info_by_hash(bundle.assignments.first().unwrap().block_hash) + .clone(); + + if bundle.should_send(per_candidate_data, &self.options) { + bundle.record_sent_assignment(per_candidate_data); + + let assignments = bundle.assignments.clone(); + + for message in bundle.assignments.into_iter().chain(bundle.approvals.into_iter()) { + if message.no_show_if_required(&assignments, per_candidate_data) { + reprocess_skipped = true; + continue; + } else { + message.record_vote(&block_info); + } + self.state + .total_unique_messages + .as_ref() + .fetch_add(1, std::sync::atomic::Ordering::SeqCst); + for (peer, messages) in + message.clone().split_by_peer_id(&self.state.test_authorities) + { + for message in messages { + self.state + .total_sent_messages_to_node + .as_ref() + .fetch_add(1, std::sync::atomic::Ordering::SeqCst); + self.queue_message_from_peer(message, peer.0) + } + } + } + } else if !block_info.approved.load(std::sync::atomic::Ordering::SeqCst) && + self.options.num_no_shows_per_candidate > 0 + { + skipped_messages.push(bundle); + } + reprocess_skipped + } + + // Tells if it is the time to process a message. + pub fn time_to_process_message( + &self, + bundle: &MessagesBundle, + current_slot: Slot, + initialized_blocks: &HashSet, + system_clock: &PastSystemClock, + per_candidate_data: &HashMap<(Hash, CandidateIndex), CandidateTestData>, + ) -> bool { + let block_info = + self.state.get_info_by_hash(bundle.assignments.first().unwrap().block_hash); + let tranche_now = system_clock.tranche_now(SLOT_DURATION_MILLIS, block_info.slot); + + Self::is_past_tranche( + bundle, + tranche_now, + current_slot, + block_info, + initialized_blocks.contains(&block_info.hash), + ) || !bundle.should_send(per_candidate_data, &self.options) + } + + // Tells if the tranche where the bundle should be sent has passed. + pub fn is_past_tranche( + bundle: &MessagesBundle, + tranche_now: u32, + current_slot: Slot, + block_info: &BlockTestData, + block_initialized: bool, + ) -> bool { + bundle.tranche_to_send() <= tranche_now && + current_slot >= block_info.slot && + block_initialized + } + + // Queue message to be sent by validator `sent_by` + fn queue_message_from_peer(&mut self, message: TestMessageInfo, sent_by: ValidatorIndex) { + let peer_authority_id = self + .state + .test_authorities + .validator_authority_id + .get(sent_by.0 as usize) + .expect("We can't handle unknown peers") + .clone(); + + self.network + .send_message_from_peer( + &peer_authority_id, + protocol_v3::ValidationProtocol::ApprovalDistribution(message.msg).into(), + ) + .unwrap_or_else(|_| panic!("Network should be up and running {:?}", sent_by)); + } + + // Queues a message to be sent by the peer identified by the `sent_by` value. + async fn send_overseer_message( + &mut self, + message: AllMessages, + _sent_by: ValidatorIndex, + _latency: Option, + ) { + self.overseer_handle + .send_msg(message, LOG_TARGET) + .timeout(MAX_TIME_OF_FLIGHT) + .await + .unwrap_or_else(|| { + panic!("{} ms maximum time of flight breached", MAX_TIME_OF_FLIGHT.as_millis()) + }); + } + + // Sends the messages needed by approval-distribution and approval-voting for processing a + // message. E.g: PeerViewChange. + async fn initialize_block(&mut self, block_info: &BlockTestData) { + gum::info!("Initialize block {:?}", block_info.hash); + let (tx, rx) = oneshot::channel(); + self.overseer_handle.wait_for_activation(block_info.hash, tx).await; + + rx.await + .expect("We should not fail waiting for block to be activated") + .expect("We should not fail waiting for block to be activated"); + + for validator in 1..self.state.test_authorities.validator_authority_id.len() as u32 { + let peer_id = self.state.test_authorities.peer_ids.get(validator as usize).unwrap(); + let validator = ValidatorIndex(validator); + let view_update = generate_peer_view_change_for(block_info.hash, *peer_id); + + self.send_overseer_message(view_update, validator, None).await; + } + } + + // Initializes the candidates test data. This is used for bookeeping if more assignments and + // approvals would be needed. + fn initialize_candidates_test_data( + &self, + ) -> HashMap<(Hash, CandidateIndex), CandidateTestData> { + let mut per_candidate_data: HashMap<(Hash, CandidateIndex), CandidateTestData> = + HashMap::new(); + for block_info in self.state.blocks.iter() { + for (candidate_index, _) in block_info.candidates.iter().enumerate() { + per_candidate_data.insert( + (block_info.hash, candidate_index as CandidateIndex), + CandidateTestData { + max_no_shows: self.options.num_no_shows_per_candidate, + last_tranche_with_no_show: 0, + sent_assignment: 0, + num_no_shows: 0, + max_tranche: 0, + needed_approvals: self.state.configuration.needed_approvals as u32, + }, + ); + } + } + per_candidate_data + } +} + +/// Helper function to build an overseer with the real implementation for `ApprovalDistribution` and +/// `ApprovalVoting` subystems and mock subsytems for all others. +fn build_overseer( + state: &ApprovalTestState, + network: &NetworkEmulatorHandle, + config: &TestConfiguration, + dependencies: &TestEnvironmentDependencies, + network_interface: &NetworkInterface, + network_receiver: NetworkInterfaceReceiver, +) -> (Overseer, AlwaysSupportsParachains>, OverseerHandleReal) { + let overseer_connector = OverseerConnector::with_event_capacity(6400000); + + let spawn_task_handle = dependencies.task_manager.spawn_handle(); + + let db = kvdb_memorydb::create(NUM_COLUMNS); + let db: polkadot_node_subsystem_util::database::kvdb_impl::DbAdapter = + polkadot_node_subsystem_util::database::kvdb_impl::DbAdapter::new(db, &[]); + let keystore = LocalKeystore::in_memory(); + + let system_clock = + PastSystemClock::new(SystemClock {}, state.delta_tick_from_generated.clone()); + let approval_voting = ApprovalVotingSubsystem::with_config_and_clock( + TEST_CONFIG, + Arc::new(db), + Arc::new(keystore), + Box::new(TestSyncOracle {}), + state.approval_voting_metrics.clone(), + Box::new(system_clock.clone()), + ); + + let approval_distribution = + ApprovalDistribution::new(Metrics::register(Some(&dependencies.registry)).unwrap()); + let mock_chain_api = MockChainApi::new(state.build_chain_api_state()); + let mock_chain_selection = MockChainSelection { state: state.clone(), clock: system_clock }; + let mock_runtime_api = MockRuntimeApi::new( + config.clone(), + state.test_authorities.clone(), + state.candidate_hashes_by_block(), + state.candidate_events_by_block(), + Some(state.babe_epoch.clone()), + 1, + ); + let mock_tx_bridge = MockNetworkBridgeTx::new( + network.clone(), + network_interface.subsystem_sender(), + state.test_authorities.clone(), + ); + let mock_rx_bridge = MockNetworkBridgeRx::new(network_receiver, None); + let overseer_metrics = OverseerMetrics::try_register(&dependencies.registry).unwrap(); + let dummy = dummy_builder!(spawn_task_handle, overseer_metrics) + .replace_approval_distribution(|_| approval_distribution) + .replace_approval_voting(|_| approval_voting) + .replace_chain_api(|_| mock_chain_api) + .replace_chain_selection(|_| mock_chain_selection) + .replace_runtime_api(|_| mock_runtime_api) + .replace_network_bridge_tx(|_| mock_tx_bridge) + .replace_network_bridge_rx(|_| mock_rx_bridge); + + let (overseer, raw_handle) = + dummy.build_with_connector(overseer_connector).expect("Should not fail"); + + let overseer_handle = OverseerHandleReal::new(raw_handle); + (overseer, overseer_handle) +} + +/// Takes a test configuration and uses it to creates the `TestEnvironment`. +pub fn prepare_test( + config: TestConfiguration, + options: ApprovalsOptions, +) -> (TestEnvironment, ApprovalTestState) { + prepare_test_inner(config, TestEnvironmentDependencies::default(), options) +} + +/// Build the test environment for an Approval benchmark. +fn prepare_test_inner( + config: TestConfiguration, + dependencies: TestEnvironmentDependencies, + options: ApprovalsOptions, +) -> (TestEnvironment, ApprovalTestState) { + gum::info!("Prepare test state"); + let state = ApprovalTestState::new(&config, options, &dependencies); + + gum::info!("Build network emulator"); + + let (network, network_interface, network_receiver) = + new_network(&config, &dependencies, &state.test_authorities, vec![Arc::new(state.clone())]); + + gum::info!("Build overseer"); + + let (overseer, overseer_handle) = build_overseer( + &state, + &network, + &config, + &dependencies, + &network_interface, + network_receiver, + ); + + ( + TestEnvironment::new( + dependencies, + config, + network, + overseer, + overseer_handle, + state.test_authorities.clone(), + ), + state, + ) +} + +pub async fn bench_approvals(env: &mut TestEnvironment, mut state: ApprovalTestState) { + let producer_rx = state + .start_message_production( + env.network(), + env.overseer_handle().clone(), + env, + env.registry().clone(), + ) + .await; + bench_approvals_run(env, state, producer_rx).await +} + +/// Runs the approval benchmark. +pub async fn bench_approvals_run( + env: &mut TestEnvironment, + state: ApprovalTestState, + producer_rx: oneshot::Receiver<()>, +) { + let config = env.config().clone(); + + env.metrics().set_n_validators(config.n_validators); + env.metrics().set_n_cores(config.n_cores); + + // First create the initialization messages that make sure that then node under + // tests receives notifications about the topology used and the connected peers. + let mut initialization_messages = env.network().generate_peer_connected(); + initialization_messages.extend(generate_new_session_topology( + &state.test_authorities, + ValidatorIndex(NODE_UNDER_TEST), + )); + for message in initialization_messages { + env.send_message(message).await; + } + + let start_marker = Instant::now(); + let real_clock = SystemClock {}; + state.delta_tick_from_generated.store( + real_clock.tick_now() - + slot_number_to_tick(SLOT_DURATION_MILLIS, state.generated_state.initial_slot), + std::sync::atomic::Ordering::SeqCst, + ); + let system_clock = PastSystemClock::new(real_clock, state.delta_tick_from_generated.clone()); + + for block_num in 0..env.config().num_blocks { + let mut current_slot = tick_to_slot_number(SLOT_DURATION_MILLIS, system_clock.tick_now()); + + // Wait untill the time arrieves at the first slot under test. + while current_slot < state.generated_state.initial_slot { + sleep(Duration::from_millis(5)).await; + current_slot = tick_to_slot_number(SLOT_DURATION_MILLIS, system_clock.tick_now()); + } + + gum::info!(target: LOG_TARGET, "Current block {}/{}", block_num + 1, env.config().num_blocks); + env.metrics().set_current_block(block_num); + let block_start_ts = Instant::now(); + + if let Some(block_info) = state.get_info_by_slot(current_slot) { + env.import_block(new_block_import_info(block_info.hash, block_info.block_number)) + .await; + } + + let block_time = Instant::now().sub(block_start_ts).as_millis() as u64; + env.metrics().set_block_time(block_time); + gum::info!("Block time {}", format!("{:?}ms", block_time).cyan()); + + system_clock + .wait(slot_number_to_tick(SLOT_DURATION_MILLIS, current_slot + 1)) + .await; + } + + // Wait for all blocks to be approved before exiting. + // This is an invariant of the benchmark, if this does not happen something went teribbly wrong. + while state.last_approved_block.load(std::sync::atomic::Ordering::SeqCst) < + env.config().num_blocks as u32 + { + gum::info!( + "Waiting for all blocks to be approved current approved {:} num_sent {:} num_unique {:}", + state.last_approved_block.load(std::sync::atomic::Ordering::SeqCst), + state.total_sent_messages_to_node.load(std::sync::atomic::Ordering::SeqCst), + state.total_unique_messages.load(std::sync::atomic::Ordering::SeqCst) + ); + tokio::time::sleep(Duration::from_secs(6)).await; + } + + gum::info!("Awaiting producer to signal done"); + + producer_rx.await.expect("Failed to receive done from message producer"); + + gum::info!("Awaiting polkadot_parachain_subsystem_bounded_received to tells us the messages have been processed"); + let at_least_messages = + state.total_sent_messages_to_node.load(std::sync::atomic::Ordering::SeqCst) as usize; + env.wait_until_metric( + "polkadot_parachain_subsystem_bounded_received", + Some(("subsystem_name", "approval-distribution-subsystem")), + |value| { + gum::info!(target: LOG_TARGET, ?value, ?at_least_messages, "Waiting metric"); + value >= at_least_messages as f64 + }, + ) + .await; + gum::info!("Requesting approval votes ms"); + + for info in &state.blocks { + for (index, candidates) in info.candidates.iter().enumerate() { + match candidates { + CandidateEvent::CandidateBacked(_, _, _, _) => todo!(), + CandidateEvent::CandidateIncluded(receipt_fetch, _head, _, _) => { + let (tx, rx) = oneshot::channel(); + + let msg = ApprovalVotingMessage::GetApprovalSignaturesForCandidate( + receipt_fetch.hash(), + tx, + ); + env.send_message(AllMessages::ApprovalVoting(msg)).await; + + let result = rx.await.unwrap(); + + for (validator, _) in result.iter() { + info.votes + .get(validator.0 as usize) + .unwrap() + .get(index) + .unwrap() + .store(false, std::sync::atomic::Ordering::SeqCst); + } + }, + + CandidateEvent::CandidateTimedOut(_, _, _) => todo!(), + }; + } + } + + gum::info!("Awaiting polkadot_parachain_subsystem_bounded_received to tells us the messages have been processed"); + let at_least_messages = + state.total_sent_messages_to_node.load(std::sync::atomic::Ordering::SeqCst) as usize; + env.wait_until_metric( + "polkadot_parachain_subsystem_bounded_received", + Some(("subsystem_name", "approval-distribution-subsystem")), + |value| { + gum::info!(target: LOG_TARGET, ?value, ?at_least_messages, "Waiting metric"); + value >= at_least_messages as f64 + }, + ) + .await; + + for state in &state.blocks { + for (validator, votes) in state + .votes + .as_ref() + .iter() + .enumerate() + .filter(|(validator, _)| *validator != NODE_UNDER_TEST as usize) + { + for (index, candidate) in votes.iter().enumerate() { + assert_eq!( + ( + validator, + index, + candidate.load(std::sync::atomic::Ordering::SeqCst), + state.hash + ), + (validator, index, false, state.hash) + ); + } + } + } + + env.stop().await; + + let duration: u128 = start_marker.elapsed().as_millis(); + gum::info!( + "All blocks processed in {} total_sent_messages_to_node {} total_sent_messages_from_node {} num_unique_messages {}", + format!("{:?}ms", duration).cyan(), + state.total_sent_messages_to_node.load(std::sync::atomic::Ordering::SeqCst), + state.total_sent_messages_from_node.load(std::sync::atomic::Ordering::SeqCst), + state.total_unique_messages.load(std::sync::atomic::Ordering::SeqCst) + ); + + env.display_network_usage(); + env.display_cpu_usage(&["approval-distribution", "approval-voting"]); +} diff --git a/polkadot/node/subsystem-bench/src/approval/test_message.rs b/polkadot/node/subsystem-bench/src/approval/test_message.rs new file mode 100644 index 00000000000..ea578776f26 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/approval/test_message.rs @@ -0,0 +1,304 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use super::{ApprovalsOptions, BlockTestData, CandidateTestData}; +use crate::core::configuration::TestAuthorities; +use itertools::Itertools; +use parity_scale_codec::{Decode, Encode}; +use polkadot_node_network_protocol::v3 as protocol_v3; + +use polkadot_primitives::{CandidateIndex, Hash, ValidatorIndex}; +use sc_network::PeerId; +use std::collections::{HashMap, HashSet}; + +#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] +pub struct TestMessageInfo { + /// The actual message + pub msg: protocol_v3::ApprovalDistributionMessage, + /// The list of peers that would sends this message in a real topology. + /// It includes both the peers that would send the message because of the topology + /// or because of randomly chosing so. + pub sent_by: Vec, + /// The tranche at which this message should be sent. + pub tranche: u32, + /// The block hash this message refers to. + pub block_hash: Hash, +} + +impl std::hash::Hash for TestMessageInfo { + fn hash(&self, state: &mut H) { + match &self.msg { + protocol_v3::ApprovalDistributionMessage::Assignments(assignments) => { + for (assignment, candidates) in assignments { + (assignment.block_hash, assignment.validator).hash(state); + candidates.hash(state); + } + }, + protocol_v3::ApprovalDistributionMessage::Approvals(approvals) => { + for approval in approvals { + (approval.block_hash, approval.validator).hash(state); + approval.candidate_indices.hash(state); + } + }, + }; + } +} + +#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] +/// A list of messages that depend of each-other, approvals cover one of the assignments and +/// vice-versa. +pub struct MessagesBundle { + pub assignments: Vec, + pub approvals: Vec, +} + +impl MessagesBundle { + /// The tranche when this bundle can be sent correctly, so no assignments or approvals will be + /// from the future. + pub fn tranche_to_send(&self) -> u32 { + self.assignments + .iter() + .chain(self.approvals.iter()) + .max_by(|a, b| a.tranche.cmp(&b.tranche)) + .unwrap() + .tranche + } + + /// The min tranche in the bundle. + pub fn min_tranche(&self) -> u32 { + self.assignments + .iter() + .chain(self.approvals.iter()) + .min_by(|a, b| a.tranche.cmp(&b.tranche)) + .unwrap() + .tranche + } + + /// Tells if the bundle is needed for sending. + /// We either send it because we need more assignments and approvals to approve the candidates + /// or because we configured the test to send messages untill a given tranche. + pub fn should_send( + &self, + candidates_test_data: &HashMap<(Hash, CandidateIndex), CandidateTestData>, + options: &ApprovalsOptions, + ) -> bool { + self.needed_for_approval(candidates_test_data) || + (!options.stop_when_approved && + self.min_tranche() <= options.last_considered_tranche) + } + + /// Tells if the bundle is needed because we need more messages to approve the candidates. + pub fn needed_for_approval( + &self, + candidates_test_data: &HashMap<(Hash, CandidateIndex), CandidateTestData>, + ) -> bool { + self.assignments + .iter() + .any(|message| message.needed_for_approval(candidates_test_data)) + } + + /// Mark the assignments in the bundle as sent. + pub fn record_sent_assignment( + &self, + candidates_test_data: &mut HashMap<(Hash, CandidateIndex), CandidateTestData>, + ) { + self.assignments + .iter() + .for_each(|assignment| assignment.record_sent_assignment(candidates_test_data)); + } +} + +impl TestMessageInfo { + /// Tells if the message is an approval. + fn is_approval(&self) -> bool { + match self.msg { + protocol_v3::ApprovalDistributionMessage::Assignments(_) => false, + protocol_v3::ApprovalDistributionMessage::Approvals(_) => true, + } + } + + /// Records an approval. + /// We use this to check after all messages have been processed that we didn't loose any + /// message. + pub fn record_vote(&self, state: &BlockTestData) { + if self.is_approval() { + match &self.msg { + protocol_v3::ApprovalDistributionMessage::Assignments(_) => todo!(), + protocol_v3::ApprovalDistributionMessage::Approvals(approvals) => + for approval in approvals { + for candidate_index in approval.candidate_indices.iter_ones() { + state + .votes + .get(approval.validator.0 as usize) + .unwrap() + .get(candidate_index) + .unwrap() + .store(true, std::sync::atomic::Ordering::SeqCst); + } + }, + } + } + } + + /// Mark the assignments in the message as sent. + pub fn record_sent_assignment( + &self, + candidates_test_data: &mut HashMap<(Hash, CandidateIndex), CandidateTestData>, + ) { + match &self.msg { + protocol_v3::ApprovalDistributionMessage::Assignments(assignments) => { + for (assignment, candidate_indices) in assignments { + for candidate_index in candidate_indices.iter_ones() { + let candidate_test_data = candidates_test_data + .get_mut(&(assignment.block_hash, candidate_index as CandidateIndex)) + .unwrap(); + candidate_test_data.mark_sent_assignment(self.tranche) + } + } + }, + protocol_v3::ApprovalDistributionMessage::Approvals(_approvals) => todo!(), + } + } + + /// Returns a list of candidates indicies in this message + pub fn candidate_indices(&self) -> HashSet { + let mut unique_candidate_indicies = HashSet::new(); + match &self.msg { + protocol_v3::ApprovalDistributionMessage::Assignments(assignments) => + for (_assignment, candidate_indices) in assignments { + for candidate_index in candidate_indices.iter_ones() { + unique_candidate_indicies.insert(candidate_index); + } + }, + protocol_v3::ApprovalDistributionMessage::Approvals(approvals) => + for approval in approvals { + for candidate_index in approval.candidate_indices.iter_ones() { + unique_candidate_indicies.insert(candidate_index); + } + }, + } + unique_candidate_indicies + } + + /// Marks this message as no-shows if the number of configured no-shows is above the registered + /// no-shows. + /// Returns true if the message is a no-show. + pub fn no_show_if_required( + &self, + assignments: &[TestMessageInfo], + candidates_test_data: &mut HashMap<(Hash, CandidateIndex), CandidateTestData>, + ) -> bool { + let mut should_no_show = false; + if self.is_approval() { + let covered_candidates = assignments + .iter() + .map(|assignment| (assignment, assignment.candidate_indices())) + .collect_vec(); + + match &self.msg { + protocol_v3::ApprovalDistributionMessage::Assignments(_) => todo!(), + protocol_v3::ApprovalDistributionMessage::Approvals(approvals) => { + assert_eq!(approvals.len(), 1); + + for approval in approvals { + should_no_show = should_no_show || + approval.candidate_indices.iter_ones().all(|candidate_index| { + let candidate_test_data = candidates_test_data + .get_mut(&( + approval.block_hash, + candidate_index as CandidateIndex, + )) + .unwrap(); + let assignment = covered_candidates + .iter() + .find(|(_assignment, candidates)| { + candidates.contains(&candidate_index) + }) + .unwrap(); + candidate_test_data.should_no_show(assignment.0.tranche) + }); + + if should_no_show { + for candidate_index in approval.candidate_indices.iter_ones() { + let candidate_test_data = candidates_test_data + .get_mut(&( + approval.block_hash, + candidate_index as CandidateIndex, + )) + .unwrap(); + let assignment = covered_candidates + .iter() + .find(|(_assignment, candidates)| { + candidates.contains(&candidate_index) + }) + .unwrap(); + candidate_test_data.record_no_show(assignment.0.tranche) + } + } + } + }, + } + } + should_no_show + } + + /// Tells if a message is needed for approval + pub fn needed_for_approval( + &self, + candidates_test_data: &HashMap<(Hash, CandidateIndex), CandidateTestData>, + ) -> bool { + match &self.msg { + protocol_v3::ApprovalDistributionMessage::Assignments(assignments) => + assignments.iter().any(|(assignment, candidate_indices)| { + candidate_indices.iter_ones().any(|candidate_index| { + candidates_test_data + .get(&(assignment.block_hash, candidate_index as CandidateIndex)) + .map(|data| data.should_send_tranche(self.tranche)) + .unwrap_or_default() + }) + }), + protocol_v3::ApprovalDistributionMessage::Approvals(approvals) => + approvals.iter().any(|approval| { + approval.candidate_indices.iter_ones().any(|candidate_index| { + candidates_test_data + .get(&(approval.block_hash, candidate_index as CandidateIndex)) + .map(|data| data.should_send_tranche(self.tranche)) + .unwrap_or_default() + }) + }), + } + } + + /// Splits a message into multiple messages based on what peers should send this message. + /// It build a HashMap of messages that should be sent by each peer. + pub fn split_by_peer_id( + self, + authorities: &TestAuthorities, + ) -> HashMap<(ValidatorIndex, PeerId), Vec> { + let mut result: HashMap<(ValidatorIndex, PeerId), Vec> = HashMap::new(); + + for validator_index in &self.sent_by { + let peer = authorities.peer_ids.get(validator_index.0 as usize).unwrap(); + result.entry((*validator_index, *peer)).or_default().push(TestMessageInfo { + msg: self.msg.clone(), + sent_by: Default::default(), + tranche: self.tranche, + block_hash: self.block_hash, + }); + } + result + } +} diff --git a/polkadot/node/subsystem-bench/src/availability/av_store_helpers.rs b/polkadot/node/subsystem-bench/src/availability/av_store_helpers.rs index 18ea2f72891..e6827f1d8ae 100644 --- a/polkadot/node/subsystem-bench/src/availability/av_store_helpers.rs +++ b/polkadot/node/subsystem-bench/src/availability/av_store_helpers.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . +use crate::core::mock::TestSyncOracle; + use super::*; use polkadot_node_metrics::metrics::Metrics; @@ -31,22 +33,10 @@ mod columns { const TEST_CONFIG: Config = Config { col_data: columns::DATA, col_meta: columns::META }; -struct DumbOracle; - -impl sp_consensus::SyncOracle for DumbOracle { - fn is_major_syncing(&self) -> bool { - false - } - - fn is_offline(&self) -> bool { - unimplemented!("oh no!") - } -} - pub fn new_av_store(dependencies: &TestEnvironmentDependencies) -> AvailabilityStoreSubsystem { let metrics = Metrics::try_register(&dependencies.registry).unwrap(); - AvailabilityStoreSubsystem::new(test_store(), TEST_CONFIG, Box::new(DumbOracle), metrics) + AvailabilityStoreSubsystem::new(test_store(), TEST_CONFIG, Box::new(TestSyncOracle {}), metrics) } fn test_store() -> Arc { diff --git a/polkadot/node/subsystem-bench/src/availability/mod.rs b/polkadot/node/subsystem-bench/src/availability/mod.rs index f9892efb3c6..f7f1184448b 100644 --- a/polkadot/node/subsystem-bench/src/availability/mod.rs +++ b/polkadot/node/subsystem-bench/src/availability/mod.rs @@ -25,7 +25,7 @@ use polkadot_node_subsystem_types::{ messages::{AvailabilityStoreMessage, NetworkBridgeEvent}, Span, }; -use polkadot_overseer::Handle as OverseerHandle; +use polkadot_overseer::{metrics::Metrics as OverseerMetrics, Handle as OverseerHandle}; use sc_network::{request_responses::ProtocolConfig, PeerId}; use sp_core::H256; use std::{collections::HashMap, iter::Cycle, ops::Sub, sync::Arc, time::Instant}; @@ -85,9 +85,12 @@ fn build_overseer_for_availability_read( av_store: MockAvailabilityStore, network_bridge: (MockNetworkBridgeTx, MockNetworkBridgeRx), availability_recovery: AvailabilityRecoverySubsystem, + dependencies: &TestEnvironmentDependencies, ) -> (Overseer, AlwaysSupportsParachains>, OverseerHandle) { let overseer_connector = OverseerConnector::with_event_capacity(64000); - let dummy = dummy_builder!(spawn_task_handle); + let overseer_metrics = OverseerMetrics::try_register(&dependencies.registry).unwrap(); + + let dummy = dummy_builder!(spawn_task_handle, overseer_metrics); let builder = dummy .replace_runtime_api(|_| runtime_api) .replace_availability_store(|_| av_store) @@ -101,6 +104,7 @@ fn build_overseer_for_availability_read( (overseer, OverseerHandle::new(raw_handle)) } +#[allow(clippy::too_many_arguments)] fn build_overseer_for_availability_write( spawn_task_handle: SpawnTaskHandle, runtime_api: MockRuntimeApi, @@ -109,9 +113,12 @@ fn build_overseer_for_availability_write( chain_api: MockChainApi, availability_store: AvailabilityStoreSubsystem, bitfield_distribution: BitfieldDistribution, + dependencies: &TestEnvironmentDependencies, ) -> (Overseer, AlwaysSupportsParachains>, OverseerHandle) { let overseer_connector = OverseerConnector::with_event_capacity(64000); - let dummy = dummy_builder!(spawn_task_handle); + let overseer_metrics = OverseerMetrics::try_register(&dependencies.registry).unwrap(); + + let dummy = dummy_builder!(spawn_task_handle, overseer_metrics); let builder = dummy .replace_runtime_api(|_| runtime_api) .replace_availability_store(|_| availability_store) @@ -171,6 +178,9 @@ fn prepare_test_inner( config.clone(), test_authorities.clone(), candidate_hashes, + Default::default(), + Default::default(), + 0, ); let availability_state = NetworkAvailabilityState { @@ -198,6 +208,7 @@ fn prepare_test_inner( let network_bridge_tx = network_bridge::MockNetworkBridgeTx::new( network.clone(), network_interface.subsystem_sender(), + test_authorities.clone(), ); let network_bridge_rx = @@ -231,6 +242,7 @@ fn prepare_test_inner( av_store, (network_bridge_tx, network_bridge_rx), subsystem, + &dependencies, ) }, TestObjective::DataAvailabilityWrite => { @@ -240,7 +252,7 @@ fn prepare_test_inner( Metrics::try_register(&dependencies.registry).unwrap(), ); - let block_headers = (0..=config.num_blocks) + let block_headers = (1..=config.num_blocks) .map(|block_number| { ( Hash::repeat_byte(block_number as u8), @@ -267,6 +279,7 @@ fn prepare_test_inner( chain_api, new_av_store(&dependencies), bitfield_distribution, + &dependencies, ) }, _ => { @@ -614,9 +627,10 @@ pub async fn benchmark_availability_write(env: &mut TestEnvironment, mut state: ); // Wait for all bitfields to be processed. - env.wait_until_metric_eq( + env.wait_until_metric( "polkadot_parachain_received_availabilty_bitfields_total", - config.connected_count() * block_num, + None, + |value| value == (config.connected_count() * block_num) as f64, ) .await; diff --git a/polkadot/node/subsystem-bench/src/cli.rs b/polkadot/node/subsystem-bench/src/cli.rs index 7213713eb6b..bfce8cc183a 100644 --- a/polkadot/node/subsystem-bench/src/cli.rs +++ b/polkadot/node/subsystem-bench/src/cli.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . use super::availability::DataAvailabilityReadOptions; +use crate::approval::ApprovalsOptions; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, clap::Parser)] @@ -34,6 +35,9 @@ pub enum TestObjective { DataAvailabilityWrite, /// Run a test sequence specified in a file TestSequence(TestSequenceOptions), + /// Benchmark the approval-voting and approval-distribution subsystems. + ApprovalVoting(ApprovalsOptions), + Unimplemented, } #[derive(Debug, clap::Parser)] diff --git a/polkadot/node/subsystem-bench/src/core/configuration.rs b/polkadot/node/subsystem-bench/src/core/configuration.rs index 66da8a1db45..0c8a78c504c 100644 --- a/polkadot/node/subsystem-bench/src/core/configuration.rs +++ b/polkadot/node/subsystem-bench/src/core/configuration.rs @@ -16,11 +16,14 @@ // //! Test configuration definition and helpers. use super::*; +use itertools::Itertools; use keyring::Keyring; -use std::path::Path; +use sc_network::PeerId; +use sp_consensus_babe::AuthorityId; +use std::{collections::HashMap, path::Path}; pub use crate::cli::TestObjective; -use polkadot_primitives::{AuthorityDiscoveryId, ValidatorId}; +use polkadot_primitives::{AssignmentId, AuthorityDiscoveryId, ValidatorId}; use rand::thread_rng; use rand_distr::{Distribution, Normal, Uniform}; @@ -65,6 +68,25 @@ fn default_backing_group_size() -> usize { 5 } +// Default needed approvals +fn default_needed_approvals() -> usize { + 30 +} + +fn default_zeroth_delay_tranche_width() -> usize { + 0 +} +fn default_relay_vrf_modulo_samples() -> usize { + 6 +} + +fn default_n_delay_tranches() -> usize { + 89 +} +fn default_no_show_slots() -> usize { + 3 +} + /// The test input parameters #[derive(Clone, Debug, Serialize, Deserialize)] pub struct TestConfiguration { @@ -74,6 +96,17 @@ pub struct TestConfiguration { pub n_validators: usize, /// Number of cores pub n_cores: usize, + /// The number of needed votes to approve a candidate. + #[serde(default = "default_needed_approvals")] + pub needed_approvals: usize, + #[serde(default = "default_zeroth_delay_tranche_width")] + pub zeroth_delay_tranche_width: usize, + #[serde(default = "default_relay_vrf_modulo_samples")] + pub relay_vrf_modulo_samples: usize, + #[serde(default = "default_n_delay_tranches")] + pub n_delay_tranches: usize, + #[serde(default = "default_no_show_slots")] + pub no_show_slots: usize, /// Maximum backing group size #[serde(default = "default_backing_group_size")] pub max_validators_per_core: usize, @@ -139,6 +172,11 @@ pub struct TestAuthorities { pub keyring: Keyring, pub validator_public: Vec, pub validator_authority_id: Vec, + pub validator_babe_id: Vec, + pub validator_assignment_id: Vec, + pub key_seeds: Vec, + pub peer_ids: Vec, + pub peer_id_to_authority: HashMap, } impl TestConfiguration { @@ -162,18 +200,45 @@ impl TestConfiguration { pub fn generate_authorities(&self) -> TestAuthorities { let keyring = Keyring::default(); - let keys = (0..self.n_validators) - .map(|peer_index| keyring.sr25519_new(format!("Node{}", peer_index))) + let key_seeds = (0..self.n_validators) + .map(|peer_index| format!("//Node{}", peer_index)) + .collect_vec(); + + let keys = key_seeds + .iter() + .map(|seed| keyring.sr25519_new(seed.as_str())) .collect::>(); - // Generate `AuthorityDiscoveryId`` for each peer + // Generate keys and peers ids in each of the format needed by the tests. let validator_public: Vec = keys.iter().map(|key| (*key).into()).collect::>(); let validator_authority_id: Vec = keys.iter().map(|key| (*key).into()).collect::>(); - TestAuthorities { keyring, validator_public, validator_authority_id } + let validator_babe_id: Vec = + keys.iter().map(|key| (*key).into()).collect::>(); + + let validator_assignment_id: Vec = + keys.iter().map(|key| (*key).into()).collect::>(); + let peer_ids: Vec = keys.iter().map(|_| PeerId::random()).collect::>(); + + let peer_id_to_authority = peer_ids + .iter() + .zip(validator_authority_id.iter()) + .map(|(peer_id, authorithy_id)| (*peer_id, authorithy_id.clone())) + .collect(); + + TestAuthorities { + keyring, + validator_public, + validator_authority_id, + peer_ids, + validator_babe_id, + validator_assignment_id, + key_seeds, + peer_id_to_authority, + } } /// An unconstrained standard configuration matching Polkadot/Kusama @@ -199,6 +264,11 @@ impl TestConfiguration { min_pov_size, max_pov_size, connectivity: 100, + needed_approvals: default_needed_approvals(), + n_delay_tranches: default_n_delay_tranches(), + no_show_slots: default_no_show_slots(), + relay_vrf_modulo_samples: default_relay_vrf_modulo_samples(), + zeroth_delay_tranche_width: default_zeroth_delay_tranche_width(), } } @@ -223,6 +293,11 @@ impl TestConfiguration { min_pov_size, max_pov_size, connectivity: 95, + needed_approvals: default_needed_approvals(), + n_delay_tranches: default_n_delay_tranches(), + no_show_slots: default_no_show_slots(), + relay_vrf_modulo_samples: default_relay_vrf_modulo_samples(), + zeroth_delay_tranche_width: default_zeroth_delay_tranche_width(), } } @@ -247,6 +322,11 @@ impl TestConfiguration { min_pov_size, max_pov_size, connectivity: 67, + needed_approvals: default_needed_approvals(), + n_delay_tranches: default_n_delay_tranches(), + no_show_slots: default_no_show_slots(), + relay_vrf_modulo_samples: default_relay_vrf_modulo_samples(), + zeroth_delay_tranche_width: default_zeroth_delay_tranche_width(), } } } diff --git a/polkadot/node/subsystem-bench/src/core/display.rs b/polkadot/node/subsystem-bench/src/core/display.rs index bca82d7b90a..b130afdcfad 100644 --- a/polkadot/node/subsystem-bench/src/core/display.rs +++ b/polkadot/node/subsystem-bench/src/core/display.rs @@ -26,7 +26,7 @@ use prometheus::{ }; use std::fmt::Display; -#[derive(Default)] +#[derive(Default, Debug)] pub struct MetricCollection(Vec); impl From> for MetricCollection { @@ -49,6 +49,11 @@ impl MetricCollection { .sum() } + /// Tells if entries in bucket metric is lower than `value` + pub fn metric_lower_than(&self, metric_name: &str, value: f64) -> bool { + self.sum_by(metric_name) < value + } + pub fn subset_with_label_value(&self, label_name: &str, label_value: &str) -> MetricCollection { self.0 .iter() @@ -163,7 +168,7 @@ pub fn parse_metrics(registry: &Registry) -> MetricCollection { name: h_name, label_names, label_values, - value: h.get_sample_sum(), + value: h.get_sample_count() as f64, }); }, MetricType::SUMMARY => { diff --git a/polkadot/node/subsystem-bench/src/core/environment.rs b/polkadot/node/subsystem-bench/src/core/environment.rs index b6846316430..59bfed7f112 100644 --- a/polkadot/node/subsystem-bench/src/core/environment.rs +++ b/polkadot/node/subsystem-bench/src/core/environment.rs @@ -243,6 +243,11 @@ impl TestEnvironment { &self.network } + /// Returns a reference to the overseer handle. + pub fn overseer_handle(&self) -> &OverseerHandle { + &self.overseer_handle + } + /// Returns the Prometheus registry. pub fn registry(&self) -> &Registry { &self.dependencies.registry @@ -311,18 +316,32 @@ impl TestEnvironment { self.overseer_handle.stop().await; } - /// Blocks until `metric_name` == `value` - pub async fn wait_until_metric_eq(&self, metric_name: &str, value: usize) { - let value = value as f64; + /// Tells if entries in bucket metric is lower than `value` + pub fn metric_lower_than(registry: &Registry, metric_name: &str, value: f64) -> bool { + let test_metrics = super::display::parse_metrics(registry); + test_metrics.metric_lower_than(metric_name, value) + } + + /// Blocks until `metric_name` >= `value` + pub async fn wait_until_metric( + &self, + metric_name: &str, + label: Option<(&str, &str)>, + condition: impl Fn(f64) -> bool, + ) { loop { - let test_metrics = super::display::parse_metrics(self.registry()); + let test_metrics = if let Some((label_name, label_value)) = label { + super::display::parse_metrics(self.registry()) + .subset_with_label_value(label_name, label_value) + } else { + super::display::parse_metrics(self.registry()) + }; let current_value = test_metrics.sum_by(metric_name); - gum::debug!(target: LOG_TARGET, metric_name, current_value, value, "Waiting for metric"); - if current_value == value { + gum::debug!(target: LOG_TARGET, metric_name, current_value, "Waiting for metric"); + if condition(current_value) { break } - // Check value every 50ms. tokio::time::sleep(std::time::Duration::from_millis(50)).await; } diff --git a/polkadot/node/subsystem-bench/src/core/keyring.rs b/polkadot/node/subsystem-bench/src/core/keyring.rs index 66c7229847c..c290d30b46f 100644 --- a/polkadot/node/subsystem-bench/src/core/keyring.rs +++ b/polkadot/node/subsystem-bench/src/core/keyring.rs @@ -34,13 +34,17 @@ impl Default for Keyring { } impl Keyring { - pub fn sr25519_new(&self, name: String) -> Public { + pub fn sr25519_new(&self, seed: &str) -> Public { self.keystore - .sr25519_generate_new(ValidatorId::ID, Some(&format!("//{}", name))) + .sr25519_generate_new(ValidatorId::ID, Some(seed)) .expect("Insert key into keystore") } pub fn keystore(&self) -> Arc { self.keystore.clone() } + + pub fn keystore_ref(&self) -> &LocalKeystore { + self.keystore.as_ref() + } } diff --git a/polkadot/node/subsystem-bench/src/core/mock/chain_api.rs b/polkadot/node/subsystem-bench/src/core/mock/chain_api.rs index 008d8eef106..7a5ee80de80 100644 --- a/polkadot/node/subsystem-bench/src/core/mock/chain_api.rs +++ b/polkadot/node/subsystem-bench/src/core/mock/chain_api.rs @@ -16,6 +16,7 @@ //! //! A generic runtime api subsystem mockup suitable to be used in benchmarks. +use itertools::Itertools; use polkadot_primitives::Header; use polkadot_node_subsystem::{ @@ -38,6 +39,12 @@ pub struct MockChainApi { state: ChainApiState, } +impl ChainApiState { + fn get_header_by_number(&self, requested_number: u32) -> Option<&Header> { + self.block_headers.values().find(|header| header.number == requested_number) + } +} + impl MockChainApi { pub fn new(state: ChainApiState) -> MockChainApi { Self { state } @@ -77,9 +84,44 @@ impl MockChainApi { .expect("Relay chain block hashes are known"), ))); }, - ChainApiMessage::Ancestors { hash: _hash, k: _k, response_channel } => { - // For our purposes, no ancestors is fine. - let _ = response_channel.send(Ok(Vec::new())); + ChainApiMessage::FinalizedBlockNumber(val) => { + val.send(Ok(0)).unwrap(); + }, + ChainApiMessage::FinalizedBlockHash(requested_number, sender) => { + let hash = self + .state + .get_header_by_number(requested_number) + .expect("Unknow block number") + .hash(); + sender.send(Ok(Some(hash))).unwrap(); + }, + ChainApiMessage::BlockNumber(requested_hash, sender) => { + sender + .send(Ok(Some( + self.state + .block_headers + .get(&requested_hash) + .expect("Unknown block hash") + .number, + ))) + .unwrap(); + }, + ChainApiMessage::Ancestors { hash, k: _, response_channel } => { + let block_number = self + .state + .block_headers + .get(&hash) + .expect("Unknown block hash") + .number; + let ancestors = self + .state + .block_headers + .iter() + .filter(|(_, header)| header.number < block_number) + .sorted_by(|a, b| a.1.number.cmp(&b.1.number)) + .map(|(hash, _)| *hash) + .collect_vec(); + response_channel.send(Ok(ancestors)).unwrap(); }, _ => { unimplemented!("Unexpected chain-api message") diff --git a/polkadot/node/subsystem-bench/src/core/mock/mod.rs b/polkadot/node/subsystem-bench/src/core/mock/mod.rs index b67c6611e8c..e766b07e8b1 100644 --- a/polkadot/node/subsystem-bench/src/core/mock/mod.rs +++ b/polkadot/node/subsystem-bench/src/core/mock/mod.rs @@ -37,7 +37,7 @@ impl HeadSupportsParachains for AlwaysSupportsParachains { // An orchestra with dummy subsystems macro_rules! dummy_builder { - ($spawn_task_handle: ident) => {{ + ($spawn_task_handle: ident, $metrics: ident) => {{ use super::core::mock::dummy::*; // Initialize a mock overseer. @@ -69,10 +69,24 @@ macro_rules! dummy_builder { .activation_external_listeners(Default::default()) .span_per_active_leaf(Default::default()) .active_leaves(Default::default()) - .metrics(Default::default()) + .metrics($metrics) .supports_parachains(AlwaysSupportsParachains {}) .spawner(SpawnGlue($spawn_task_handle)) }}; } pub(crate) use dummy_builder; +use sp_consensus::SyncOracle; + +#[derive(Clone)] +pub struct TestSyncOracle {} + +impl SyncOracle for TestSyncOracle { + fn is_major_syncing(&self) -> bool { + false + } + + fn is_offline(&self) -> bool { + unimplemented!("not used by subsystem benchmarks") + } +} diff --git a/polkadot/node/subsystem-bench/src/core/mock/network_bridge.rs b/polkadot/node/subsystem-bench/src/core/mock/network_bridge.rs index a2be853ef8d..a171deb2e71 100644 --- a/polkadot/node/subsystem-bench/src/core/mock/network_bridge.rs +++ b/polkadot/node/subsystem-bench/src/core/mock/network_bridge.rs @@ -18,11 +18,11 @@ //! the emulated network. use futures::{channel::mpsc::UnboundedSender, FutureExt, StreamExt}; use polkadot_node_subsystem_types::{ - messages::{BitfieldDistributionMessage, NetworkBridgeEvent}, + messages::{ApprovalDistributionMessage, BitfieldDistributionMessage, NetworkBridgeEvent}, OverseerSignal, }; -use sc_network::{request_responses::ProtocolConfig, PeerId, RequestFailure}; +use sc_network::{request_responses::ProtocolConfig, RequestFailure}; use polkadot_node_subsystem::{ messages::NetworkBridgeTxMessage, overseer, SpawnedSubsystem, SubsystemError, @@ -30,8 +30,9 @@ use polkadot_node_subsystem::{ use polkadot_node_network_protocol::Versioned; -use crate::core::network::{ - NetworkEmulatorHandle, NetworkInterfaceReceiver, NetworkMessage, RequestExt, +use crate::core::{ + configuration::TestAuthorities, + network::{NetworkEmulatorHandle, NetworkInterfaceReceiver, NetworkMessage, RequestExt}, }; const LOG_TARGET: &str = "subsystem-bench::network-bridge"; @@ -44,6 +45,8 @@ pub struct MockNetworkBridgeTx { network: NetworkEmulatorHandle, /// A channel to the network interface, to_network_interface: UnboundedSender, + /// Test authorithies + test_authorithies: TestAuthorities, } /// A mock of the network bridge tx subsystem. @@ -58,8 +61,9 @@ impl MockNetworkBridgeTx { pub fn new( network: NetworkEmulatorHandle, to_network_interface: UnboundedSender, + test_authorithies: TestAuthorities, ) -> MockNetworkBridgeTx { - Self { network, to_network_interface } + Self { network, to_network_interface, test_authorithies } } } @@ -126,9 +130,21 @@ impl MockNetworkBridgeTx { NetworkBridgeTxMessage::ReportPeer(_) => { // ingore rep changes }, - _ => { - unimplemented!("Unexpected network bridge message") + NetworkBridgeTxMessage::SendValidationMessage(peers, message) => { + for peer in peers { + self.to_network_interface + .unbounded_send(NetworkMessage::MessageFromNode( + self.test_authorithies + .peer_id_to_authority + .get(&peer) + .unwrap() + .clone(), + message.clone(), + )) + .expect("Should not fail"); + } }, + _ => unimplemented!("Unexpected network bridge message"), }, } } @@ -145,16 +161,23 @@ impl MockNetworkBridgeRx { maybe_peer_message = from_network_interface.next() => { if let Some(message) = maybe_peer_message { match message { - NetworkMessage::MessageFromPeer(message) => match message { + NetworkMessage::MessageFromPeer(peer_id, message) => match message { Versioned::V2( polkadot_node_network_protocol::v2::ValidationProtocol::BitfieldDistribution( bitfield, ), ) => { ctx.send_message( - BitfieldDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage(PeerId::random(), polkadot_node_network_protocol::Versioned::V2(bitfield))) + BitfieldDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage(peer_id, polkadot_node_network_protocol::Versioned::V2(bitfield))) ).await; }, + Versioned::V3( + polkadot_node_network_protocol::v3::ValidationProtocol::ApprovalDistribution(msg) + ) => { + ctx.send_message( + ApprovalDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage(peer_id, polkadot_node_network_protocol::Versioned::V3(msg))) + ).await; + } _ => { unimplemented!("We only talk v2 network protocol") }, diff --git a/polkadot/node/subsystem-bench/src/core/mock/runtime_api.rs b/polkadot/node/subsystem-bench/src/core/mock/runtime_api.rs index caefe068eff..ca6896dbb29 100644 --- a/polkadot/node/subsystem-bench/src/core/mock/runtime_api.rs +++ b/polkadot/node/subsystem-bench/src/core/mock/runtime_api.rs @@ -16,8 +16,10 @@ //! //! A generic runtime api subsystem mockup suitable to be used in benchmarks. +use itertools::Itertools; use polkadot_primitives::{ - CandidateReceipt, CoreState, GroupIndex, IndexedVec, OccupiedCore, SessionInfo, ValidatorIndex, + vstaging::NodeFeatures, CandidateEvent, CandidateReceipt, CoreState, GroupIndex, IndexedVec, + OccupiedCore, SessionIndex, SessionInfo, ValidatorIndex, }; use bitvec::prelude::BitVec; @@ -26,6 +28,7 @@ use polkadot_node_subsystem::{ overseer, SpawnedSubsystem, SubsystemError, }; use polkadot_node_subsystem_types::OverseerSignal; +use sp_consensus_babe::Epoch as BabeEpoch; use sp_core::H256; use std::collections::HashMap; @@ -38,8 +41,13 @@ const LOG_TARGET: &str = "subsystem-bench::runtime-api-mock"; pub struct RuntimeApiState { // All authorities in the test, authorities: TestAuthorities, - // Candidate + // Candidate hashes per block candidate_hashes: HashMap>, + // Included candidates per bock + included_candidates: HashMap>, + babe_epoch: Option, + // The session child index, + session_index: SessionIndex, } /// A mocked `runtime-api` subsystem. @@ -53,34 +61,57 @@ impl MockRuntimeApi { config: TestConfiguration, authorities: TestAuthorities, candidate_hashes: HashMap>, + included_candidates: HashMap>, + babe_epoch: Option, + session_index: SessionIndex, ) -> MockRuntimeApi { - Self { state: RuntimeApiState { authorities, candidate_hashes }, config } + Self { + state: RuntimeApiState { + authorities, + candidate_hashes, + included_candidates, + babe_epoch, + session_index, + }, + config, + } } fn session_info(&self) -> SessionInfo { - let all_validators = (0..self.config.n_validators) - .map(|i| ValidatorIndex(i as _)) - .collect::>(); - - let validator_groups = all_validators - .chunks(self.config.max_validators_per_core) - .map(Vec::from) - .collect::>(); - SessionInfo { - validators: self.state.authorities.validator_public.clone().into(), - discovery_keys: self.state.authorities.validator_authority_id.clone(), - validator_groups: IndexedVec::>::from(validator_groups), - assignment_keys: vec![], - n_cores: self.config.n_cores as u32, - zeroth_delay_tranche_width: 0, - relay_vrf_modulo_samples: 0, - n_delay_tranches: 0, - no_show_slots: 0, - needed_approvals: 0, - active_validator_indices: vec![], - dispute_period: 6, - random_seed: [0u8; 32], - } + session_info_for_peers(&self.config, &self.state.authorities) + } +} + +/// Generates a test session info with all passed authorities as consensus validators. +pub fn session_info_for_peers( + configuration: &TestConfiguration, + authorities: &TestAuthorities, +) -> SessionInfo { + let all_validators = (0..configuration.n_validators) + .map(|i| ValidatorIndex(i as _)) + .collect::>(); + + let validator_groups = all_validators + .chunks(configuration.max_validators_per_core) + .map(Vec::from) + .collect::>(); + + SessionInfo { + validators: authorities.validator_public.iter().cloned().collect(), + discovery_keys: authorities.validator_authority_id.to_vec(), + assignment_keys: authorities.validator_assignment_id.to_vec(), + validator_groups: IndexedVec::>::from(validator_groups), + n_cores: configuration.n_cores as u32, + needed_approvals: configuration.needed_approvals as u32, + zeroth_delay_tranche_width: configuration.zeroth_delay_tranche_width as u32, + relay_vrf_modulo_samples: configuration.relay_vrf_modulo_samples as u32, + n_delay_tranches: configuration.n_delay_tranches as u32, + no_show_slots: configuration.no_show_slots as u32, + active_validator_indices: (0..authorities.validator_authority_id.len()) + .map(|index| ValidatorIndex(index as u32)) + .collect_vec(), + dispute_period: 6, + random_seed: [0u8; 32], } } @@ -110,6 +141,13 @@ impl MockRuntimeApi { gum::debug!(target: LOG_TARGET, msg=?msg, "recv message"); match msg { + RuntimeApiMessage::Request( + request, + RuntimeApiRequest::CandidateEvents(sender), + ) => { + let candidate_events = self.state.included_candidates.get(&request); + let _ = sender.send(Ok(candidate_events.cloned().unwrap_or_default())); + }, RuntimeApiMessage::Request( _block_hash, RuntimeApiRequest::SessionInfo(_session_index, sender), @@ -123,24 +161,24 @@ impl MockRuntimeApi { let _ = sender.send(Ok(Some(Default::default()))); }, RuntimeApiMessage::Request( - _block_hash, - RuntimeApiRequest::Validators(sender), + _request, + RuntimeApiRequest::NodeFeatures(_session_index, sender), ) => { - let _ = - sender.send(Ok(self.state.authorities.validator_public.clone())); + let _ = sender.send(Ok(NodeFeatures::EMPTY)); }, RuntimeApiMessage::Request( _block_hash, - RuntimeApiRequest::CandidateEvents(sender), + RuntimeApiRequest::Validators(sender), ) => { - let _ = sender.send(Ok(Default::default())); + let _ = + sender.send(Ok(self.state.authorities.validator_public.clone())); }, RuntimeApiMessage::Request( _block_hash, RuntimeApiRequest::SessionIndexForChild(sender), ) => { // Session is always the same. - let _ = sender.send(Ok(0)); + let _ = sender.send(Ok(self.state.session_index)); }, RuntimeApiMessage::Request( block_hash, @@ -176,10 +214,14 @@ impl MockRuntimeApi { let _ = sender.send(Ok(cores)); }, RuntimeApiMessage::Request( - _block_hash, - RuntimeApiRequest::NodeFeatures(_session_index, sender), + _request, + RuntimeApiRequest::CurrentBabeEpoch(sender), ) => { - let _ = sender.send(Ok(Default::default())); + let _ = sender.send(Ok(self + .state + .babe_epoch + .clone() + .expect("Babe epoch unpopulated"))); }, // Long term TODO: implement more as needed. message => { diff --git a/polkadot/node/subsystem-bench/src/core/mod.rs b/polkadot/node/subsystem-bench/src/core/mod.rs index 282788d143b..507dd1aa83f 100644 --- a/polkadot/node/subsystem-bench/src/core/mod.rs +++ b/polkadot/node/subsystem-bench/src/core/mod.rs @@ -15,6 +15,8 @@ // along with Polkadot. If not, see . const LOG_TARGET: &str = "subsystem-bench::core"; +// The validator index that represent the node that is under test. +pub const NODE_UNDER_TEST: u32 = 0; pub mod configuration; pub mod display; diff --git a/polkadot/node/subsystem-bench/src/core/network.rs b/polkadot/node/subsystem-bench/src/core/network.rs index e2932bf0f51..8e7c2814063 100644 --- a/polkadot/node/subsystem-bench/src/core/network.rs +++ b/polkadot/node/subsystem-bench/src/core/network.rs @@ -10,7 +10,6 @@ // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. - // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . //! @@ -48,17 +47,21 @@ use futures::{ stream::FuturesUnordered, }; +use itertools::Itertools; use net_protocol::{ + peer_set::{ProtocolVersion, ValidationVersion}, request_response::{Recipient, Requests, ResponseSender}, - VersionedValidationProtocol, + ObservedRole, VersionedValidationProtocol, }; use parity_scale_codec::Encode; +use polkadot_node_subsystem_types::messages::{ApprovalDistributionMessage, NetworkBridgeEvent}; +use polkadot_overseer::AllMessages; use polkadot_primitives::AuthorityDiscoveryId; use prometheus_endpoint::U64; use rand::{seq::SliceRandom, thread_rng}; use sc_network::{ request_responses::{IncomingRequest, OutgoingResponse}, - RequestFailure, + PeerId, RequestFailure, }; use sc_service::SpawnTaskHandle; use std::{ @@ -142,7 +145,7 @@ impl RateLimit { /// peer(`AuthorityDiscoveryId``). pub enum NetworkMessage { /// A gossip message from peer to node. - MessageFromPeer(VersionedValidationProtocol), + MessageFromPeer(PeerId, VersionedValidationProtocol), /// A gossip message from node to a peer. MessageFromNode(AuthorityDiscoveryId, VersionedValidationProtocol), /// A request originating from our node @@ -155,9 +158,9 @@ impl NetworkMessage { /// Returns the size of the encoded message or request pub fn size(&self) -> usize { match &self { - NetworkMessage::MessageFromPeer(Versioned::V2(message)) => message.encoded_size(), - NetworkMessage::MessageFromPeer(Versioned::V1(message)) => message.encoded_size(), - NetworkMessage::MessageFromPeer(Versioned::V3(message)) => message.encoded_size(), + NetworkMessage::MessageFromPeer(_, Versioned::V2(message)) => message.encoded_size(), + NetworkMessage::MessageFromPeer(_, Versioned::V1(message)) => message.encoded_size(), + NetworkMessage::MessageFromPeer(_, Versioned::V3(message)) => message.encoded_size(), NetworkMessage::MessageFromNode(_peer_id, Versioned::V2(message)) => message.encoded_size(), NetworkMessage::MessageFromNode(_peer_id, Versioned::V1(message)) => @@ -430,6 +433,7 @@ pub struct EmulatedPeerHandle { messages_tx: UnboundedSender, /// Send actions to be performed by the peer. actions_tx: UnboundedSender, + peer_id: PeerId, } impl EmulatedPeerHandle { @@ -441,7 +445,7 @@ impl EmulatedPeerHandle { /// Send a message to the node. pub fn send_message(&self, message: VersionedValidationProtocol) { self.actions_tx - .unbounded_send(NetworkMessage::MessageFromPeer(message)) + .unbounded_send(NetworkMessage::MessageFromPeer(self.peer_id, message)) .expect("Peer action channel hangup"); } @@ -613,6 +617,7 @@ pub fn new_peer( stats: Arc, to_network_interface: UnboundedSender, latency_ms: usize, + peer_id: PeerId, ) -> EmulatedPeerHandle { let (messages_tx, messages_rx) = mpsc::unbounded::(); let (actions_tx, actions_rx) = mpsc::unbounded::(); @@ -641,7 +646,7 @@ pub fn new_peer( .boxed(), ); - EmulatedPeerHandle { messages_tx, actions_tx } + EmulatedPeerHandle { messages_tx, actions_tx, peer_id } } /// Book keeping of sent and received bytes. @@ -719,6 +724,28 @@ pub struct NetworkEmulatorHandle { validator_authority_ids: HashMap, } +impl NetworkEmulatorHandle { + /// Generates peer_connected messages for all peers in `test_authorities` + pub fn generate_peer_connected(&self) -> Vec { + self.peers + .iter() + .filter(|peer| peer.is_connected()) + .map(|peer| { + let network = NetworkBridgeEvent::PeerConnected( + peer.handle().peer_id, + ObservedRole::Full, + ProtocolVersion::from(ValidationVersion::V3), + None, + ); + + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NetworkBridgeUpdate( + network, + )) + }) + .collect_vec() + } +} + /// Create a new emulated network based on `config`. /// Each emulated peer will run the specified `handlers` to process incoming messages. pub fn new_network( @@ -753,6 +780,7 @@ pub fn new_network( stats, to_network_interface.clone(), random_latency(config.latency.as_ref()), + *authorities.peer_ids.get(peer_index).unwrap(), )), ) }) @@ -760,10 +788,14 @@ pub fn new_network( let connected_count = config.connected_count(); - let (_connected, to_disconnect) = peers.partial_shuffle(&mut thread_rng(), connected_count); + let mut peers_indicies = (0..n_peers).collect_vec(); + let (_connected, to_disconnect) = + peers_indicies.partial_shuffle(&mut thread_rng(), connected_count); - for peer in to_disconnect { - peer.disconnect(); + // Node under test is always mark as disconnected. + peers[NODE_UNDER_TEST as usize].disconnect(); + for peer in to_disconnect.iter().skip(1) { + peers[*peer].disconnect(); } gum::info!(target: LOG_TARGET, "{}",format!("Network created, connected validator count {}", connected_count).bright_black()); @@ -786,6 +818,7 @@ pub fn new_network( } /// Errors that can happen when sending data to emulated peers. +#[derive(Clone, Debug)] pub enum EmulatedPeerError { NotConnected, } diff --git a/polkadot/node/subsystem-bench/src/subsystem-bench.rs b/polkadot/node/subsystem-bench/src/subsystem-bench.rs index 8633ebb703a..6f45214bc73 100644 --- a/polkadot/node/subsystem-bench/src/subsystem-bench.rs +++ b/polkadot/node/subsystem-bench/src/subsystem-bench.rs @@ -26,6 +26,7 @@ use pyroscope_pprofrs::{pprof_backend, PprofConfig}; use std::path::Path; +pub(crate) mod approval; pub(crate) mod availability; pub(crate) mod cli; pub(crate) mod core; @@ -43,7 +44,7 @@ use core::{ use clap_num::number_range; -use crate::core::display::display_configuration; +use crate::{approval::bench_approvals, core::display::display_configuration}; fn le_100(s: &str) -> Result { number_range(s, 0, 100) @@ -174,6 +175,12 @@ impl BenchCli { &mut env, state, )); }, + TestObjective::ApprovalVoting(ref options) => { + let (mut env, state) = + approval::prepare_test(test_config.clone(), options.clone()); + + env.runtime().block_on(bench_approvals(&mut env, state)); + }, TestObjective::DataAvailabilityWrite => { let mut state = TestState::new(&test_config); let (mut env, _protocol_config) = prepare_test(test_config, &mut state); @@ -181,13 +188,16 @@ impl BenchCli { &mut env, state, )); }, - _ => gum::error!("Invalid test objective in sequence"), + TestObjective::TestSequence(_) => todo!(), + TestObjective::Unimplemented => todo!(), } } return Ok(()) }, TestObjective::DataAvailabilityRead(ref _options) => self.create_test_configuration(), TestObjective::DataAvailabilityWrite => self.create_test_configuration(), + TestObjective::ApprovalVoting(_) => todo!(), + TestObjective::Unimplemented => todo!(), }; let mut latency_config = test_config.latency.clone().unwrap_or_default(); @@ -232,6 +242,8 @@ impl BenchCli { .block_on(availability::benchmark_availability_write(&mut env, state)); }, TestObjective::TestSequence(_options) => {}, + TestObjective::ApprovalVoting(_) => todo!(), + TestObjective::Unimplemented => todo!(), } if let Some(agent_running) = agent_running { -- GitLab From 53f615de503c8936c2639f51b99019c64eab1018 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 07:54:27 +0000 Subject: [PATCH 013/133] Bump indicatif from 0.17.6 to 0.17.7 (#3200) Bumps [indicatif](https://github.com/console-rs/indicatif) from 0.17.6 to 0.17.7.
Commits
  • 0c037ed Bump version to 0.17.7 (#589)
  • 4461012 Fix attempt to subtract with overflow (#582) (#586)
  • 257d3ec Bump actions/checkout from 3 to 4
  • 40b40d2 fix unnecessary vec! lint instances
  • a5a8524 Tick ProgressTrackers before drawing
  • 75fca29 Add scheduled CI runs every week
  • c0ea468 Upgrade to 2021 edition
  • 73a67f8 Bump MSRV to 1.63 for tokio 1.30
  • de09017 Reorder Cargo metadata fields
  • cee6fd4 Fix a potential overflow with a saturating add.
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=indicatif&package-manager=cargo&previous-version=0.17.6&new-version=0.17.7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- substrate/utils/frame/remote-externalities/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 540bcc327a3..8e2551f7cc9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6628,9 +6628,9 @@ checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" [[package]] name = "indicatif" -version = "0.17.6" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b297dc40733f23a0e52728a58fa9489a5b7638a324932de16b41adc3ef80730" +checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" dependencies = [ "console", "instant", diff --git a/substrate/utils/frame/remote-externalities/Cargo.toml b/substrate/utils/frame/remote-externalities/Cargo.toml index fead2d20c70..ff0b7572d33 100644 --- a/substrate/utils/frame/remote-externalities/Cargo.toml +++ b/substrate/utils/frame/remote-externalities/Cargo.toml @@ -27,7 +27,7 @@ sp-runtime = { path = "../../../primitives/runtime" } tokio = { version = "1.22.0", features = ["macros", "rt-multi-thread"] } substrate-rpc-client = { path = "../rpc/client" } futures = "0.3" -indicatif = "0.17.3" +indicatif = "0.17.7" spinners = "4.1.0" tokio-retry = "0.3.0" -- GitLab From 7df1ae3b8111d534cce108b2b405b6a33fcdedc3 Mon Sep 17 00:00:00 2001 From: Alin Dima Date: Tue, 6 Feb 2024 10:21:28 +0200 Subject: [PATCH 014/133] prospective-parachains: respond with multiple backable candidates (#3160) Fixes https://github.com/paritytech/polkadot-sdk/issues/3129 --- .../src/fragment_tree.rs | 163 ++++- .../core/prospective-parachains/src/lib.rs | 74 +- .../core/prospective-parachains/src/tests.rs | 638 +++++++++++++++++- polkadot/node/core/provisioner/src/lib.rs | 7 +- polkadot/node/core/provisioner/src/tests.rs | 19 +- polkadot/node/subsystem-types/src/messages.rs | 8 +- .../node/backing/prospective-parachains.md | 7 +- .../src/node/utility/provisioner.md | 8 +- prdoc/pr_3160.prdoc | 12 + 9 files changed, 844 insertions(+), 92 deletions(-) create mode 100644 prdoc/pr_3160.prdoc diff --git a/polkadot/node/core/prospective-parachains/src/fragment_tree.rs b/polkadot/node/core/prospective-parachains/src/fragment_tree.rs index f7b97f07827..04ee42a9de0 100644 --- a/polkadot/node/core/prospective-parachains/src/fragment_tree.rs +++ b/polkadot/node/core/prospective-parachains/src/fragment_tree.rs @@ -756,53 +756,127 @@ impl FragmentTree { depths.iter_ones().collect() } - /// Select a candidate after the given `required_path` which passes - /// the predicate. + /// Select `count` candidates after the given `required_path` which pass + /// the predicate and have not already been backed on chain. /// - /// If there are multiple possibilities, this will select the first one. - /// - /// This returns `None` if there is no candidate meeting those criteria. + /// Does an exhaustive search into the tree starting after `required_path`. + /// If there are multiple possibilities of size `count`, this will select the first one. + /// If there is no chain of size `count` that matches the criteria, this will return the largest + /// chain it could find with the criteria. + /// If there are no candidates meeting those criteria, returns an empty `Vec`. + /// Cycles are accepted, see module docs for the `Cycles` section. /// /// The intention of the `required_path` is to allow queries on the basis of /// one or more candidates which were previously pending availability becoming /// available and opening up more room on the core. - pub(crate) fn select_child( + pub(crate) fn select_children( &self, required_path: &[CandidateHash], + count: u32, pred: impl Fn(&CandidateHash) -> bool, - ) -> Option { + ) -> Vec { let base_node = { // traverse the required path. let mut node = NodePointer::Root; for required_step in required_path { - node = self.node_candidate_child(node, &required_step)?; + if let Some(next_node) = self.node_candidate_child(node, &required_step) { + node = next_node; + } else { + return vec![] + }; } node }; - // TODO [now]: taking the first selection might introduce bias + // TODO: taking the first best selection might introduce bias // or become gameable. // // For plausibly unique parachains, this shouldn't matter much. // figure out alternative selection criteria? - match base_node { + self.select_children_inner(base_node, count, count, &pred, &mut vec![]) + } + + // Try finding a candidate chain starting from `base_node` of length `expected_count`. + // If not possible, return the longest one we could find. + // Does a depth-first search, since we're optimistic that there won't be more than one such + // chains (parachains shouldn't usually have forks). So in the usual case, this will conclude + // in `O(expected_count)`. + // Cycles are accepted, but this doesn't allow for infinite execution time, because the maximum + // depth we'll reach is `expected_count`. + // + // Worst case performance is `O(num_forks ^ expected_count)`. + // Although an exponential function, this is actually a constant that can only be altered via + // sudo/governance, because: + // 1. `num_forks` at a given level is at most `max_candidate_depth * max_validators_per_core` + // (because each validator in the assigned group can second `max_candidate_depth` + // candidates). The prospective-parachains subsystem assumes that the number of para forks is + // limited by collator-protocol and backing subsystems. In practice, this is a constant which + // can only be altered by sudo or governance. + // 2. `expected_count` is equal to the number of cores a para is scheduled on (in an elastic + // scaling scenario). For non-elastic-scaling, this is just 1. In practice, this should be a + // small number (1-3), capped by the total number of available cores (a constant alterable + // only via governance/sudo). + fn select_children_inner( + &self, + base_node: NodePointer, + expected_count: u32, + remaining_count: u32, + pred: &dyn Fn(&CandidateHash) -> bool, + accumulator: &mut Vec, + ) -> Vec { + if remaining_count == 0 { + // The best option is the chain we've accumulated so far. + return accumulator.to_vec(); + } + + let children: Vec<_> = match base_node { NodePointer::Root => self .nodes .iter() - .take_while(|n| n.parent == NodePointer::Root) - .filter(|n| self.scope.get_pending_availability(&n.candidate_hash).is_none()) - .filter(|n| pred(&n.candidate_hash)) - .map(|n| n.candidate_hash) - .next(), - NodePointer::Storage(ptr) => self.nodes[ptr] - .children - .iter() - .filter(|n| self.scope.get_pending_availability(&n.1).is_none()) - .filter(|n| pred(&n.1)) - .map(|n| n.1) - .next(), + .enumerate() + .take_while(|(_, n)| n.parent == NodePointer::Root) + .filter(|(_, n)| self.scope.get_pending_availability(&n.candidate_hash).is_none()) + .filter(|(_, n)| pred(&n.candidate_hash)) + .map(|(ptr, n)| (NodePointer::Storage(ptr), n.candidate_hash)) + .collect(), + NodePointer::Storage(base_node_ptr) => { + let base_node = &self.nodes[base_node_ptr]; + + base_node + .children + .iter() + .filter(|(_, hash)| self.scope.get_pending_availability(&hash).is_none()) + .filter(|(_, hash)| pred(&hash)) + .map(|(ptr, hash)| (*ptr, *hash)) + .collect() + }, + }; + + let mut best_result = accumulator.clone(); + for (child_ptr, child_hash) in children { + accumulator.push(child_hash); + + let result = self.select_children_inner( + child_ptr, + expected_count, + remaining_count - 1, + &pred, + accumulator, + ); + + accumulator.pop(); + + // Short-circuit the search if we've found the right length. Otherwise, we'll + // search for a max. + if result.len() == expected_count as usize { + return result + } else if best_result.len() < result.len() { + best_result = result; + } } + + best_result } fn populate_from_bases(&mut self, storage: &CandidateStorage, initial_bases: Vec) { @@ -987,6 +1061,7 @@ mod tests { use polkadot_node_subsystem_util::inclusion_emulator::InboundHrmpLimitations; use polkadot_primitives::{BlockNumber, CandidateCommitments, CandidateDescriptor, HeadData}; use polkadot_primitives_test_helpers as test_helpers; + use std::iter; fn make_constraints( min_relay_parent_number: BlockNumber, @@ -1524,6 +1599,21 @@ mod tests { assert_eq!(tree.nodes[2].candidate_hash, candidate_a_hash); assert_eq!(tree.nodes[3].candidate_hash, candidate_a_hash); assert_eq!(tree.nodes[4].candidate_hash, candidate_a_hash); + + for count in 1..10 { + assert_eq!( + tree.select_children(&[], count, |_| true), + iter::repeat(candidate_a_hash) + .take(std::cmp::min(count as usize, max_depth + 1)) + .collect::>() + ); + assert_eq!( + tree.select_children(&[candidate_a_hash], count - 1, |_| true), + iter::repeat(candidate_a_hash) + .take(std::cmp::min(count as usize - 1, max_depth)) + .collect::>() + ); + } } #[test] @@ -1591,6 +1681,35 @@ mod tests { assert_eq!(tree.nodes[2].candidate_hash, candidate_a_hash); assert_eq!(tree.nodes[3].candidate_hash, candidate_b_hash); assert_eq!(tree.nodes[4].candidate_hash, candidate_a_hash); + + assert_eq!(tree.select_children(&[], 1, |_| true), vec![candidate_a_hash],); + assert_eq!( + tree.select_children(&[], 2, |_| true), + vec![candidate_a_hash, candidate_b_hash], + ); + assert_eq!( + tree.select_children(&[], 3, |_| true), + vec![candidate_a_hash, candidate_b_hash, candidate_a_hash], + ); + assert_eq!( + tree.select_children(&[candidate_a_hash], 2, |_| true), + vec![candidate_b_hash, candidate_a_hash], + ); + + assert_eq!( + tree.select_children(&[], 6, |_| true), + vec![ + candidate_a_hash, + candidate_b_hash, + candidate_a_hash, + candidate_b_hash, + candidate_a_hash + ], + ); + assert_eq!( + tree.select_children(&[candidate_a_hash, candidate_b_hash], 6, |_| true), + vec![candidate_a_hash, candidate_b_hash, candidate_a_hash,], + ); } #[test] diff --git a/polkadot/node/core/prospective-parachains/src/lib.rs b/polkadot/node/core/prospective-parachains/src/lib.rs index aba3d092a41..6e6915b9272 100644 --- a/polkadot/node/core/prospective-parachains/src/lib.rs +++ b/polkadot/node/core/prospective-parachains/src/lib.rs @@ -146,12 +146,20 @@ async fn run_iteration( handle_candidate_seconded(view, para, candidate_hash), ProspectiveParachainsMessage::CandidateBacked(para, candidate_hash) => handle_candidate_backed(&mut *ctx, view, para, candidate_hash).await?, - ProspectiveParachainsMessage::GetBackableCandidate( + ProspectiveParachainsMessage::GetBackableCandidates( relay_parent, para, + count, required_path, tx, - ) => answer_get_backable_candidate(&view, relay_parent, para, required_path, tx), + ) => answer_get_backable_candidates( + &view, + relay_parent, + para, + count, + required_path, + tx, + ), ProspectiveParachainsMessage::GetHypotheticalFrontier(request, tx) => answer_hypothetical_frontier_request(&view, request, tx), ProspectiveParachainsMessage::GetTreeMembership(para, candidate, tx) => @@ -552,12 +560,13 @@ async fn handle_candidate_backed( Ok(()) } -fn answer_get_backable_candidate( +fn answer_get_backable_candidates( view: &View, relay_parent: Hash, para: ParaId, + count: u32, required_path: Vec, - tx: oneshot::Sender>, + tx: oneshot::Sender>, ) { let data = match view.active_leaves.get(&relay_parent) { None => { @@ -568,7 +577,7 @@ fn answer_get_backable_candidate( "Requested backable candidate for inactive relay-parent." ); - let _ = tx.send(None); + let _ = tx.send(vec![]); return }, Some(d) => d, @@ -583,7 +592,7 @@ fn answer_get_backable_candidate( "Requested backable candidate for inactive para." ); - let _ = tx.send(None); + let _ = tx.send(vec![]); return }, Some(tree) => tree, @@ -598,15 +607,32 @@ fn answer_get_backable_candidate( "No candidate storage for active para", ); - let _ = tx.send(None); + let _ = tx.send(vec![]); return }, Some(s) => s, }; - let Some(child_hash) = - tree.select_child(&required_path, |candidate| storage.is_backed(candidate)) - else { + let backable_candidates: Vec<_> = tree + .select_children(&required_path, count, |candidate| storage.is_backed(candidate)) + .into_iter() + .filter_map(|child_hash| { + storage.relay_parent_by_candidate_hash(&child_hash).map_or_else( + || { + gum::error!( + target: LOG_TARGET, + ?child_hash, + para_id = ?para, + "Candidate is present in fragment tree but not in candidate's storage!", + ); + None + }, + |parent_hash| Some((child_hash, parent_hash)), + ) + }) + .collect(); + + if backable_candidates.is_empty() { gum::trace!( target: LOG_TARGET, ?required_path, @@ -614,30 +640,16 @@ fn answer_get_backable_candidate( %relay_parent, "Could not find any backable candidate", ); - - let _ = tx.send(None); - return - }; - let Some(candidate_relay_parent) = storage.relay_parent_by_candidate_hash(&child_hash) else { - gum::error!( + } else { + gum::trace!( target: LOG_TARGET, - ?child_hash, - para_id = ?para, - "Candidate is present in fragment tree but not in candidate's storage!", + ?relay_parent, + ?backable_candidates, + "Found backable candidates", ); - let _ = tx.send(None); - return - }; - - gum::trace!( - target: LOG_TARGET, - ?relay_parent, - candidate_hash = ?child_hash, - ?candidate_relay_parent, - "Found backable candidate", - ); + } - let _ = tx.send(Some((child_hash, candidate_relay_parent))); + let _ = tx.send(backable_candidates); } fn answer_hypothetical_frontier_request( diff --git a/polkadot/node/core/prospective-parachains/src/tests.rs b/polkadot/node/core/prospective-parachains/src/tests.rs index 7e369245c0e..732736b101d 100644 --- a/polkadot/node/core/prospective-parachains/src/tests.rs +++ b/polkadot/node/core/prospective-parachains/src/tests.rs @@ -403,25 +403,28 @@ async fn get_membership( assert_eq!(resp, expected_membership_response); } -async fn get_backable_candidate( +async fn get_backable_candidates( virtual_overseer: &mut VirtualOverseer, leaf: &TestLeaf, para_id: ParaId, required_path: Vec, - expected_result: Option<(CandidateHash, Hash)>, + count: u32, + expected_result: Vec<(CandidateHash, Hash)>, ) { let (tx, rx) = oneshot::channel(); virtual_overseer .send(overseer::FromOrchestra::Communication { - msg: ProspectiveParachainsMessage::GetBackableCandidate( + msg: ProspectiveParachainsMessage::GetBackableCandidates( leaf.hash, para_id, + count, required_path, tx, ), }) .await; let resp = rx.await.unwrap(); + assert_eq!(resp.len(), expected_result.len()); assert_eq!(resp, expected_result); } @@ -849,9 +852,9 @@ fn check_candidate_on_multiple_forks() { assert_eq!(view.candidate_storage.get(&2.into()).unwrap().len(), (0, 0)); } -// Backs some candidates and tests `GetBackableCandidate`. +// Backs some candidates and tests `GetBackableCandidates` when requesting a single candidate. #[test] -fn check_backable_query() { +fn check_backable_query_single_candidate() { let test_state = TestState::default(); let view = test_harness(|mut virtual_overseer| async move { // Leaf A @@ -896,26 +899,38 @@ fn check_backable_query() { introduce_candidate(&mut virtual_overseer, candidate_b.clone(), pvd_b).await; // Should not get any backable candidates. - get_backable_candidate( + get_backable_candidates( &mut virtual_overseer, &leaf_a, 1.into(), vec![candidate_hash_a], - None, + 1, + vec![], ) .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a], + 0, + vec![], + ) + .await; + get_backable_candidates(&mut virtual_overseer, &leaf_a, 1.into(), vec![], 0, vec![]).await; // Second candidates. second_candidate(&mut virtual_overseer, candidate_a.clone()).await; second_candidate(&mut virtual_overseer, candidate_b.clone()).await; // Should not get any backable candidates. - get_backable_candidate( + get_backable_candidates( &mut virtual_overseer, &leaf_a, 1.into(), vec![candidate_hash_a], - None, + 1, + vec![], ) .await; @@ -923,31 +938,46 @@ fn check_backable_query() { back_candidate(&mut virtual_overseer, &candidate_a, candidate_hash_a).await; back_candidate(&mut virtual_overseer, &candidate_b, candidate_hash_b).await; + // Should not get any backable candidates for the other para. + get_backable_candidates(&mut virtual_overseer, &leaf_a, 2.into(), vec![], 1, vec![]).await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 2.into(), + vec![candidate_hash_a], + 1, + vec![], + ) + .await; + // Get backable candidate. - get_backable_candidate( + get_backable_candidates( &mut virtual_overseer, &leaf_a, 1.into(), vec![], - Some((candidate_hash_a, leaf_a.hash)), + 1, + vec![(candidate_hash_a, leaf_a.hash)], ) .await; - get_backable_candidate( + get_backable_candidates( &mut virtual_overseer, &leaf_a, 1.into(), vec![candidate_hash_a], - Some((candidate_hash_b, leaf_a.hash)), + 1, + vec![(candidate_hash_b, leaf_a.hash)], ) .await; // Should not get anything at the wrong path. - get_backable_candidate( + get_backable_candidates( &mut virtual_overseer, &leaf_a, 1.into(), vec![candidate_hash_b], - None, + 1, + vec![], ) .await; @@ -961,6 +991,572 @@ fn check_backable_query() { assert_eq!(view.candidate_storage.get(&2.into()).unwrap().len(), (0, 0)); } +// Backs some candidates and tests `GetBackableCandidates` when requesting a multiple candidates. +#[test] +fn check_backable_query_multiple_candidates() { + macro_rules! make_and_back_candidate { + ($test_state:ident, $virtual_overseer:ident, $leaf:ident, $parent:expr, $index:expr) => {{ + let (mut candidate, pvd) = make_candidate( + $leaf.hash, + $leaf.number, + 1.into(), + $parent.commitments.head_data.clone(), + HeadData(vec![$index]), + $test_state.validation_code_hash, + ); + // Set a field to make this candidate unique. + candidate.descriptor.para_head = Hash::from_low_u64_le($index); + let candidate_hash = candidate.hash(); + introduce_candidate(&mut $virtual_overseer, candidate.clone(), pvd).await; + second_candidate(&mut $virtual_overseer, candidate.clone()).await; + back_candidate(&mut $virtual_overseer, &candidate, candidate_hash).await; + + (candidate, candidate_hash) + }}; + } + + // Parachain 1 looks like this: + // +---A----+ + // | | + // +----B---+ C + // | | | | + // D E F H + // | | + // G I + // | + // J + { + let test_state = TestState::default(); + let view = test_harness(|mut virtual_overseer| async move { + // Leaf A + let leaf_a = TestLeaf { + number: 100, + hash: Hash::from_low_u64_be(130), + para_data: vec![ + (1.into(), PerParaData::new(97, HeadData(vec![1, 2, 3]))), + (2.into(), PerParaData::new(100, HeadData(vec![2, 3, 4]))), + ], + }; + + // Activate leaves. + activate_leaf(&mut virtual_overseer, &leaf_a, &test_state).await; + + // Candidate A + let (candidate_a, pvd_a) = make_candidate( + leaf_a.hash, + leaf_a.number, + 1.into(), + HeadData(vec![1, 2, 3]), + HeadData(vec![1]), + test_state.validation_code_hash, + ); + let candidate_hash_a = candidate_a.hash(); + introduce_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a).await; + second_candidate(&mut virtual_overseer, candidate_a.clone()).await; + back_candidate(&mut virtual_overseer, &candidate_a, candidate_hash_a).await; + + let (candidate_b, candidate_hash_b) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_a, 2); + let (candidate_c, candidate_hash_c) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_a, 3); + let (_candidate_d, candidate_hash_d) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_b, 4); + let (_candidate_e, candidate_hash_e) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_b, 5); + let (candidate_f, candidate_hash_f) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_b, 6); + let (_candidate_g, candidate_hash_g) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_f, 7); + let (candidate_h, candidate_hash_h) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_c, 8); + let (candidate_i, candidate_hash_i) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_h, 9); + let (_candidate_j, candidate_hash_j) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_i, 10); + + // Should not get any backable candidates for the other para. + get_backable_candidates(&mut virtual_overseer, &leaf_a, 2.into(), vec![], 1, vec![]) + .await; + get_backable_candidates(&mut virtual_overseer, &leaf_a, 2.into(), vec![], 5, vec![]) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 2.into(), + vec![candidate_hash_a], + 1, + vec![], + ) + .await; + + // Test various scenarios with various counts. + + // empty required_path + { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![], + 1, + vec![(candidate_hash_a, leaf_a.hash)], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![], + 4, + vec![ + (candidate_hash_a, leaf_a.hash), + (candidate_hash_b, leaf_a.hash), + (candidate_hash_f, leaf_a.hash), + (candidate_hash_g, leaf_a.hash), + ], + ) + .await; + } + + // required path of 1 + { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a], + 1, + vec![(candidate_hash_b, leaf_a.hash)], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a], + 2, + vec![(candidate_hash_b, leaf_a.hash), (candidate_hash_d, leaf_a.hash)], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a], + 3, + vec![ + (candidate_hash_b, leaf_a.hash), + (candidate_hash_f, leaf_a.hash), + (candidate_hash_g, leaf_a.hash), + ], + ) + .await; + + // If the requested count exceeds the largest chain, return the longest + // chain we can get. + for count in 5..10 { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a], + count, + vec![ + (candidate_hash_c, leaf_a.hash), + (candidate_hash_h, leaf_a.hash), + (candidate_hash_i, leaf_a.hash), + (candidate_hash_j, leaf_a.hash), + ], + ) + .await; + } + } + + // required path of 2 + { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a, candidate_hash_b], + 1, + vec![(candidate_hash_d, leaf_a.hash)], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a, candidate_hash_c], + 1, + vec![(candidate_hash_h, leaf_a.hash)], + ) + .await; + // If the requested count exceeds the largest chain, return the longest + // chain we can get. + for count in 4..10 { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a, candidate_hash_c], + count, + vec![ + (candidate_hash_h, leaf_a.hash), + (candidate_hash_i, leaf_a.hash), + (candidate_hash_j, leaf_a.hash), + ], + ) + .await; + } + } + + // No more candidates in any chain. + { + let required_paths = vec![ + vec![candidate_hash_a, candidate_hash_b, candidate_hash_e], + vec![ + candidate_hash_a, + candidate_hash_c, + candidate_hash_h, + candidate_hash_i, + candidate_hash_j, + ], + ]; + for path in required_paths { + for count in 1..4 { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + path.clone(), + count, + vec![], + ) + .await; + } + } + } + + // Should not get anything at the wrong path. + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_b], + 1, + vec![], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_b, candidate_hash_a], + 3, + vec![], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a, candidate_hash_b, candidate_hash_c], + 3, + vec![], + ) + .await; + + virtual_overseer + }); + + assert_eq!(view.active_leaves.len(), 1); + assert_eq!(view.candidate_storage.len(), 2); + // 10 candidates and 7 parents on para 1. + assert_eq!(view.candidate_storage.get(&1.into()).unwrap().len(), (7, 10)); + assert_eq!(view.candidate_storage.get(&2.into()).unwrap().len(), (0, 0)); + } + + // A tree with multiple roots. + // Parachain 1 looks like this: + // (imaginary root) + // | | + // +----B---+ A + // | | | | + // | | | C + // D E F | + // | H + // G | + // I + // | + // J + { + let test_state = TestState::default(); + let view = test_harness(|mut virtual_overseer| async move { + // Leaf A + let leaf_a = TestLeaf { + number: 100, + hash: Hash::from_low_u64_be(130), + para_data: vec![ + (1.into(), PerParaData::new(97, HeadData(vec![1, 2, 3]))), + (2.into(), PerParaData::new(100, HeadData(vec![2, 3, 4]))), + ], + }; + + // Activate leaves. + activate_leaf(&mut virtual_overseer, &leaf_a, &test_state).await; + + // Candidate B + let (candidate_b, pvd_b) = make_candidate( + leaf_a.hash, + leaf_a.number, + 1.into(), + HeadData(vec![1, 2, 3]), + HeadData(vec![2]), + test_state.validation_code_hash, + ); + let candidate_hash_b = candidate_b.hash(); + introduce_candidate(&mut virtual_overseer, candidate_b.clone(), pvd_b).await; + second_candidate(&mut virtual_overseer, candidate_b.clone()).await; + back_candidate(&mut virtual_overseer, &candidate_b, candidate_hash_b).await; + + // Candidate A + let (candidate_a, pvd_a) = make_candidate( + leaf_a.hash, + leaf_a.number, + 1.into(), + HeadData(vec![1, 2, 3]), + HeadData(vec![1]), + test_state.validation_code_hash, + ); + let candidate_hash_a = candidate_a.hash(); + introduce_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a).await; + second_candidate(&mut virtual_overseer, candidate_a.clone()).await; + back_candidate(&mut virtual_overseer, &candidate_a, candidate_hash_a).await; + + let (candidate_c, candidate_hash_c) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_a, 3); + let (_candidate_d, candidate_hash_d) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_b, 4); + let (_candidate_e, candidate_hash_e) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_b, 5); + let (candidate_f, candidate_hash_f) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_b, 6); + let (_candidate_g, candidate_hash_g) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_f, 7); + let (candidate_h, candidate_hash_h) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_c, 8); + let (candidate_i, candidate_hash_i) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_h, 9); + let (_candidate_j, candidate_hash_j) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_i, 10); + + // Should not get any backable candidates for the other para. + get_backable_candidates(&mut virtual_overseer, &leaf_a, 2.into(), vec![], 1, vec![]) + .await; + get_backable_candidates(&mut virtual_overseer, &leaf_a, 2.into(), vec![], 5, vec![]) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 2.into(), + vec![candidate_hash_a], + 1, + vec![], + ) + .await; + + // Test various scenarios with various counts. + + // empty required_path + { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![], + 1, + vec![(candidate_hash_b, leaf_a.hash)], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![], + 2, + vec![(candidate_hash_b, leaf_a.hash), (candidate_hash_d, leaf_a.hash)], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![], + 4, + vec![ + (candidate_hash_a, leaf_a.hash), + (candidate_hash_c, leaf_a.hash), + (candidate_hash_h, leaf_a.hash), + (candidate_hash_i, leaf_a.hash), + ], + ) + .await; + } + + // required path of 1 + { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a], + 1, + vec![(candidate_hash_c, leaf_a.hash)], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_b], + 1, + vec![(candidate_hash_d, leaf_a.hash)], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a], + 2, + vec![(candidate_hash_c, leaf_a.hash), (candidate_hash_h, leaf_a.hash)], + ) + .await; + + // If the requested count exceeds the largest chain, return the longest + // chain we can get. + for count in 2..10 { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_b], + count, + vec![(candidate_hash_f, leaf_a.hash), (candidate_hash_g, leaf_a.hash)], + ) + .await; + } + } + + // required path of 2 + { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_b, candidate_hash_f], + 1, + vec![(candidate_hash_g, leaf_a.hash)], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a, candidate_hash_c], + 1, + vec![(candidate_hash_h, leaf_a.hash)], + ) + .await; + // If the requested count exceeds the largest chain, return the longest + // chain we can get. + for count in 4..10 { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a, candidate_hash_c], + count, + vec![ + (candidate_hash_h, leaf_a.hash), + (candidate_hash_i, leaf_a.hash), + (candidate_hash_j, leaf_a.hash), + ], + ) + .await; + } + } + + // No more candidates in any chain. + { + let required_paths = vec![ + vec![candidate_hash_b, candidate_hash_f, candidate_hash_g], + vec![candidate_hash_b, candidate_hash_e], + vec![candidate_hash_b, candidate_hash_d], + vec![ + candidate_hash_a, + candidate_hash_c, + candidate_hash_h, + candidate_hash_i, + candidate_hash_j, + ], + ]; + for path in required_paths { + for count in 1..4 { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + path.clone(), + count, + vec![], + ) + .await; + } + } + } + + // Should not get anything at the wrong path. + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_d], + 1, + vec![], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_b, candidate_hash_a], + 3, + vec![], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a, candidate_hash_c, candidate_hash_d], + 3, + vec![], + ) + .await; + + virtual_overseer + }); + + assert_eq!(view.active_leaves.len(), 1); + assert_eq!(view.candidate_storage.len(), 2); + // 10 candidates and 7 parents on para 1. + assert_eq!(view.candidate_storage.get(&1.into()).unwrap().len(), (7, 10)); + assert_eq!(view.candidate_storage.get(&2.into()).unwrap().len(), (0, 0)); + } +} + // Test depth query. #[test] fn check_hypothetical_frontier_query() { @@ -1448,12 +2044,13 @@ fn persists_pending_availability_candidate() { second_candidate(&mut virtual_overseer, candidate_b.clone()).await; back_candidate(&mut virtual_overseer, &candidate_b, candidate_hash_b).await; - get_backable_candidate( + get_backable_candidates( &mut virtual_overseer, &leaf_b, para_id, vec![candidate_hash_a], - Some((candidate_hash_b, leaf_b_hash)), + 1, + vec![(candidate_hash_b, leaf_b_hash)], ) .await; @@ -1512,12 +2109,13 @@ fn backwards_compatible() { second_candidate(&mut virtual_overseer, candidate_a.clone()).await; back_candidate(&mut virtual_overseer, &candidate_a, candidate_hash_a).await; - get_backable_candidate( + get_backable_candidates( &mut virtual_overseer, &leaf_a, para_id, vec![], - Some((candidate_hash_a, candidate_relay_parent)), + 1, + vec![(candidate_hash_a, candidate_relay_parent)], ) .await; @@ -1537,7 +2135,7 @@ fn backwards_compatible() { ) .await; - get_backable_candidate(&mut virtual_overseer, &leaf_b, para_id, vec![], None).await; + get_backable_candidates(&mut virtual_overseer, &leaf_b, para_id, vec![], 1, vec![]).await; virtual_overseer }); diff --git a/polkadot/node/core/provisioner/src/lib.rs b/polkadot/node/core/provisioner/src/lib.rs index 3970b857261..51f768d782e 100644 --- a/polkadot/node/core/provisioner/src/lib.rs +++ b/polkadot/node/core/provisioner/src/lib.rs @@ -806,15 +806,18 @@ async fn get_backable_candidate( ) -> Result, Error> { let (tx, rx) = oneshot::channel(); sender - .send_message(ProspectiveParachainsMessage::GetBackableCandidate( + .send_message(ProspectiveParachainsMessage::GetBackableCandidates( relay_parent, para_id, + 1, // core count hardcoded to 1, until elastic scaling is implemented and enabled. required_path, tx, )) .await; - rx.await.map_err(Error::CanceledBackableCandidate) + rx.await + .map_err(Error::CanceledBackableCandidate) + .map(|res| res.get(0).copied()) } /// The availability bitfield for a given core is the transpose diff --git a/polkadot/node/core/provisioner/src/tests.rs b/polkadot/node/core/provisioner/src/tests.rs index 1d7bdfcfcb8..b26df8ddb91 100644 --- a/polkadot/node/core/provisioner/src/tests.rs +++ b/polkadot/node/core/provisioner/src/tests.rs @@ -373,13 +373,18 @@ mod select_candidates { let _ = sender.send(response); }, AllMessages::ProspectiveParachains( - ProspectiveParachainsMessage::GetBackableCandidate(.., tx), - ) => match prospective_parachains_mode { - ProspectiveParachainsMode::Enabled { .. } => { - let _ = tx.send(candidates_iter.next()); - }, - ProspectiveParachainsMode::Disabled => - panic!("unexpected prospective parachains request"), + ProspectiveParachainsMessage::GetBackableCandidates(_, _, count, _, tx), + ) => { + assert_eq!(count, 1); + + match prospective_parachains_mode { + ProspectiveParachainsMode::Enabled { .. } => { + let _ = + tx.send(candidates_iter.next().map_or_else(Vec::new, |c| vec![c])); + }, + ProspectiveParachainsMode::Disabled => + panic!("unexpected prospective parachains request"), + } }, _ => panic!("Unexpected message: {:?}", from_job), } diff --git a/polkadot/node/subsystem-types/src/messages.rs b/polkadot/node/subsystem-types/src/messages.rs index 1d5d82b57fd..549e43a671d 100644 --- a/polkadot/node/subsystem-types/src/messages.rs +++ b/polkadot/node/subsystem-types/src/messages.rs @@ -1128,14 +1128,16 @@ pub enum ProspectiveParachainsMessage { /// has been backed. This requires that the candidate was successfully introduced in /// the past. CandidateBacked(ParaId, CandidateHash), - /// Get a backable candidate hash along with its relay parent for the given parachain, + /// Get N backable candidate hashes along with their relay parents for the given parachain, /// under the given relay-parent hash, which is a descendant of the given candidate hashes. + /// N should represent the number of scheduled cores of this ParaId. /// Returns `None` on the channel if no such candidate exists. - GetBackableCandidate( + GetBackableCandidates( Hash, ParaId, + u32, Vec, - oneshot::Sender>, + oneshot::Sender>, ), /// Get the hypothetical frontier membership of candidates with the given properties /// under the specified active leaves' fragment trees. diff --git a/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md b/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md index 286aeddb986..8f00ff08494 100644 --- a/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md +++ b/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md @@ -92,9 +92,10 @@ prospective validation data. This is unlikely to change. been backed. - Sent by the Backing Subsystem after it successfully imports a statement giving a candidate the necessary quorum of backing votes. -- `ProspectiveParachainsMessage::GetBackableCandidate` - - Get a backable candidate hash along with its relay parent for a given parachain, - under a given relay-parent (leaf) hash, which is a descendant of given candidate hashes. +- `ProspectiveParachainsMessage::GetBackableCandidates` + - Get the requested number of backable candidate hashes along with their relay parent for a given + parachain,under a given relay-parent (leaf) hash, which are descendants of given candidate + hashes. - Sent by the Provisioner when requesting backable candidates, when selecting candidates for a given relay-parent. - `ProspectiveParachainsMessage::GetHypotheticalFrontier` diff --git a/polkadot/roadmap/implementers-guide/src/node/utility/provisioner.md b/polkadot/roadmap/implementers-guide/src/node/utility/provisioner.md index 0b4fe6a4587..b017259da8c 100644 --- a/polkadot/roadmap/implementers-guide/src/node/utility/provisioner.md +++ b/polkadot/roadmap/implementers-guide/src/node/utility/provisioner.md @@ -187,16 +187,16 @@ this process is a vector of `CandidateHash`s, sorted in order of their core inde #### Required Path -Required path is a parameter for `ProspectiveParachainsMessage::GetBackableCandidate`, which the provisioner sends in +Required path is a parameter for `ProspectiveParachainsMessage::GetBackableCandidates`, which the provisioner sends in candidate selection. -An empty required path indicates that the requested candidate should be a direct child of the most recently included +An empty required path indicates that the requested candidate chain should start with the most recently included parablock for the given `para_id` as of the given relay parent. In contrast, a required path with one or more entries prompts [prospective parachains](../backing/prospective-parachains.md) to step forward through its fragment tree for the given `para_id` and -relay parent until the desired parablock is reached. We then select a direct child of that parablock to pass to the -provisioner. +relay parent until the desired parablock is reached. We then select the chain starting with the direct child of that +parablock to pass to the provisioner. The parablocks making up a required path do not need to have been previously seen as included in relay chain blocks. Thus the ability to provision backable candidates based on a required path effectively decouples backing from inclusion. diff --git a/prdoc/pr_3160.prdoc b/prdoc/pr_3160.prdoc new file mode 100644 index 00000000000..22305b6635a --- /dev/null +++ b/prdoc/pr_3160.prdoc @@ -0,0 +1,12 @@ +title: "prospective-parachains: allow requesting a chain of backable candidates" + +topic: Node + +doc: + - audience: Node Dev + description: | + Enable requesting a chain of multiple backable candidates. Will be used by the provisioner + to build paras inherent data for elastic scaling. + +crates: + - name: polkadot-node-core-prospective-parachains -- GitLab From c552fb5495d28ef29a2da62e54cf7e0dedf776d0 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 6 Feb 2024 13:45:40 +0100 Subject: [PATCH 015/133] Ranked collective `Add`+`Remove` origins (#3212) Superseeds https://github.com/paritytech/polkadot-sdk/pull/1245 This PR is a migration of the https://github.com/paritytech/substrate/pull/14577. The PR added associated types (`AddOrigin` & `RemoveOrigin`) to `Config`. It allows you to decouple types and areas of responsibility, since at the moment the same types are responsible for adding and promoting(removing and demoting). This will improve the flexibility of the pallet configuration. ``` /// The origin required to add a member. type AddOrigin: EnsureOrigin; /// The origin required to remove a member. The success value indicates the /// maximum rank *from which* the removal may be. type RemoveOrigin: EnsureOrigin; ``` To achieve the backward compatibility, the users of the pallet can use the old type via the new morph: ``` type AddOrigin = MapSuccess; type RemoveOrigin = Self::DemoteOrigin; ``` --------- Signed-off-by: Oliver Tale-Yazdi Co-authored-by: PraetorP Co-authored-by: Pavel Orlov <45266194+PraetorP@users.noreply.github.com> --- .../collectives-westend/src/ambassador/mod.rs | 4 ++- .../collectives-westend/src/fellowship/mod.rs | 10 +++++-- .../rococo/src/governance/fellowship.rs | 12 ++++++++- prdoc/pr_3212.prdoc | 9 +++++++ substrate/bin/node/runtime/src/lib.rs | 2 ++ .../core-fellowship/src/tests/integration.rs | 4 ++- .../ranked-collective/src/benchmarking.rs | 26 +++++------------- substrate/frame/ranked-collective/src/lib.rs | 27 ++++++++++++------- .../frame/ranked-collective/src/tests.rs | 7 ++++- .../frame/salary/src/tests/integration.rs | 4 ++- substrate/primitives/runtime/src/traits.rs | 3 +++ 11 files changed, 72 insertions(+), 36 deletions(-) create mode 100644 prdoc/pr_3212.prdoc diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/ambassador/mod.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/ambassador/mod.rs index d9042f2a556..0c9f428c139 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/ambassador/mod.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/ambassador/mod.rs @@ -40,7 +40,7 @@ use origins::pallet_origins::{ EnsureAmbassadorsVoice, EnsureAmbassadorsVoiceFrom, EnsureHeadAmbassadorsVoice, Origin, }; use sp_core::ConstU128; -use sp_runtime::traits::{CheckedReduceBy, ConstU16, ConvertToValue, Replace}; +use sp_runtime::traits::{CheckedReduceBy, ConstU16, ConvertToValue, Replace, ReplaceWithDefault}; use xcm::prelude::*; use xcm_builder::{AliasesIntoAccountId32, PayOverXcm}; @@ -108,8 +108,10 @@ pub type ExchangeOrigin = EitherOf for Runtime { type WeightInfo = weights::pallet_ranked_collective_ambassador_collective::WeightInfo; type RuntimeEvent = RuntimeEvent; + type AddOrigin = MapSuccess>; type PromoteOrigin = PromoteOrigin; type DemoteOrigin = DemoteOrigin; + type RemoveOrigin = Self::DemoteOrigin; type ExchangeOrigin = ExchangeOrigin; type Polls = AmbassadorReferenda; type MinRankOfClass = sp_runtime::traits::Identity; diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/mod.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/mod.rs index 6632d511f7c..3816d2ed848 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/mod.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/mod.rs @@ -113,12 +113,17 @@ impl pallet_ranked_collective::Config for Runtime type WeightInfo = weights::pallet_ranked_collective_fellowship_collective::WeightInfo; type RuntimeEvent = RuntimeEvent; - #[cfg(not(feature = "runtime-benchmarks"))] // Promotions and the induction of new members are serviced by `FellowshipCore` pallet instance. - type PromoteOrigin = frame_system::EnsureNever; + #[cfg(not(feature = "runtime-benchmarks"))] + type AddOrigin = frame_system::EnsureNever<()>; #[cfg(feature = "runtime-benchmarks")] + type AddOrigin = frame_system::EnsureRoot; + // The maximum value of `u16` set as a success value for the root to ensure the benchmarks will // pass. + #[cfg(not(feature = "runtime-benchmarks"))] + type PromoteOrigin = frame_system::EnsureNever; + #[cfg(feature = "runtime-benchmarks")] type PromoteOrigin = EnsureRootWithSuccess>; // Demotion is by any of: @@ -127,6 +132,7 @@ impl pallet_ranked_collective::Config for Runtime // // The maximum value of `u16` set as a success value for the root to ensure the benchmarks will // pass. + type RemoveOrigin = Self::DemoteOrigin; type DemoteOrigin = EitherOf< EnsureRootWithSuccess>, MapSuccess< diff --git a/polkadot/runtime/rococo/src/governance/fellowship.rs b/polkadot/runtime/rococo/src/governance/fellowship.rs index 9ac47e008a2..a589b768afd 100644 --- a/polkadot/runtime/rococo/src/governance/fellowship.rs +++ b/polkadot/runtime/rococo/src/governance/fellowship.rs @@ -17,7 +17,7 @@ //! Elements of governance concerning the Rococo Fellowship. use frame_support::traits::{MapSuccess, TryMapSuccess}; -use sp_runtime::traits::{CheckedReduceBy, ConstU16, Replace}; +use sp_runtime::traits::{CheckedReduceBy, ConstU16, Replace, ReplaceWithDefault}; use super::*; use crate::{CENTS, DAYS}; @@ -315,6 +315,11 @@ pub type FellowshipCollectiveInstance = pallet_ranked_collective::Instance1; impl pallet_ranked_collective::Config for Runtime { type WeightInfo = weights::pallet_ranked_collective::WeightInfo; type RuntimeEvent = RuntimeEvent; + // Adding is by any of: + // - Root. + // - the FellowshipAdmin origin. + // - a Fellowship origin. + type AddOrigin = MapSuccess>; // Promotion is by any of: // - Root can demote arbitrarily. // - the FellowshipAdmin origin (i.e. token holder referendum); @@ -326,6 +331,11 @@ impl pallet_ranked_collective::Config for Runtime TryMapSuccess>>, >, >; + // Removing is by any of: + // - Root can remove arbitrarily. + // - the FellowshipAdmin origin (i.e. token holder referendum); + // - a vote by the rank two above the current rank. + type RemoveOrigin = Self::DemoteOrigin; // Demotion is by any of: // - Root can demote arbitrarily. // - the FellowshipAdmin origin (i.e. token holder referendum); diff --git a/prdoc/pr_3212.prdoc b/prdoc/pr_3212.prdoc new file mode 100644 index 00000000000..a9de1e28cdf --- /dev/null +++ b/prdoc/pr_3212.prdoc @@ -0,0 +1,9 @@ +title: "Ranked collective introduce `Add` and `Remove` origins" + +doc: + - audience: Runtime Dev + description: | + Add two new origins to the ranked-collective pallet. One to add new members and one to remove members, named `AddOrigin` and `RemoveOrigin` respectively. + +crates: + - name: pallet-ranked-collective diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 02d084c6c62..515272cd99a 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1013,6 +1013,8 @@ impl pallet_referenda::Config for Runtime { impl pallet_ranked_collective::Config for Runtime { type WeightInfo = pallet_ranked_collective::weights::SubstrateWeight; type RuntimeEvent = RuntimeEvent; + type AddOrigin = EnsureRoot; + type RemoveOrigin = Self::DemoteOrigin; type PromoteOrigin = EnsureRootWithSuccess>; type DemoteOrigin = EnsureRootWithSuccess>; type ExchangeOrigin = EnsureRootWithSuccess>; diff --git a/substrate/frame/core-fellowship/src/tests/integration.rs b/substrate/frame/core-fellowship/src/tests/integration.rs index 57f9cad3dcb..6f177ba66db 100644 --- a/substrate/frame/core-fellowship/src/tests/integration.rs +++ b/substrate/frame/core-fellowship/src/tests/integration.rs @@ -27,7 +27,7 @@ use frame_system::EnsureSignedBy; use pallet_ranked_collective::{EnsureRanked, Geometric, Rank, TallyOf, Votes}; use sp_core::Get; use sp_runtime::{ - traits::{Convert, ReduceBy, TryMorphInto}, + traits::{Convert, ReduceBy, ReplaceWithDefault, TryMorphInto}, BuildStorage, DispatchError, }; type Class = Rank; @@ -137,12 +137,14 @@ impl pallet_ranked_collective::Config for Test { // Members can promote up to the rank of 2 below them. MapSuccess, ReduceBy>>, >; + type AddOrigin = MapSuccess>; type DemoteOrigin = EitherOf< // Root can demote arbitrarily. frame_system::EnsureRootWithSuccess>, // Members can demote up to the rank of 3 below them. MapSuccess, ReduceBy>>, >; + type RemoveOrigin = Self::DemoteOrigin; type ExchangeOrigin = EitherOf< // Root can exchange arbitrarily. frame_system::EnsureRootWithSuccess>, diff --git a/substrate/frame/ranked-collective/src/benchmarking.rs b/substrate/frame/ranked-collective/src/benchmarking.rs index 332d8292e53..f093fd93e6e 100644 --- a/substrate/frame/ranked-collective/src/benchmarking.rs +++ b/substrate/frame/ranked-collective/src/benchmarking.rs @@ -41,8 +41,8 @@ fn make_member, I: 'static>(rank: Rank) -> T::AccountId { let who = account::("member", MemberCount::::get(0), SEED); let who_lookup = T::Lookup::unlookup(who.clone()); assert_ok!(Pallet::::add_member( - T::PromoteOrigin::try_successful_origin() - .expect("PromoteOrigin has no successful origin required for the benchmark"), + T::AddOrigin::try_successful_origin() + .expect("AddOrigin has no successful origin required for the benchmark"), who_lookup.clone(), )); for _ in 0..rank { @@ -60,7 +60,7 @@ benchmarks_instance_pallet! { let who = account::("member", 0, SEED); let who_lookup = T::Lookup::unlookup(who.clone()); let origin = - T::PromoteOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + T::AddOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let call = Call::::add_member { who: who_lookup }; }: { call.dispatch_bypass_filter(origin)? } verify { @@ -77,7 +77,7 @@ benchmarks_instance_pallet! { let last = make_member::(rank); let last_index = (0..=rank).map(|r| IdToIndex::::get(r, &last).unwrap()).collect::>(); let origin = - T::DemoteOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + T::RemoveOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let call = Call::::remove_member { who: who_lookup, min_rank: rank }; }: { call.dispatch_bypass_filter(origin)? } verify { @@ -125,23 +125,11 @@ benchmarks_instance_pallet! { } vote { - let caller: T::AccountId = whitelisted_caller(); - let caller_lookup = T::Lookup::unlookup(caller.clone()); - assert_ok!(Pallet::::add_member( - T::PromoteOrigin::try_successful_origin() - .expect("PromoteOrigin has no successful origin required for the benchmark"), - caller_lookup.clone(), - )); - // Create a poll let class = T::Polls::classes().into_iter().next().unwrap(); let rank = T::MinRankOfClass::convert(class.clone()); - for _ in 0..rank { - assert_ok!(Pallet::::promote_member( - T::PromoteOrigin::try_successful_origin() - .expect("PromoteOrigin has no successful origin required for the benchmark"), - caller_lookup.clone(), - )); - } + + let caller = make_member::(rank); + let caller_lookup = T::Lookup::unlookup(caller.clone()); let poll = T::Polls::create_ongoing(class).expect("Must always be able to create a poll for rank 0"); diff --git a/substrate/frame/ranked-collective/src/lib.rs b/substrate/frame/ranked-collective/src/lib.rs index d0303fee0b3..65ea886acc4 100644 --- a/substrate/frame/ranked-collective/src/lib.rs +++ b/substrate/frame/ranked-collective/src/lib.rs @@ -393,12 +393,20 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; - /// The origin required to add or promote a mmember. The success value indicates the + /// The origin required to add a member. + type AddOrigin: EnsureOrigin; + + /// The origin required to remove a member. + /// + /// The success value indicates the maximum rank *from which* the removal may be. + type RemoveOrigin: EnsureOrigin; + + /// The origin required to promote a member. The success value indicates the /// maximum rank *to which* the promotion may be. type PromoteOrigin: EnsureOrigin; - /// The origin required to demote or remove a member. The success value indicates the - /// maximum rank *from which* the demotion/removal may be. + /// The origin required to demote a member. The success value indicates the + /// maximum rank *from which* the demotion may be. type DemoteOrigin: EnsureOrigin; /// The origin that can swap the account of a member. @@ -510,22 +518,21 @@ pub mod pallet { impl, I: 'static> Pallet { /// Introduce a new member. /// - /// - `origin`: Must be the `AdminOrigin`. + /// - `origin`: Must be the `AddOrigin`. /// - `who`: Account of non-member which will become a member. - /// - `rank`: The rank to give the new member. /// /// Weight: `O(1)` #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::add_member())] pub fn add_member(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { - let _ = T::PromoteOrigin::ensure_origin(origin)?; + T::AddOrigin::ensure_origin(origin)?; let who = T::Lookup::lookup(who)?; Self::do_add_member(who, true) } /// Increment the rank of an existing member by one. /// - /// - `origin`: Must be the `AdminOrigin`. + /// - `origin`: Must be the `PromoteOrigin`. /// - `who`: Account of existing member. /// /// Weight: `O(1)` @@ -540,7 +547,7 @@ pub mod pallet { /// Decrement the rank of an existing member by one. If the member is already at rank zero, /// then they are removed entirely. /// - /// - `origin`: Must be the `AdminOrigin`. + /// - `origin`: Must be the `DemoteOrigin`. /// - `who`: Account of existing member of rank greater than zero. /// /// Weight: `O(1)`, less if the member's index is highest in its rank. @@ -554,7 +561,7 @@ pub mod pallet { /// Remove the member entirely. /// - /// - `origin`: Must be the `AdminOrigin`. + /// - `origin`: Must be the `RemoveOrigin`. /// - `who`: Account of existing member of rank greater than zero. /// - `min_rank`: The rank of the member or greater. /// @@ -566,7 +573,7 @@ pub mod pallet { who: AccountIdLookupOf, min_rank: Rank, ) -> DispatchResultWithPostInfo { - let max_rank = T::DemoteOrigin::ensure_origin(origin)?; + let max_rank = T::RemoveOrigin::ensure_origin(origin)?; let who = T::Lookup::lookup(who)?; let MemberRecord { rank, .. } = Self::ensure_member(&who)?; ensure!(min_rank >= rank, Error::::InvalidWitness); diff --git a/substrate/frame/ranked-collective/src/tests.rs b/substrate/frame/ranked-collective/src/tests.rs index 58e21ded358..c5fccd3f772 100644 --- a/substrate/frame/ranked-collective/src/tests.rs +++ b/substrate/frame/ranked-collective/src/tests.rs @@ -26,7 +26,10 @@ use frame_support::{ traits::{ConstU16, EitherOf, MapSuccess, Polling}, }; use sp_core::Get; -use sp_runtime::{traits::ReduceBy, BuildStorage}; +use sp_runtime::{ + traits::{ReduceBy, ReplaceWithDefault}, + BuildStorage, +}; use super::*; use crate as pallet_ranked_collective; @@ -152,6 +155,8 @@ parameter_types! { impl Config for Test { type WeightInfo = (); type RuntimeEvent = RuntimeEvent; + type AddOrigin = MapSuccess>; + type RemoveOrigin = Self::DemoteOrigin; type PromoteOrigin = EitherOf< // Root can promote arbitrarily. frame_system::EnsureRootWithSuccess>, diff --git a/substrate/frame/salary/src/tests/integration.rs b/substrate/frame/salary/src/tests/integration.rs index bf1e82b028c..a49b5637b8a 100644 --- a/substrate/frame/salary/src/tests/integration.rs +++ b/substrate/frame/salary/src/tests/integration.rs @@ -26,7 +26,7 @@ use frame_support::{ use pallet_ranked_collective::{EnsureRanked, Geometric, TallyOf, Votes}; use sp_core::{ConstU16, Get}; use sp_runtime::{ - traits::{Convert, ReduceBy}, + traits::{Convert, ReduceBy, ReplaceWithDefault}, BuildStorage, DispatchError, }; @@ -162,12 +162,14 @@ impl pallet_ranked_collective::Config for Test { // Members can promote up to the rank of 2 below them. MapSuccess, ReduceBy>>, >; + type AddOrigin = MapSuccess>; type DemoteOrigin = EitherOf< // Root can demote arbitrarily. frame_system::EnsureRootWithSuccess>, // Members can demote up to the rank of 3 below them. MapSuccess, ReduceBy>>, >; + type RemoveOrigin = Self::DemoteOrigin; type ExchangeOrigin = EitherOf< // Root can exchange arbitrarily. frame_system::EnsureRootWithSuccess>, diff --git a/substrate/primitives/runtime/src/traits.rs b/substrate/primitives/runtime/src/traits.rs index 4213117334e..caede5e2b59 100644 --- a/substrate/primitives/runtime/src/traits.rs +++ b/substrate/primitives/runtime/src/traits.rs @@ -540,6 +540,9 @@ morph_types! { /// Morpher to disregard the source value and replace with another. pub type Replace = |_| -> V::Type { V::get() }; + /// Morpher to disregard the source value and replace with the default of `V`. + pub type ReplaceWithDefault = |_| -> V { Default::default() }; + /// Mutator which reduces a scalar by a particular amount. pub type ReduceBy = |r: N::Type| -> N::Type { r.checked_sub(&N::get()).unwrap_or(Zero::zero()) -- GitLab From bc2e5e1fe26e2c2c8ee766ff9fe7be7e212a0c62 Mon Sep 17 00:00:00 2001 From: Squirrel Date: Tue, 6 Feb 2024 13:01:29 +0000 Subject: [PATCH 016/133] sp-std -> core (#3199) First in a series of PRs that reduces our use of sp-std with a view to deprecating it. This is just looking at /substrate and moving some of the references from `sp-std` to `core`. These particular changes should be uncontroversial. Where macros are used `::core` should be used to remove any ambiguity. part of https://github.com/paritytech/polkadot-sdk/issues/2101 --- substrate/frame/alliance/src/benchmarking.rs | 5 ++--- substrate/frame/alliance/src/mock.rs | 2 +- substrate/frame/asset-conversion/src/mock.rs | 2 +- substrate/frame/asset-conversion/src/types.rs | 2 +- substrate/frame/assets/src/extra_mutator.rs | 6 +++--- substrate/frame/assets/src/migration.rs | 2 +- substrate/frame/assets/src/types.rs | 4 ++-- substrate/frame/atomic-swap/src/lib.rs | 9 ++++----- substrate/frame/aura/src/migrations.rs | 2 +- substrate/frame/babe/src/randomness.rs | 8 ++++---- substrate/frame/bounties/src/migrations/v4.rs | 2 +- substrate/frame/collective/src/benchmarking.rs | 2 +- substrate/frame/contracts/src/gas.rs | 2 +- substrate/frame/contracts/src/migration/v10.rs | 7 +++++-- substrate/frame/contracts/src/schedule.rs | 2 +- .../frame/core-fellowship/src/tests/unit.rs | 2 +- substrate/frame/democracy/src/conviction.rs | 2 +- substrate/frame/democracy/src/migrations/v1.rs | 2 +- substrate/frame/democracy/src/vote_threshold.rs | 2 +- .../src/benchmarking.rs | 2 +- .../frame/election-provider-support/src/tests.rs | 2 +- .../election-provider-support/src/weights.rs | 2 +- substrate/frame/examples/basic/src/lib.rs | 9 +++++---- substrate/frame/examples/basic/src/weights.rs | 2 +- substrate/frame/grandpa/src/migrations.rs | 2 +- substrate/frame/im-online/src/migration.rs | 2 +- .../frame/merkle-mountain-range/src/mmr/mod.rs | 2 +- substrate/frame/multisig/src/migrations.rs | 2 +- substrate/frame/nfts/src/macros.rs | 2 +- substrate/frame/nfts/src/migration.rs | 2 +- .../frame/node-authorization/src/weights.rs | 2 +- substrate/frame/nomination-pools/src/tests.rs | 2 +- substrate/frame/referenda/src/types.rs | 2 +- substrate/frame/safe-mode/src/lib.rs | 2 +- substrate/frame/salary/src/lib.rs | 2 +- substrate/frame/salary/src/tests/unit.rs | 2 +- substrate/frame/sassafras/src/mock.rs | 2 +- substrate/frame/scheduler/src/migration.rs | 4 ++-- substrate/frame/session/src/migrations/v1.rs | 2 +- substrate/frame/society/src/migrations.rs | 2 +- substrate/frame/society/src/weights.rs | 2 +- .../frame/staking/src/election_size_tracker.rs | 2 +- substrate/frame/staking/src/migrations.rs | 10 +++++----- .../src/construct_runtime/expand/call.rs | 2 +- .../src/construct_runtime/expand/outer_enums.rs | 4 +--- .../support/procedural/src/pallet/expand/call.rs | 2 +- .../procedural/src/pallet/expand/event.rs | 2 +- .../procedural/src/pallet/parse/composite.rs | 2 +- .../support/procedural/src/storage_alias.rs | 4 ++-- substrate/frame/support/src/tests/mod.rs | 2 +- substrate/frame/support/src/traits/dispatch.rs | 14 +++++++------- substrate/frame/support/src/traits/error.rs | 2 +- substrate/frame/support/src/traits/stored_map.rs | 2 +- .../frame/support/src/traits/tokens/currency.rs | 4 ++-- .../frame/support/src/traits/tokens/imbalance.rs | 2 +- substrate/frame/tips/src/migrations/v4.rs | 2 +- .../frame/transaction-payment/src/payment.rs | 2 +- substrate/frame/uniques/src/migration.rs | 2 +- .../api/proc-macro/src/impl_runtime_apis.rs | 4 ++-- .../primitives/arithmetic/src/helpers_128bit.rs | 2 +- substrate/primitives/arithmetic/src/traits.rs | 16 ++++++++-------- .../primitives/consensus/aura/src/inherents.rs | 2 +- .../primitives/consensus/babe/src/inherents.rs | 4 ++-- substrate/primitives/consensus/slots/src/lib.rs | 6 +++--- substrate/primitives/core/src/testing.rs | 4 ++-- .../primitives/runtime-interface/src/util.rs | 4 ++-- .../primitives/runtime-interface/src/wasm.rs | 2 +- .../primitives/runtime/src/generic/header.rs | 2 +- .../runtime/src/offchain/storage_lock.rs | 2 +- substrate/primitives/state-machine/src/error.rs | 2 +- .../state-machine/src/in_memory_backend.rs | 2 +- substrate/primitives/state-machine/src/stats.rs | 2 +- substrate/primitives/timestamp/src/lib.rs | 6 +++--- substrate/primitives/trie/src/node_header.rs | 6 +++--- substrate/primitives/weights/src/lib.rs | 6 +++--- 75 files changed, 125 insertions(+), 125 deletions(-) diff --git a/substrate/frame/alliance/src/benchmarking.rs b/substrate/frame/alliance/src/benchmarking.rs index b69d0156ec4..9fe0e29b42c 100644 --- a/substrate/frame/alliance/src/benchmarking.rs +++ b/substrate/frame/alliance/src/benchmarking.rs @@ -19,13 +19,12 @@ #![cfg(feature = "runtime-benchmarks")] -use sp_runtime::traits::{Bounded, Hash, StaticLookup}; -use sp_std::{ +use core::{ cmp, convert::{TryFrom, TryInto}, mem::size_of, - prelude::*, }; +use sp_runtime::traits::{Bounded, Hash, StaticLookup}; use frame_benchmarking::{account, impl_benchmark_test_suite, v2::*, BenchmarkError}; use frame_support::traits::{EnsureOrigin, Get, UnfilteredDispatchable}; diff --git a/substrate/frame/alliance/src/mock.rs b/substrate/frame/alliance/src/mock.rs index 627dde81afa..4a65485ed8f 100644 --- a/substrate/frame/alliance/src/mock.rs +++ b/substrate/frame/alliance/src/mock.rs @@ -17,13 +17,13 @@ //! Test utilities +use core::convert::{TryFrom, TryInto}; pub use sp_core::H256; use sp_runtime::traits::Hash; pub use sp_runtime::{ traits::{BlakeTwo256, IdentifyAccount, Lazy, Verify}, BuildStorage, }; -use sp_std::convert::{TryFrom, TryInto}; pub use frame_support::{ assert_noop, assert_ok, derive_impl, ord_parameter_types, parameter_types, diff --git a/substrate/frame/asset-conversion/src/mock.rs b/substrate/frame/asset-conversion/src/mock.rs index dd1d26ff238..6c19f834cb8 100644 --- a/substrate/frame/asset-conversion/src/mock.rs +++ b/substrate/frame/asset-conversion/src/mock.rs @@ -19,6 +19,7 @@ use super::*; use crate as pallet_asset_conversion; +use core::default::Default; use frame_support::{ construct_runtime, derive_impl, instances::{Instance1, Instance2}, @@ -39,7 +40,6 @@ use sp_runtime::{ traits::{AccountIdConversion, BlakeTwo256, IdentityLookup}, BuildStorage, }; -use sp_std::default::Default; type Block = frame_system::mocking::MockBlock; diff --git a/substrate/frame/asset-conversion/src/types.rs b/substrate/frame/asset-conversion/src/types.rs index fd6d41a55b6..5ee81c2012d 100644 --- a/substrate/frame/asset-conversion/src/types.rs +++ b/substrate/frame/asset-conversion/src/types.rs @@ -17,8 +17,8 @@ use super::*; use codec::{Decode, Encode, MaxEncodedLen}; +use core::marker::PhantomData; use scale_info::TypeInfo; -use sp_std::marker::PhantomData; /// Represents a swap path with associated asset amounts indicating how much of the asset needs to /// be deposited to get the following asset's amount withdrawn (this is inclusive of fees). diff --git a/substrate/frame/assets/src/extra_mutator.rs b/substrate/frame/assets/src/extra_mutator.rs index 2a44df5f0c6..e2f7742c694 100644 --- a/substrate/frame/assets/src/extra_mutator.rs +++ b/substrate/frame/assets/src/extra_mutator.rs @@ -38,7 +38,7 @@ impl, I: 'static> Drop for ExtraMutator { } } -impl, I: 'static> sp_std::ops::Deref for ExtraMutator { +impl, I: 'static> core::ops::Deref for ExtraMutator { type Target = T::Extra; fn deref(&self) -> &T::Extra { match self.pending { @@ -48,7 +48,7 @@ impl, I: 'static> sp_std::ops::Deref for ExtraMutator { } } -impl, I: 'static> sp_std::ops::DerefMut for ExtraMutator { +impl, I: 'static> core::ops::DerefMut for ExtraMutator { fn deref_mut(&mut self) -> &mut T::Extra { if self.pending.is_none() { self.pending = Some(self.original.clone()); @@ -60,7 +60,7 @@ impl, I: 'static> sp_std::ops::DerefMut for ExtraMutator { impl, I: 'static> ExtraMutator { pub(super) fn maybe_new( id: T::AssetId, - who: impl sp_std::borrow::Borrow, + who: impl core::borrow::Borrow, ) -> Option> { if let Some(a) = Account::::get(&id, who.borrow()) { Some(ExtraMutator:: { diff --git a/substrate/frame/assets/src/migration.rs b/substrate/frame/assets/src/migration.rs index efe77714c52..ff0ffbff0d3 100644 --- a/substrate/frame/assets/src/migration.rs +++ b/substrate/frame/assets/src/migration.rs @@ -64,7 +64,7 @@ pub mod v1 { } } - pub struct MigrateToV1(sp_std::marker::PhantomData); + pub struct MigrateToV1(core::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV1 { fn on_runtime_upgrade() -> Weight { let current_version = Pallet::::current_storage_version(); diff --git a/substrate/frame/assets/src/types.rs b/substrate/frame/assets/src/types.rs index 67f9bf07f5e..11edc7d3fcb 100644 --- a/substrate/frame/assets/src/types.rs +++ b/substrate/frame/assets/src/types.rs @@ -123,7 +123,7 @@ where return None } if let ExistenceReason::DepositHeld(deposit) = - sp_std::mem::replace(self, ExistenceReason::DepositRefunded) + core::mem::replace(self, ExistenceReason::DepositRefunded) { Some(deposit) } else { @@ -136,7 +136,7 @@ where return None } if let ExistenceReason::DepositFrom(depositor, deposit) = - sp_std::mem::replace(self, ExistenceReason::DepositRefunded) + core::mem::replace(self, ExistenceReason::DepositRefunded) { Some((depositor, deposit)) } else { diff --git a/substrate/frame/atomic-swap/src/lib.rs b/substrate/frame/atomic-swap/src/lib.rs index 8094c060301..609903e67e3 100644 --- a/substrate/frame/atomic-swap/src/lib.rs +++ b/substrate/frame/atomic-swap/src/lib.rs @@ -43,6 +43,10 @@ mod tests; use codec::{Decode, Encode}; +use core::{ + marker::PhantomData, + ops::{Deref, DerefMut}, +}; use frame_support::{ dispatch::DispatchResult, pallet_prelude::MaxEncodedLen, @@ -54,11 +58,6 @@ use frame_system::pallet_prelude::BlockNumberFor; use scale_info::TypeInfo; use sp_io::hashing::blake2_256; use sp_runtime::RuntimeDebug; -use sp_std::{ - marker::PhantomData, - ops::{Deref, DerefMut}, - prelude::*, -}; /// Pending atomic swap operation. #[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo, MaxEncodedLen)] diff --git a/substrate/frame/aura/src/migrations.rs b/substrate/frame/aura/src/migrations.rs index b45e4eb7cb5..727fe281aa3 100644 --- a/substrate/frame/aura/src/migrations.rs +++ b/substrate/frame/aura/src/migrations.rs @@ -19,7 +19,7 @@ use frame_support::{pallet_prelude::*, traits::Get, weights::Weight}; -struct __LastTimestamp(sp_std::marker::PhantomData); +struct __LastTimestamp(core::marker::PhantomData); impl frame_support::traits::StorageInstance for __LastTimestamp { fn pallet_prefix() -> &'static str { T::PalletPrefix::get() diff --git a/substrate/frame/babe/src/randomness.rs b/substrate/frame/babe/src/randomness.rs index d3d1bea2292..fd52981d2a1 100644 --- a/substrate/frame/babe/src/randomness.rs +++ b/substrate/frame/babe/src/randomness.rs @@ -51,7 +51,7 @@ use sp_runtime::traits::{Hash, One, Saturating}; /// /// Adversaries should not possess many block production slots towards the beginning or /// end of every epoch, but they possess some influence over when they possess more slots. -pub struct RandomnessFromTwoEpochsAgo(sp_std::marker::PhantomData); +pub struct RandomnessFromTwoEpochsAgo(core::marker::PhantomData); /// Randomness usable by on-chain code that **does not depend** upon finality and takes /// action based upon on-chain commitments made during the previous epoch. @@ -79,7 +79,7 @@ pub struct RandomnessFromTwoEpochsAgo(sp_std::marker::PhantomData); /// As an example usage, we determine parachain auctions ending times in Polkadot using /// `RandomnessFromOneEpochAgo` because it reduces bias from `ParentBlockRandomness` and /// does not require the extra finality delay of `RandomnessFromTwoEpochsAgo`. -pub struct RandomnessFromOneEpochAgo(sp_std::marker::PhantomData); +pub struct RandomnessFromOneEpochAgo(core::marker::PhantomData); /// Randomness produced semi-freshly with each block, but inherits limitations of /// `RandomnessFromTwoEpochsAgo` from which it derives. @@ -119,7 +119,7 @@ pub struct RandomnessFromOneEpochAgo(sp_std::marker::PhantomData); /// instead you are using this randomness externally, i.e. after block execution, then /// this randomness will be provided by the "current" block (this stems from the fact that /// we process VRF outputs on block execution finalization, i.e. `on_finalize`). -pub struct ParentBlockRandomness(sp_std::marker::PhantomData); +pub struct ParentBlockRandomness(core::marker::PhantomData); /// Randomness produced semi-freshly with each block, but inherits limitations of /// `RandomnessFromTwoEpochsAgo` from which it derives. @@ -128,7 +128,7 @@ pub struct ParentBlockRandomness(sp_std::marker::PhantomData); #[deprecated(note = "Should not be relied upon for correctness, \ will not provide fresh randomness for the current block. \ Please use `ParentBlockRandomness` instead.")] -pub struct CurrentBlockRandomness(sp_std::marker::PhantomData); +pub struct CurrentBlockRandomness(core::marker::PhantomData); impl RandomnessT> for RandomnessFromTwoEpochsAgo { fn random(subject: &[u8]) -> (T::Hash, BlockNumberFor) { diff --git a/substrate/frame/bounties/src/migrations/v4.rs b/substrate/frame/bounties/src/migrations/v4.rs index 4e6ba934481..71cd4f76be6 100644 --- a/substrate/frame/bounties/src/migrations/v4.rs +++ b/substrate/frame/bounties/src/migrations/v4.rs @@ -15,6 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use core::str; use frame_support::{ storage::{generator::StorageValue, StoragePrefixedMap}, traits::{ @@ -25,7 +26,6 @@ use frame_support::{ }; use sp_core::hexdisplay::HexDisplay; use sp_io::{hashing::twox_128, storage}; -use sp_std::str; use crate as pallet_bounties; diff --git a/substrate/frame/collective/src/benchmarking.rs b/substrate/frame/collective/src/benchmarking.rs index 503d7251053..af10eae5b67 100644 --- a/substrate/frame/collective/src/benchmarking.rs +++ b/substrate/frame/collective/src/benchmarking.rs @@ -20,8 +20,8 @@ use super::*; use crate::Pallet as Collective; +use core::mem::size_of; use sp_runtime::traits::Bounded; -use sp_std::mem::size_of; use frame_benchmarking::v1::{account, benchmarks_instance_pallet, whitelisted_caller}; use frame_system::{ diff --git a/substrate/frame/contracts/src/gas.rs b/substrate/frame/contracts/src/gas.rs index 363ddfad975..f78fb03a401 100644 --- a/substrate/frame/contracts/src/gas.rs +++ b/substrate/frame/contracts/src/gas.rs @@ -16,6 +16,7 @@ // limitations under the License. use crate::{exec::ExecError, Config, Error}; +use core::marker::PhantomData; use frame_support::{ dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, PostDispatchInfo}, weights::Weight, @@ -23,7 +24,6 @@ use frame_support::{ }; use sp_core::Get; use sp_runtime::{traits::Zero, DispatchError}; -use sp_std::marker::PhantomData; #[cfg(test)] use std::{any::Any, fmt::Debug}; diff --git a/substrate/frame/contracts/src/migration/v10.rs b/substrate/frame/contracts/src/migration/v10.rs index 22fad38739e..d64673aac7d 100644 --- a/substrate/frame/contracts/src/migration/v10.rs +++ b/substrate/frame/contracts/src/migration/v10.rs @@ -25,7 +25,10 @@ use crate::{ CodeHash, Config, Pallet, TrieId, Weight, LOG_TARGET, }; use codec::{Decode, Encode}; -use core::cmp::{max, min}; +use core::{ + cmp::{max, min}, + ops::Deref, +}; use frame_support::{ pallet_prelude::*, storage_alias, @@ -42,7 +45,7 @@ use sp_runtime::{ traits::{Hash, TrailingZeroInput, Zero}, Perbill, Saturating, }; -use sp_std::{ops::Deref, prelude::*}; +use sp_std::prelude::*; mod old { use super::*; diff --git a/substrate/frame/contracts/src/schedule.rs b/substrate/frame/contracts/src/schedule.rs index 51c7c0bd9dd..63641832315 100644 --- a/substrate/frame/contracts/src/schedule.rs +++ b/substrate/frame/contracts/src/schedule.rs @@ -21,11 +21,11 @@ use crate::{weights::WeightInfo, Config}; use codec::{Decode, Encode}; +use core::marker::PhantomData; use frame_support::{weights::Weight, DefaultNoBound}; use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; -use sp_std::marker::PhantomData; /// Definition of the cost schedule and other parameterizations for the wasm vm. /// diff --git a/substrate/frame/core-fellowship/src/tests/unit.rs b/substrate/frame/core-fellowship/src/tests/unit.rs index 52a31e5e106..de8cd858bdf 100644 --- a/substrate/frame/core-fellowship/src/tests/unit.rs +++ b/substrate/frame/core-fellowship/src/tests/unit.rs @@ -19,6 +19,7 @@ use std::collections::BTreeMap; +use core::cell::RefCell; use frame_support::{ assert_noop, assert_ok, derive_impl, ord_parameter_types, pallet_prelude::Weight, @@ -27,7 +28,6 @@ use frame_support::{ }; use frame_system::EnsureSignedBy; use sp_runtime::{traits::TryMorphInto, BuildStorage, DispatchError, DispatchResult}; -use sp_std::cell::RefCell; use crate as pallet_core_fellowship; use crate::*; diff --git a/substrate/frame/democracy/src/conviction.rs b/substrate/frame/democracy/src/conviction.rs index d2f685f7d99..54f4ff524f2 100644 --- a/substrate/frame/democracy/src/conviction.rs +++ b/substrate/frame/democracy/src/conviction.rs @@ -19,12 +19,12 @@ use crate::types::Delegations; use codec::{Decode, Encode, MaxEncodedLen}; +use core::result::Result; use scale_info::TypeInfo; use sp_runtime::{ traits::{Bounded, CheckedDiv, CheckedMul, Zero}, RuntimeDebug, }; -use sp_std::{prelude::*, result::Result}; /// A value denoting the strength of conviction of a vote. #[derive( diff --git a/substrate/frame/democracy/src/migrations/v1.rs b/substrate/frame/democracy/src/migrations/v1.rs index 64baea8f3af..5e423b9ab6e 100644 --- a/substrate/frame/democracy/src/migrations/v1.rs +++ b/substrate/frame/democracy/src/migrations/v1.rs @@ -54,7 +54,7 @@ pub mod v1 { use super::*; /// Migration for translating bare `Hash`es into `Bounded`s. - pub struct Migration(sp_std::marker::PhantomData); + pub struct Migration(core::marker::PhantomData); impl> OnRuntimeUpgrade for Migration { #[cfg(feature = "try-runtime")] diff --git a/substrate/frame/democracy/src/vote_threshold.rs b/substrate/frame/democracy/src/vote_threshold.rs index e8efa179ed8..82d6ed178f1 100644 --- a/substrate/frame/democracy/src/vote_threshold.rs +++ b/substrate/frame/democracy/src/vote_threshold.rs @@ -19,11 +19,11 @@ use crate::Tally; use codec::{Decode, Encode, MaxEncodedLen}; +use core::ops::{Add, Div, Mul, Rem}; use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; use sp_runtime::traits::{IntegerSquareRoot, Zero}; -use sp_std::ops::{Add, Div, Mul, Rem}; /// A means of determining if a vote is past pass threshold. #[derive( diff --git a/substrate/frame/election-provider-multi-phase/src/benchmarking.rs b/substrate/frame/election-provider-multi-phase/src/benchmarking.rs index 4a2855f1361..957ae51b8f1 100644 --- a/substrate/frame/election-provider-multi-phase/src/benchmarking.rs +++ b/substrate/frame/election-provider-multi-phase/src/benchmarking.rs @@ -496,7 +496,7 @@ frame_benchmarking::benchmarks! { let (_, stake, _) = voters[*idx]; stake }).unwrap_or_default(); - sp_std::cmp::Reverse(stake) + core::cmp::Reverse(stake) }); let mut index_assignments = assignments diff --git a/substrate/frame/election-provider-support/src/tests.rs b/substrate/frame/election-provider-support/src/tests.rs index 73ce1427cf2..6e3deb9e383 100644 --- a/substrate/frame/election-provider-support/src/tests.rs +++ b/substrate/frame/election-provider-support/src/tests.rs @@ -29,7 +29,7 @@ mod solution_type { // these need to come from the same dev-dependency `frame-election-provider-support`, not from // the crate. use crate::{generate_solution_type, Assignment, Error as NposError, NposSolution}; - use sp_std::fmt::Debug; + use core::fmt::Debug; #[allow(dead_code)] mod __private { diff --git a/substrate/frame/election-provider-support/src/weights.rs b/substrate/frame/election-provider-support/src/weights.rs index addb6ad8d03..09b0cb108f7 100644 --- a/substrate/frame/election-provider-support/src/weights.rs +++ b/substrate/frame/election-provider-support/src/weights.rs @@ -41,7 +41,7 @@ #![allow(unused_imports)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_election_provider_support_benchmarking. pub trait WeightInfo { diff --git a/substrate/frame/examples/basic/src/lib.rs b/substrate/frame/examples/basic/src/lib.rs index 5eff74922ca..dad4d01978f 100644 --- a/substrate/frame/examples/basic/src/lib.rs +++ b/substrate/frame/examples/basic/src/lib.rs @@ -54,6 +54,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::{Decode, Encode}; +use core::marker::PhantomData; use frame_support::{ dispatch::{ClassifyDispatch, DispatchClass, DispatchResult, Pays, PaysFee, WeighData}, traits::IsSubType, @@ -68,7 +69,7 @@ use sp_runtime::{ InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction, }, }; -use sp_std::{marker::PhantomData, prelude::*}; +use sp_std::vec::Vec; // Re-export pallet items so that they can be accessed from the crate namespace. pub use pallet::*; @@ -485,8 +486,8 @@ impl Pallet { #[scale_info(skip_type_params(T))] pub struct WatchDummy(PhantomData); -impl sp_std::fmt::Debug for WatchDummy { - fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { +impl core::fmt::Debug for WatchDummy { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "WatchDummy") } } @@ -501,7 +502,7 @@ where type AdditionalSigned = (); type Pre = (); - fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { + fn additional_signed(&self) -> core::result::Result<(), TransactionValidityError> { Ok(()) } diff --git a/substrate/frame/examples/basic/src/weights.rs b/substrate/frame/examples/basic/src/weights.rs index def944054cc..dbb9170aa67 100644 --- a/substrate/frame/examples/basic/src/weights.rs +++ b/substrate/frame/examples/basic/src/weights.rs @@ -42,7 +42,7 @@ #![allow(unused_imports)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_example_basic. pub trait WeightInfo { diff --git a/substrate/frame/grandpa/src/migrations.rs b/substrate/frame/grandpa/src/migrations.rs index 3a484eb60d2..9d0522c3a6e 100644 --- a/substrate/frame/grandpa/src/migrations.rs +++ b/substrate/frame/grandpa/src/migrations.rs @@ -35,7 +35,7 @@ mod v5; /// This migration should be added with a runtime upgrade that introduces the /// `MaxSetIdSessionEntries` constant to the pallet (although it could also be /// done later on). -pub struct CleanupSetIdSessionMap(sp_std::marker::PhantomData); +pub struct CleanupSetIdSessionMap(core::marker::PhantomData); impl OnRuntimeUpgrade for CleanupSetIdSessionMap { fn on_runtime_upgrade() -> Weight { // NOTE: since this migration will loop over all stale entries in the diff --git a/substrate/frame/im-online/src/migration.rs b/substrate/frame/im-online/src/migration.rs index 84652965972..3860a7ca532 100644 --- a/substrate/frame/im-online/src/migration.rs +++ b/substrate/frame/im-online/src/migration.rs @@ -56,7 +56,7 @@ pub mod v1 { use super::*; /// Simple migration that replaces `ReceivedHeartbeats` values with `true`. - pub struct Migration(sp_std::marker::PhantomData); + pub struct Migration(core::marker::PhantomData); impl OnRuntimeUpgrade for Migration { #[cfg(feature = "try-runtime")] diff --git a/substrate/frame/merkle-mountain-range/src/mmr/mod.rs b/substrate/frame/merkle-mountain-range/src/mmr/mod.rs index 536faa68e4e..93fefe910e4 100644 --- a/substrate/frame/merkle-mountain-range/src/mmr/mod.rs +++ b/substrate/frame/merkle-mountain-range/src/mmr/mod.rs @@ -30,7 +30,7 @@ pub type NodeOf = Node<>::Hashing, L>; pub type Node = DataOrHash; /// Default Merging & Hashing behavior for MMR. -pub struct Hasher(sp_std::marker::PhantomData<(H, L)>); +pub struct Hasher(core::marker::PhantomData<(H, L)>); impl mmr_lib::Merge for Hasher { type Item = Node; diff --git a/substrate/frame/multisig/src/migrations.rs b/substrate/frame/multisig/src/migrations.rs index 330613bb3df..d03e42a66a5 100644 --- a/substrate/frame/multisig/src/migrations.rs +++ b/substrate/frame/multisig/src/migrations.rs @@ -39,7 +39,7 @@ pub mod v1 { (OpaqueCall, ::AccountId, BalanceOf), >; - pub struct MigrateToV1(sp_std::marker::PhantomData); + pub struct MigrateToV1(core::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV1 { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { diff --git a/substrate/frame/nfts/src/macros.rs b/substrate/frame/nfts/src/macros.rs index 1a601ce0927..d313c8785b2 100644 --- a/substrate/frame/nfts/src/macros.rs +++ b/substrate/frame/nfts/src/macros.rs @@ -42,7 +42,7 @@ macro_rules! impl_codec_bitflags { impl Decode for $wrapper { fn decode( input: &mut I, - ) -> sp_std::result::Result { + ) -> ::core::result::Result { let field = <$size>::decode(input)?; Ok(Self(BitFlags::from_bits(field as $size).map_err(|_| "invalid value")?)) } diff --git a/substrate/frame/nfts/src/migration.rs b/substrate/frame/nfts/src/migration.rs index 94635a96aeb..d4cdf7e820d 100644 --- a/substrate/frame/nfts/src/migration.rs +++ b/substrate/frame/nfts/src/migration.rs @@ -51,7 +51,7 @@ pub mod v1 { } /// A migration utility to update the storage version from v0 to v1 for the pallet. - pub struct MigrateToV1(sp_std::marker::PhantomData); + pub struct MigrateToV1(core::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV1 { fn on_runtime_upgrade() -> Weight { let current_version = Pallet::::current_storage_version(); diff --git a/substrate/frame/node-authorization/src/weights.rs b/substrate/frame/node-authorization/src/weights.rs index a4529c845c7..881eeaf7a4c 100644 --- a/substrate/frame/node-authorization/src/weights.rs +++ b/substrate/frame/node-authorization/src/weights.rs @@ -22,7 +22,7 @@ #![allow(unused_imports)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; pub trait WeightInfo { fn add_well_known_node() -> Weight; diff --git a/substrate/frame/nomination-pools/src/tests.rs b/substrate/frame/nomination-pools/src/tests.rs index 47f48ba98b7..13298fa64f5 100644 --- a/substrate/frame/nomination-pools/src/tests.rs +++ b/substrate/frame/nomination-pools/src/tests.rs @@ -23,7 +23,7 @@ use sp_runtime::{bounded_btree_map, traits::Dispatchable, FixedU128}; macro_rules! unbonding_pools_with_era { ($($k:expr => $v:expr),* $(,)?) => {{ - use sp_std::iter::{Iterator, IntoIterator}; + use ::core::iter::{Iterator, IntoIterator}; let not_bounded: BTreeMap<_, _> = Iterator::collect(IntoIterator::into_iter([$(($k, $v),)*])); BoundedBTreeMap::, TotalUnbondingPools>::try_from(not_bounded).unwrap() }}; diff --git a/substrate/frame/referenda/src/types.rs b/substrate/frame/referenda/src/types.rs index 8d6a13ef27c..0a8fb4ff820 100644 --- a/substrate/frame/referenda/src/types.rs +++ b/substrate/frame/referenda/src/types.rs @@ -19,6 +19,7 @@ use super::*; use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; +use core::fmt::Debug; use frame_support::{ traits::{schedule::v3::Anon, Bounded}, Parameter, @@ -26,7 +27,6 @@ use frame_support::{ use scale_info::TypeInfo; use sp_arithmetic::{Rounding::*, SignedRounding::*}; use sp_runtime::{FixedI64, PerThing, RuntimeDebug}; -use sp_std::fmt::Debug; pub type BalanceOf = <>::Currency as Currency<::AccountId>>::Balance; diff --git a/substrate/frame/safe-mode/src/lib.rs b/substrate/frame/safe-mode/src/lib.rs index 325d751e17f..2bf2ebee0a4 100644 --- a/substrate/frame/safe-mode/src/lib.rs +++ b/substrate/frame/safe-mode/src/lib.rs @@ -79,6 +79,7 @@ pub mod mock; mod tests; pub mod weights; +use core::convert::TryInto; use frame_support::{ defensive_assert, pallet_prelude::*, @@ -96,7 +97,6 @@ use frame_support::{ use frame_system::pallet_prelude::*; use sp_arithmetic::traits::Zero; use sp_runtime::traits::Saturating; -use sp_std::{convert::TryInto, prelude::*}; pub use pallet::*; pub use weights::*; diff --git a/substrate/frame/salary/src/lib.rs b/substrate/frame/salary/src/lib.rs index 18e5c624218..f83a75dd97d 100644 --- a/substrate/frame/salary/src/lib.rs +++ b/substrate/frame/salary/src/lib.rs @@ -21,10 +21,10 @@ #![recursion_limit = "128"] use codec::{Decode, Encode, MaxEncodedLen}; +use core::marker::PhantomData; use scale_info::TypeInfo; use sp_arithmetic::traits::{Saturating, Zero}; use sp_runtime::{Perbill, RuntimeDebug}; -use sp_std::{marker::PhantomData, prelude::*}; use frame_support::{ defensive, diff --git a/substrate/frame/salary/src/tests/unit.rs b/substrate/frame/salary/src/tests/unit.rs index 07ef5ce63d5..b3fd00ec76b 100644 --- a/substrate/frame/salary/src/tests/unit.rs +++ b/substrate/frame/salary/src/tests/unit.rs @@ -19,6 +19,7 @@ use std::collections::BTreeMap; +use core::cell::RefCell; use frame_support::{ assert_noop, assert_ok, derive_impl, pallet_prelude::Weight, @@ -26,7 +27,6 @@ use frame_support::{ traits::{tokens::ConvertRank, ConstU64}, }; use sp_runtime::{traits::Identity, BuildStorage, DispatchResult}; -use sp_std::cell::RefCell; use crate as pallet_salary; use crate::*; diff --git a/substrate/frame/sassafras/src/mock.rs b/substrate/frame/sassafras/src/mock.rs index 82ea2fe1ef2..5e5909fcb0d 100644 --- a/substrate/frame/sassafras/src/mock.rs +++ b/substrate/frame/sassafras/src/mock.rs @@ -99,7 +99,7 @@ pub fn new_test_ext_with_pairs( pallet_sassafras::GenesisConfig:: { authorities: authorities.clone(), epoch_config: TEST_EPOCH_CONFIGURATION, - _phantom: sp_std::marker::PhantomData, + _phantom: core::marker::PhantomData, } .assimilate_storage(&mut storage) .unwrap(); diff --git a/substrate/frame/scheduler/src/migration.rs b/substrate/frame/scheduler/src/migration.rs index 76e2e04b49c..c2e956035a7 100644 --- a/substrate/frame/scheduler/src/migration.rs +++ b/substrate/frame/scheduler/src/migration.rs @@ -81,7 +81,7 @@ pub mod v3 { StorageMap, Twox64Concat, Vec, TaskAddress>>; /// Migrate the scheduler pallet from V3 to V4. - pub struct MigrateToV4(sp_std::marker::PhantomData); + pub struct MigrateToV4(core::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV4 { #[cfg(feature = "try-runtime")] @@ -194,7 +194,7 @@ pub mod v4 { /// /// This should be run on a scheduler that does not have /// since it piles up `None`-only agendas. This does not modify the pallet version. - pub struct CleanupAgendas(sp_std::marker::PhantomData); + pub struct CleanupAgendas(core::marker::PhantomData); impl OnRuntimeUpgrade for CleanupAgendas { #[cfg(feature = "try-runtime")] diff --git a/substrate/frame/session/src/migrations/v1.rs b/substrate/frame/session/src/migrations/v1.rs index 394a1f4a522..b6838099837 100644 --- a/substrate/frame/session/src/migrations/v1.rs +++ b/substrate/frame/session/src/migrations/v1.rs @@ -15,8 +15,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use core::str; use sp_io::hashing::twox_128; -use sp_std::str; use frame_support::{ storage::{generator::StorageValue, StoragePrefixedMap}, diff --git a/substrate/frame/society/src/migrations.rs b/substrate/frame/society/src/migrations.rs index a995c9d7be7..dafb1e0b9e5 100644 --- a/substrate/frame/society/src/migrations.rs +++ b/substrate/frame/society/src/migrations.rs @@ -29,7 +29,7 @@ const TARGET: &'static str = "runtime::society::migration"; /// This migration moves all the state to v2 of Society. pub struct VersionUncheckedMigrateToV2, I: 'static, PastPayouts>( - sp_std::marker::PhantomData<(T, I, PastPayouts)>, + core::marker::PhantomData<(T, I, PastPayouts)>, ); impl< diff --git a/substrate/frame/society/src/weights.rs b/substrate/frame/society/src/weights.rs index 7c59aed8449..c32c2383ac9 100644 --- a/substrate/frame/society/src/weights.rs +++ b/substrate/frame/society/src/weights.rs @@ -41,7 +41,7 @@ #![allow(unused_imports)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_society. pub trait WeightInfo { diff --git a/substrate/frame/staking/src/election_size_tracker.rs b/substrate/frame/staking/src/election_size_tracker.rs index 283ae0140ee..36e7fa48fda 100644 --- a/substrate/frame/staking/src/election_size_tracker.rs +++ b/substrate/frame/staking/src/election_size_tracker.rs @@ -84,7 +84,7 @@ use frame_election_provider_support::{ pub struct StaticTracker { pub size: usize, pub counter: usize, - _marker: sp_std::marker::PhantomData, + _marker: core::marker::PhantomData, } impl Default for StaticTracker { diff --git a/substrate/frame/staking/src/migrations.rs b/substrate/frame/staking/src/migrations.rs index 92af4fdba5f..98984be9920 100644 --- a/substrate/frame/staking/src/migrations.rs +++ b/substrate/frame/staking/src/migrations.rs @@ -64,7 +64,7 @@ type StorageVersion = StorageValue, ObsoleteReleases, Value pub mod v14 { use super::*; - pub struct MigrateToV14(sp_std::marker::PhantomData); + pub struct MigrateToV14(core::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV14 { fn on_runtime_upgrade() -> Weight { let current = Pallet::::current_storage_version(); @@ -95,7 +95,7 @@ pub mod v14 { pub mod v13 { use super::*; - pub struct MigrateToV13(sp_std::marker::PhantomData); + pub struct MigrateToV13(core::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV13 { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, TryRuntimeError> { @@ -151,7 +151,7 @@ pub mod v12 { /// /// We will be depending on the configurable value of `T::HistoryDepth` post /// this release. - pub struct MigrateToV12(sp_std::marker::PhantomData); + pub struct MigrateToV12(core::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV12 { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, TryRuntimeError> { @@ -205,7 +205,7 @@ pub mod v11 { #[cfg(feature = "try-runtime")] use sp_io::hashing::twox_128; - pub struct MigrateToV11(sp_std::marker::PhantomData<(T, P, N)>); + pub struct MigrateToV11(core::marker::PhantomData<(T, P, N)>); impl> OnRuntimeUpgrade for MigrateToV11 { @@ -301,7 +301,7 @@ pub mod v10 { /// That means we might slash someone a bit too early, but we will definitely /// won't forget to slash them. The cap of 512 is somewhat randomly taken to /// prevent us from iterating over an arbitrary large number of keys `on_runtime_upgrade`. - pub struct MigrateToV10(sp_std::marker::PhantomData); + pub struct MigrateToV10(core::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV10 { fn on_runtime_upgrade() -> frame_support::weights::Weight { if StorageVersion::::get() == ObsoleteReleases::V9_0_0 { diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs index ce2aa094279..b0041ccc075 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs @@ -178,7 +178,7 @@ pub fn expand_outer_dispatch( type PostInfo = #scrate::dispatch::PostDispatchInfo; fn dispatch(self, origin: RuntimeOrigin) -> #scrate::dispatch::DispatchResultWithPostInfo { if !::filter_call(&origin, &self) { - return #scrate::__private::sp_std::result::Result::Err( + return ::core::result::Result::Err( #system_path::Error::<#runtime>::CallFiltered.into() ); } diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs index df69c19a4b6..80b242ccbe4 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs @@ -134,7 +134,6 @@ pub fn expand_outer_enum( enum_ty, )); enum_conversions.extend(expand_enum_conversion( - scrate, pallet_decl, &pallet_enum, &enum_name_ident, @@ -220,7 +219,6 @@ fn expand_enum_variant( } fn expand_enum_conversion( - scrate: &TokenStream, pallet: &Pallet, pallet_enum: &TokenStream, enum_name_ident: &Ident, @@ -247,7 +245,7 @@ fn expand_enum_conversion( impl TryInto<#pallet_enum> for #enum_name_ident { type Error = (); - fn try_into(self) -> #scrate::__private::sp_std::result::Result<#pallet_enum, Self::Error> { + fn try_into(self) -> ::core::result::Result<#pallet_enum, Self::Error> { match self { Self::#variant_name(evt) => Ok(evt), _ => Err(()), diff --git a/substrate/frame/support/procedural/src/pallet/expand/call.rs b/substrate/frame/support/procedural/src/pallet/expand/call.rs index 90974619e8e..f43faba1ee0 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/call.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/call.rs @@ -304,7 +304,7 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { #[doc(hidden)] #[codec(skip)] __Ignore( - #frame_support::__private::sp_std::marker::PhantomData<(#type_use_gen,)>, + ::core::marker::PhantomData<(#type_use_gen,)>, #frame_support::Never, ), #( diff --git a/substrate/frame/support/procedural/src/pallet/expand/event.rs b/substrate/frame/support/procedural/src/pallet/expand/event.rs index 2713f45fc3d..655fc5507d2 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/event.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/event.rs @@ -87,7 +87,7 @@ pub fn expand_event(def: &mut Def) -> proc_macro2::TokenStream { #[doc(hidden)] #[codec(skip)] __Ignore( - #frame_support::__private::sp_std::marker::PhantomData<(#event_use_gen)>, + ::core::marker::PhantomData<(#event_use_gen)>, #frame_support::Never, ) ); diff --git a/substrate/frame/support/procedural/src/pallet/parse/composite.rs b/substrate/frame/support/procedural/src/pallet/parse/composite.rs index a744756234e..c3ac74846bf 100644 --- a/substrate/frame/support/procedural/src/pallet/parse/composite.rs +++ b/substrate/frame/support/procedural/src/pallet/parse/composite.rs @@ -171,7 +171,7 @@ impl CompositeDef { #[doc(hidden)] #[codec(skip)] __Ignore( - #scrate::__private::sp_std::marker::PhantomData, + ::core::marker::PhantomData, ) }); } diff --git a/substrate/frame/support/procedural/src/storage_alias.rs b/substrate/frame/support/procedural/src/storage_alias.rs index c0b4089a274..06f62768ff8 100644 --- a/substrate/frame/support/procedural/src/storage_alias.rs +++ b/substrate/frame/support/procedural/src/storage_alias.rs @@ -623,7 +623,7 @@ fn generate_storage_instance( quote! { #visibility struct #counter_name< #impl_generics >( - #crate_::__private::sp_std::marker::PhantomData<(#type_generics)> + ::core::marker::PhantomData<(#type_generics)> ) #where_clause; impl<#impl_generics> #crate_::traits::StorageInstance @@ -653,7 +653,7 @@ fn generate_storage_instance( let code = quote! { #[allow(non_camel_case_types)] #visibility struct #name< #impl_generics >( - #crate_::__private::sp_std::marker::PhantomData<(#type_generics)> + ::core::marker::PhantomData<(#type_generics)> ) #where_clause; impl<#impl_generics> #crate_::traits::StorageInstance diff --git a/substrate/frame/support/src/tests/mod.rs b/substrate/frame/support/src/tests/mod.rs index c6a0b6cde77..c63bfb181c3 100644 --- a/substrate/frame/support/src/tests/mod.rs +++ b/substrate/frame/support/src/tests/mod.rs @@ -171,7 +171,7 @@ pub mod frame_system { pub data: Vec<(u32, u64)>, pub test_config: Vec<(u32, u32, u64)>, #[serde(skip)] - pub _config: sp_std::marker::PhantomData, + pub _config: core::marker::PhantomData, } impl Default for GenesisConfig { diff --git a/substrate/frame/support/src/traits/dispatch.rs b/substrate/frame/support/src/traits/dispatch.rs index facc35a77ae..de50ce7a26c 100644 --- a/substrate/frame/support/src/traits/dispatch.rs +++ b/substrate/frame/support/src/traits/dispatch.rs @@ -19,11 +19,11 @@ use crate::dispatch::{DispatchResultWithPostInfo, Parameter, RawOrigin}; use codec::MaxEncodedLen; +use core::{cmp::Ordering, marker::PhantomData}; use sp_runtime::{ traits::{BadOrigin, Get, Member, Morph, TryMorph}, Either, }; -use sp_std::{cmp::Ordering, marker::PhantomData}; use super::misc; @@ -85,7 +85,7 @@ pub trait EnsureOrigin { /// ```rust /// # use frame_support::traits::{EnsureOriginEqualOrHigherPrivilege, PrivilegeCmp, EnsureOrigin as _}; /// # use sp_runtime::traits::{parameter_types, Get}; -/// # use sp_std::cmp::Ordering; +/// # use core::cmp::Ordering; /// /// #[derive(Eq, PartialEq, Debug)] /// pub enum Origin { @@ -124,7 +124,7 @@ pub trait EnsureOrigin { /// assert!(EnsureOrigin::ensure_origin(Origin::NormalUser).is_err()); /// ``` pub struct EnsureOriginEqualOrHigherPrivilege( - sp_std::marker::PhantomData<(Origin, PrivilegeCmp)>, + core::marker::PhantomData<(Origin, PrivilegeCmp)>, ); impl EnsureOrigin @@ -218,7 +218,7 @@ macro_rules! impl_ensure_origin_with_arg_ignoring_arg { } /// [`EnsureOrigin`] implementation that always fails. -pub struct NeverEnsureOrigin(sp_std::marker::PhantomData); +pub struct NeverEnsureOrigin(core::marker::PhantomData); impl EnsureOrigin for NeverEnsureOrigin { type Success = Success; fn try_origin(o: OO) -> Result { @@ -235,7 +235,7 @@ impl_ensure_origin_with_arg_ignoring_arg! { {} } -pub struct AsEnsureOriginWithArg(sp_std::marker::PhantomData); +pub struct AsEnsureOriginWithArg(core::marker::PhantomData); impl> EnsureOriginWithArg for AsEnsureOriginWithArg { @@ -353,7 +353,7 @@ impl< /// Origin check will pass if `L` or `R` origin check passes. `L` is tested first. /// /// Successful origin is derived from the left side. -pub struct EitherOfDiverse(sp_std::marker::PhantomData<(L, R)>); +pub struct EitherOfDiverse(core::marker::PhantomData<(L, R)>); impl, R: EnsureOrigin> EnsureOrigin for EitherOfDiverse { @@ -402,7 +402,7 @@ pub type EnsureOneOf = EitherOfDiverse; /// Origin check will pass if `L` or `R` origin check passes. `L` is tested first. /// /// Successful origin is derived from the left side. -pub struct EitherOf(sp_std::marker::PhantomData<(L, R)>); +pub struct EitherOf(core::marker::PhantomData<(L, R)>); impl< OuterOrigin, L: EnsureOrigin, diff --git a/substrate/frame/support/src/traits/error.rs b/substrate/frame/support/src/traits/error.rs index 0f30e266da2..7b577fe9995 100644 --- a/substrate/frame/support/src/traits/error.rs +++ b/substrate/frame/support/src/traits/error.rs @@ -17,7 +17,7 @@ //! Traits for describing and constraining pallet error types. use codec::{Compact, Decode, Encode}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Trait indicating that the implementing type is going to be included as a field in a variant of /// the `#[pallet::error]` enum type. diff --git a/substrate/frame/support/src/traits/stored_map.rs b/substrate/frame/support/src/traits/stored_map.rs index cbe70f29323..15df2ef0af5 100644 --- a/substrate/frame/support/src/traits/stored_map.rs +++ b/substrate/frame/support/src/traits/stored_map.rs @@ -81,7 +81,7 @@ pub trait StoredMap { /// be the default value), or where the account is being removed or reset back to the default value /// where previously it did exist (though may have been in a default state). This works well with /// system module's `CallOnCreatedAccount` and `CallKillAccount`. -pub struct StorageMapShim(sp_std::marker::PhantomData<(S, K, T)>); +pub struct StorageMapShim(core::marker::PhantomData<(S, K, T)>); impl, K: FullCodec, T: FullCodec + Default> StoredMap for StorageMapShim { diff --git a/substrate/frame/support/src/traits/tokens/currency.rs b/substrate/frame/support/src/traits/tokens/currency.rs index 0030e1261da..8b773115011 100644 --- a/substrate/frame/support/src/traits/tokens/currency.rs +++ b/substrate/frame/support/src/traits/tokens/currency.rs @@ -211,7 +211,7 @@ pub trait Currency { /// A non-const `Get` implementation parameterised by a `Currency` impl which provides the result /// of `total_issuance`. -pub struct TotalIssuanceOf, A>(sp_std::marker::PhantomData<(C, A)>); +pub struct TotalIssuanceOf, A>(core::marker::PhantomData<(C, A)>); impl, A> Get for TotalIssuanceOf { fn get() -> C::Balance { C::total_issuance() @@ -220,7 +220,7 @@ impl, A> Get for TotalIssuanceOf { /// A non-const `Get` implementation parameterised by a `Currency` impl which provides the result /// of `active_issuance`. -pub struct ActiveIssuanceOf, A>(sp_std::marker::PhantomData<(C, A)>); +pub struct ActiveIssuanceOf, A>(core::marker::PhantomData<(C, A)>); impl, A> Get for ActiveIssuanceOf { fn get() -> C::Balance { C::active_issuance() diff --git a/substrate/frame/support/src/traits/tokens/imbalance.rs b/substrate/frame/support/src/traits/tokens/imbalance.rs index 9dd8531324d..8eb9b355a4c 100644 --- a/substrate/frame/support/src/traits/tokens/imbalance.rs +++ b/substrate/frame/support/src/traits/tokens/imbalance.rs @@ -19,8 +19,8 @@ //! with unbalanced operations. use crate::traits::misc::{SameOrOther, TryDrop}; +use core::ops::Div; use sp_runtime::traits::Saturating; -use sp_std::ops::Div; mod on_unbalanced; mod signed_imbalance; diff --git a/substrate/frame/tips/src/migrations/v4.rs b/substrate/frame/tips/src/migrations/v4.rs index 2404c6de1a1..8b0e65c58db 100644 --- a/substrate/frame/tips/src/migrations/v4.rs +++ b/substrate/frame/tips/src/migrations/v4.rs @@ -15,8 +15,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use core::str; use sp_io::hashing::twox_128; -use sp_std::str; use super::super::LOG_TARGET; use frame_support::{ diff --git a/substrate/frame/transaction-payment/src/payment.rs b/substrate/frame/transaction-payment/src/payment.rs index 22b0ac7c742..886683f2e0b 100644 --- a/substrate/frame/transaction-payment/src/payment.rs +++ b/substrate/frame/transaction-payment/src/payment.rs @@ -18,11 +18,11 @@ /// ! Traits and default implementation for paying transaction fees. use crate::Config; +use core::marker::PhantomData; use sp_runtime::{ traits::{DispatchInfoOf, PostDispatchInfoOf, Saturating, Zero}, transaction_validity::InvalidTransaction, }; -use sp_std::marker::PhantomData; use frame_support::{ traits::{Currency, ExistenceRequirement, Imbalance, OnUnbalanced, WithdrawReasons}, diff --git a/substrate/frame/uniques/src/migration.rs b/substrate/frame/uniques/src/migration.rs index 6b2bbf375e7..ba0855a6bb6 100644 --- a/substrate/frame/uniques/src/migration.rs +++ b/substrate/frame/uniques/src/migration.rs @@ -17,8 +17,8 @@ //! Various pieces of common functionality. use super::*; +use core::marker::PhantomData; use frame_support::traits::{Get, OnRuntimeUpgrade}; -use sp_std::marker::PhantomData; mod v1 { use super::*; diff --git a/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs b/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs index 79378968437..b7e5600a017 100644 --- a/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs +++ b/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs @@ -243,7 +243,7 @@ fn generate_wasm_interface(impls: &[ItemImpl]) -> Result { &[0u8; 0] } else { unsafe { - #c::slice::from_raw_parts(input_data, input_len) + ::core::slice::from_raw_parts(input_data, input_len) } }; @@ -345,7 +345,7 @@ fn generate_runtime_api_base_structures() -> Result { &self, backend: &B, parent_hash: Block::Hash, - ) -> core::result::Result< + ) -> ::core::result::Result< #crate_::StorageChanges, String > where Self: Sized { diff --git a/substrate/primitives/arithmetic/src/helpers_128bit.rs b/substrate/primitives/arithmetic/src/helpers_128bit.rs index 4e234d4026c..cbbad58443f 100644 --- a/substrate/primitives/arithmetic/src/helpers_128bit.rs +++ b/substrate/primitives/arithmetic/src/helpers_128bit.rs @@ -22,7 +22,7 @@ //! multiplication implementation provided there. use crate::{biguint, Rounding}; -use sp_std::cmp::{max, min}; +use core::cmp::{max, min}; /// Helper gcd function used in Rational128 implementation. pub fn gcd(a: u128, b: u128) -> u128 { diff --git a/substrate/primitives/arithmetic/src/traits.rs b/substrate/primitives/arithmetic/src/traits.rs index 6fcc8248539..66ec19c1b57 100644 --- a/substrate/primitives/arithmetic/src/traits.rs +++ b/substrate/primitives/arithmetic/src/traits.rs @@ -18,6 +18,9 @@ //! Primitive traits for the runtime arithmetic. use codec::HasCompact; +use core::ops::{ + Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, Shl, Shr, Sub, SubAssign, +}; pub use ensure::{ ensure_pow, Ensure, EnsureAdd, EnsureAddAssign, EnsureDiv, EnsureDivAssign, EnsureFixedPointNumber, EnsureFrom, EnsureInto, EnsureMul, EnsureMulAssign, EnsureOp, @@ -28,9 +31,6 @@ pub use num_traits::{ checked_pow, Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr, CheckedSub, One, Signed, Unsigned, Zero, }; -use sp_std::ops::{ - Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, Shl, Shr, Sub, SubAssign, -}; use crate::MultiplyRational; @@ -262,7 +262,7 @@ pub trait Saturating { Self: One, { let mut o = Self::one(); - sp_std::mem::swap(&mut o, self); + core::mem::swap(&mut o, self); *self = o.saturating_add(One::one()); } @@ -272,7 +272,7 @@ pub trait Saturating { Self: One, { let mut o = Self::one(); - sp_std::mem::swap(&mut o, self); + core::mem::swap(&mut o, self); *self = o.saturating_sub(One::one()); } @@ -282,7 +282,7 @@ pub trait Saturating { Self: One, { let mut o = Self::one(); - sp_std::mem::swap(&mut o, self); + core::mem::swap(&mut o, self); *self = o.saturating_add(amount); } @@ -292,7 +292,7 @@ pub trait Saturating { Self: One, { let mut o = Self::one(); - sp_std::mem::swap(&mut o, self); + core::mem::swap(&mut o, self); *self = o.saturating_sub(amount); } } @@ -949,7 +949,7 @@ mod ensure { } } - impl sp_std::ops::Mul for Signum { + impl core::ops::Mul for Signum { type Output = Self; fn mul(self, rhs: Self) -> Self { diff --git a/substrate/primitives/consensus/aura/src/inherents.rs b/substrate/primitives/consensus/aura/src/inherents.rs index 1ef25feb0ad..733081dd790 100644 --- a/substrate/primitives/consensus/aura/src/inherents.rs +++ b/substrate/primitives/consensus/aura/src/inherents.rs @@ -69,7 +69,7 @@ impl InherentDataProvider { } #[cfg(feature = "std")] -impl sp_std::ops::Deref for InherentDataProvider { +impl core::ops::Deref for InherentDataProvider { type Target = InherentType; fn deref(&self) -> &Self::Target { diff --git a/substrate/primitives/consensus/babe/src/inherents.rs b/substrate/primitives/consensus/babe/src/inherents.rs index b01bd1c9221..909769f3031 100644 --- a/substrate/primitives/consensus/babe/src/inherents.rs +++ b/substrate/primitives/consensus/babe/src/inherents.rs @@ -17,8 +17,8 @@ //! Inherents for BABE +use core::result::Result; use sp_inherents::{Error, InherentData, InherentIdentifier}; -use sp_std::result::Result; /// The BABE inherent identifier. pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"babeslot"; @@ -75,7 +75,7 @@ impl InherentDataProvider { } #[cfg(feature = "std")] -impl sp_std::ops::Deref for InherentDataProvider { +impl core::ops::Deref for InherentDataProvider { type Target = InherentType; fn deref(&self) -> &Self::Target { diff --git a/substrate/primitives/consensus/slots/src/lib.rs b/substrate/primitives/consensus/slots/src/lib.rs index 23cb7f7365a..eb3b3d3a449 100644 --- a/substrate/primitives/consensus/slots/src/lib.rs +++ b/substrate/primitives/consensus/slots/src/lib.rs @@ -155,9 +155,9 @@ impl SlotDuration { #[cfg(feature = "std")] impl SlotDuration { - /// Returns `self` as [`sp_std::time::Duration`]. - pub const fn as_duration(&self) -> sp_std::time::Duration { - sp_std::time::Duration::from_millis(self.0) + /// Returns `self` as [`core::time::Duration`]. + pub const fn as_duration(&self) -> core::time::Duration { + core::time::Duration::from_millis(self.0) } } diff --git a/substrate/primitives/core/src/testing.rs b/substrate/primitives/core/src/testing.rs index 947dcc387fc..c26e23d442f 100644 --- a/substrate/primitives/core/src/testing.rs +++ b/substrate/primitives/core/src/testing.rs @@ -89,7 +89,7 @@ macro_rules! wasm_export_functions { &[0u8; 0] } else { unsafe { - $crate::sp_std::slice::from_raw_parts(input_data, input_len) + ::core::slice::from_raw_parts(input_data, input_len) } }; @@ -117,7 +117,7 @@ macro_rules! wasm_export_functions { &[0u8; 0] } else { unsafe { - $crate::sp_std::slice::from_raw_parts(input_data, input_len) + ::core::slice::from_raw_parts(input_data, input_len) } }; diff --git a/substrate/primitives/runtime-interface/src/util.rs b/substrate/primitives/runtime-interface/src/util.rs index 8db32271a0e..86c8e4b50e2 100644 --- a/substrate/primitives/runtime-interface/src/util.rs +++ b/substrate/primitives/runtime-interface/src/util.rs @@ -21,7 +21,7 @@ pub fn pack_ptr_and_len(ptr: u32, len: u32) -> u64 { // The static assertions from above are changed into a runtime check. #[cfg(all(not(feature = "std"), feature = "disable_target_static_assertions"))] - assert_eq!(4, sp_std::mem::size_of::()); + assert_eq!(4, core::mem::size_of::()); (u64::from(len) << 32) | u64::from(ptr) } @@ -34,7 +34,7 @@ pub fn pack_ptr_and_len(ptr: u32, len: u32) -> u64 { pub fn unpack_ptr_and_len(val: u64) -> (u32, u32) { // The static assertions from above are changed into a runtime check. #[cfg(all(not(feature = "std"), feature = "disable_target_static_assertions"))] - assert_eq!(4, sp_std::mem::size_of::()); + assert_eq!(4, core::mem::size_of::()); let ptr = (val & (!0u32 as u64)) as u32; let len = (val >> 32) as u32; diff --git a/substrate/primitives/runtime-interface/src/wasm.rs b/substrate/primitives/runtime-interface/src/wasm.rs index 91205addf21..10bb50c6403 100644 --- a/substrate/primitives/runtime-interface/src/wasm.rs +++ b/substrate/primitives/runtime-interface/src/wasm.rs @@ -19,7 +19,7 @@ use crate::RIType; -use sp_std::cell::Cell; +use core::cell::Cell; /// Something that can be created from a ffi value. /// diff --git a/substrate/primitives/runtime/src/generic/header.rs b/substrate/primitives/runtime/src/generic/header.rs index 0eeef363a06..d78aa5c8d3c 100644 --- a/substrate/primitives/runtime/src/generic/header.rs +++ b/substrate/primitives/runtime/src/generic/header.rs @@ -133,7 +133,7 @@ where impl Header where Number: Member - + sp_std::hash::Hash + + core::hash::Hash + Copy + MaybeDisplay + AtLeast32BitUnsigned diff --git a/substrate/primitives/runtime/src/offchain/storage_lock.rs b/substrate/primitives/runtime/src/offchain/storage_lock.rs index a2da48721e7..56d0eeae527 100644 --- a/substrate/primitives/runtime/src/offchain/storage_lock.rs +++ b/substrate/primitives/runtime/src/offchain/storage_lock.rs @@ -66,9 +66,9 @@ use crate::{ traits::BlockNumberProvider, }; use codec::{Codec, Decode, Encode}; +use core::fmt; use sp_core::offchain::{Duration, Timestamp}; use sp_io::offchain; -use sp_std::fmt; /// Default expiry duration for time based locks in milliseconds. const STORAGE_LOCK_DEFAULT_EXPIRY_DURATION: Duration = Duration::from_millis(20_000); diff --git a/substrate/primitives/state-machine/src/error.rs b/substrate/primitives/state-machine/src/error.rs index 4e8e02a2602..cf8ca4425c1 100644 --- a/substrate/primitives/state-machine/src/error.rs +++ b/substrate/primitives/state-machine/src/error.rs @@ -16,7 +16,7 @@ // limitations under the License. /// State Machine Errors -use sp_std::fmt; +use core::fmt; /// State Machine Error bound. /// diff --git a/substrate/primitives/state-machine/src/in_memory_backend.rs b/substrate/primitives/state-machine/src/in_memory_backend.rs index ce551cec2a4..06fe6d4162a 100644 --- a/substrate/primitives/state-machine/src/in_memory_backend.rs +++ b/substrate/primitives/state-machine/src/in_memory_backend.rs @@ -79,7 +79,7 @@ where /// Apply the given transaction to this backend and set the root to the given value. pub fn apply_transaction(&mut self, root: H::Out, transaction: PrefixedMemoryDB) { - let mut storage = sp_std::mem::take(self).into_storage(); + let mut storage = core::mem::take(self).into_storage(); storage.consolidate(transaction); *self = TrieBackendBuilder::new(storage, root).build(); diff --git a/substrate/primitives/state-machine/src/stats.rs b/substrate/primitives/state-machine/src/stats.rs index 7c5510961a3..84ab7725660 100644 --- a/substrate/primitives/state-machine/src/stats.rs +++ b/substrate/primitives/state-machine/src/stats.rs @@ -17,7 +17,7 @@ //! Usage statistics for state db -use sp_std::cell::RefCell; +use core::cell::RefCell; #[cfg(feature = "std")] use std::time::{Duration, Instant}; diff --git a/substrate/primitives/timestamp/src/lib.rs b/substrate/primitives/timestamp/src/lib.rs index d1bd2a3446e..a91ee16afe8 100644 --- a/substrate/primitives/timestamp/src/lib.rs +++ b/substrate/primitives/timestamp/src/lib.rs @@ -20,8 +20,8 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::{Decode, Encode}; +use core::time::Duration; use sp_inherents::{InherentData, InherentIdentifier, IsFatalError}; -use sp_std::time::Duration; /// The identifier for the `timestamp` inherent. pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"timstap0"; @@ -69,7 +69,7 @@ impl Timestamp { } } -impl sp_std::ops::Deref for Timestamp { +impl core::ops::Deref for Timestamp { type Target = u64; fn deref(&self) -> &Self::Target { @@ -219,7 +219,7 @@ impl InherentDataProvider { } #[cfg(feature = "std")] -impl sp_std::ops::Deref for InherentDataProvider { +impl core::ops::Deref for InherentDataProvider { type Target = InherentType; fn deref(&self) -> &Self::Target { diff --git a/substrate/primitives/trie/src/node_header.rs b/substrate/primitives/trie/src/node_header.rs index c118ee07b8c..dc33e6bd614 100644 --- a/substrate/primitives/trie/src/node_header.rs +++ b/substrate/primitives/trie/src/node_header.rs @@ -19,7 +19,7 @@ use crate::trie_constants; use codec::{Decode, Encode, Input, Output}; -use sp_std::iter::once; +use core::iter::once; /// A node header #[derive(Copy, Clone, PartialEq, Eq, sp_core::RuntimeDebug)] @@ -118,7 +118,7 @@ pub(crate) fn size_and_prefix_iterator( prefix_mask: usize, ) -> impl Iterator { let max_value = 255u8 >> prefix_mask; - let l1 = sp_std::cmp::min((max_value as usize).saturating_sub(1), size); + let l1 = core::cmp::min((max_value as usize).saturating_sub(1), size); let (first_byte, mut rem) = if size == l1 { (once(prefix + l1 as u8), 0) } else { @@ -138,7 +138,7 @@ pub(crate) fn size_and_prefix_iterator( None } }; - first_byte.chain(sp_std::iter::from_fn(next_bytes)) + first_byte.chain(core::iter::from_fn(next_bytes)) } /// Encodes size and prefix to a stream output. diff --git a/substrate/primitives/weights/src/lib.rs b/substrate/primitives/weights/src/lib.rs index ef431bddee2..aede9473535 100644 --- a/substrate/primitives/weights/src/lib.rs +++ b/substrate/primitives/weights/src/lib.rs @@ -235,7 +235,7 @@ where } /// Implementor of `WeightToFee` that maps one unit of weight to one unit of fee. -pub struct IdentityFee(sp_std::marker::PhantomData); +pub struct IdentityFee(core::marker::PhantomData); impl WeightToFee for IdentityFee where @@ -249,7 +249,7 @@ where } /// Implementor of [`WeightToFee`] such that it maps any unit of weight to a fixed fee. -pub struct FixedFee(sp_std::marker::PhantomData); +pub struct FixedFee(core::marker::PhantomData); impl WeightToFee for FixedFee where @@ -275,7 +275,7 @@ pub type NoFee = FixedFee<0, T>; /// // Results in a multiplier of 10 for each unit of weight (or length) /// type LengthToFee = ConstantMultiplier::>; /// ``` -pub struct ConstantMultiplier(sp_std::marker::PhantomData<(T, M)>); +pub struct ConstantMultiplier(core::marker::PhantomData<(T, M)>); impl WeightToFee for ConstantMultiplier where -- GitLab From a462207158360b162228d9877fed7b9ca1f23fc2 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 6 Feb 2024 16:11:27 +0300 Subject: [PATCH 017/133] Introduce submit_finality_proof_ex call to bridges GRANDPA pallet (#3225) backport of https://github.com/paritytech/parity-bridges-common/pull/2821 (see detailed description there) --- .../src/refund_relayer_extension.rs | 268 +++++++++++++++++- bridges/modules/grandpa/README.md | 10 +- bridges/modules/grandpa/src/benchmarking.rs | 5 +- bridges/modules/grandpa/src/call_ext.rs | 115 +++++++- bridges/modules/grandpa/src/lib.rs | 218 +++++++++----- bridges/modules/parachains/src/lib.rs | 4 +- bridges/primitives/header-chain/src/lib.rs | 10 + .../helpers/chains/rococo-at-westend.js | 6 +- .../helpers/chains/westend-at-rococo.js | 6 +- prdoc/pr_3225.prdoc | 11 + 10 files changed, 550 insertions(+), 103 deletions(-) create mode 100644 prdoc/pr_3225.prdoc diff --git a/bridges/bin/runtime-common/src/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/refund_relayer_extension.rs index 27b7ff1a551..bfcb82ad166 100644 --- a/bridges/bin/runtime-common/src/refund_relayer_extension.rs +++ b/bridges/bin/runtime-common/src/refund_relayer_extension.rs @@ -195,6 +195,19 @@ impl CallInfo { } } + /// Returns mutable reference to pre-dispatch `finality_target` sent to the + /// `SubmitFinalityProof` call. + #[cfg(test)] + fn submit_finality_proof_info_mut( + &mut self, + ) -> Option<&mut SubmitFinalityProofInfo> { + match *self { + Self::AllFinalityAndMsgs(ref mut info, _, _) => Some(info), + Self::RelayFinalityAndMsgs(ref mut info, _) => Some(info), + _ => None, + } + } + /// Returns the pre-dispatch `SubmitParachainHeadsInfo`. fn submit_parachain_heads_info(&self) -> Option<&SubmitParachainHeadsInfo> { match self { @@ -844,7 +857,7 @@ mod tests { use bp_parachains::{BestParaHeadHash, ParaInfo}; use bp_polkadot_core::parachains::{ParaHeadsProof, ParaId}; use bp_runtime::{BasicOperatingMode, HeaderId}; - use bp_test_utils::{make_default_justification, test_keyring}; + use bp_test_utils::{make_default_justification, test_keyring, TEST_GRANDPA_SET_ID}; use frame_support::{ assert_storage_noop, parameter_types, traits::{fungible::Mutate, ReservableCurrency}, @@ -929,7 +942,7 @@ mod tests { let authorities = test_keyring().into_iter().map(|(a, w)| (a.into(), w)).collect(); let best_relay_header = HeaderId(best_relay_header_number, RelayBlockHash::default()); pallet_bridge_grandpa::CurrentAuthoritySet::::put( - StoredAuthoritySet::try_new(authorities, 0).unwrap(), + StoredAuthoritySet::try_new(authorities, TEST_GRANDPA_SET_ID).unwrap(), ); pallet_bridge_grandpa::BestFinalized::::put(best_relay_header); @@ -977,6 +990,23 @@ mod tests { }) } + fn submit_relay_header_call_ex(relay_header_number: RelayBlockNumber) -> RuntimeCall { + let relay_header = BridgedChainHeader::new( + relay_header_number, + Default::default(), + Default::default(), + Default::default(), + Default::default(), + ); + let relay_justification = make_default_justification(&relay_header); + + RuntimeCall::BridgeGrandpa(GrandpaCall::submit_finality_proof_ex { + finality_target: Box::new(relay_header), + justification: relay_justification, + current_set_id: TEST_GRANDPA_SET_ID, + }) + } + fn submit_parachain_head_call( parachain_head_at_relay_header_number: RelayBlockNumber, ) -> RuntimeCall { @@ -1059,6 +1089,18 @@ mod tests { }) } + fn relay_finality_and_delivery_batch_call_ex( + relay_header_number: RelayBlockNumber, + best_message: MessageNonce, + ) -> RuntimeCall { + RuntimeCall::Utility(UtilityCall::batch_all { + calls: vec![ + submit_relay_header_call_ex(relay_header_number), + message_delivery_call(best_message), + ], + }) + } + fn relay_finality_and_confirmation_batch_call( relay_header_number: RelayBlockNumber, best_message: MessageNonce, @@ -1071,6 +1113,18 @@ mod tests { }) } + fn relay_finality_and_confirmation_batch_call_ex( + relay_header_number: RelayBlockNumber, + best_message: MessageNonce, + ) -> RuntimeCall { + RuntimeCall::Utility(UtilityCall::batch_all { + calls: vec![ + submit_relay_header_call_ex(relay_header_number), + message_confirmation_call(best_message), + ], + }) + } + fn all_finality_and_delivery_batch_call( relay_header_number: RelayBlockNumber, parachain_head_at_relay_header_number: RelayBlockNumber, @@ -1085,6 +1139,20 @@ mod tests { }) } + fn all_finality_and_delivery_batch_call_ex( + relay_header_number: RelayBlockNumber, + parachain_head_at_relay_header_number: RelayBlockNumber, + best_message: MessageNonce, + ) -> RuntimeCall { + RuntimeCall::Utility(UtilityCall::batch_all { + calls: vec![ + submit_relay_header_call_ex(relay_header_number), + submit_parachain_head_call(parachain_head_at_relay_header_number), + message_delivery_call(best_message), + ], + }) + } + fn all_finality_and_confirmation_batch_call( relay_header_number: RelayBlockNumber, parachain_head_at_relay_header_number: RelayBlockNumber, @@ -1099,12 +1167,27 @@ mod tests { }) } + fn all_finality_and_confirmation_batch_call_ex( + relay_header_number: RelayBlockNumber, + parachain_head_at_relay_header_number: RelayBlockNumber, + best_message: MessageNonce, + ) -> RuntimeCall { + RuntimeCall::Utility(UtilityCall::batch_all { + calls: vec![ + submit_relay_header_call_ex(relay_header_number), + submit_parachain_head_call(parachain_head_at_relay_header_number), + message_confirmation_call(best_message), + ], + }) + } + fn all_finality_pre_dispatch_data() -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: CallInfo::AllFinalityAndMsgs( SubmitFinalityProofInfo { block_number: 200, + current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, }, @@ -1128,12 +1211,20 @@ mod tests { } } + fn all_finality_pre_dispatch_data_ex() -> PreDispatchData { + let mut data = all_finality_pre_dispatch_data(); + data.call_info.submit_finality_proof_info_mut().unwrap().current_set_id = + Some(TEST_GRANDPA_SET_ID); + data + } + fn all_finality_confirmation_pre_dispatch_data() -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: CallInfo::AllFinalityAndMsgs( SubmitFinalityProofInfo { block_number: 200, + current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, }, @@ -1153,12 +1244,20 @@ mod tests { } } + fn all_finality_confirmation_pre_dispatch_data_ex() -> PreDispatchData { + let mut data = all_finality_confirmation_pre_dispatch_data(); + data.call_info.submit_finality_proof_info_mut().unwrap().current_set_id = + Some(TEST_GRANDPA_SET_ID); + data + } + fn relay_finality_pre_dispatch_data() -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: CallInfo::RelayFinalityAndMsgs( SubmitFinalityProofInfo { block_number: 200, + current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, }, @@ -1177,12 +1276,20 @@ mod tests { } } + fn relay_finality_pre_dispatch_data_ex() -> PreDispatchData { + let mut data = relay_finality_pre_dispatch_data(); + data.call_info.submit_finality_proof_info_mut().unwrap().current_set_id = + Some(TEST_GRANDPA_SET_ID); + data + } + fn relay_finality_confirmation_pre_dispatch_data() -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: CallInfo::RelayFinalityAndMsgs( SubmitFinalityProofInfo { block_number: 200, + current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, }, @@ -1197,6 +1304,13 @@ mod tests { } } + fn relay_finality_confirmation_pre_dispatch_data_ex() -> PreDispatchData { + let mut data = relay_finality_confirmation_pre_dispatch_data(); + data.call_info.submit_finality_proof_info_mut().unwrap().current_set_id = + Some(TEST_GRANDPA_SET_ID); + data + } + fn parachain_finality_pre_dispatch_data() -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), @@ -1393,6 +1507,10 @@ mod tests { run_validate(all_finality_and_delivery_batch_call(200, 200, 200)), Ok(Default::default()), ); + assert_eq!( + run_validate(all_finality_and_delivery_batch_call_ex(200, 200, 200)), + Ok(Default::default()), + ); // message confirmation validation is passing assert_eq!( run_validate_ignore_priority(message_confirmation_call(200)), @@ -1410,6 +1528,12 @@ mod tests { )), Ok(Default::default()), ); + assert_eq!( + run_validate_ignore_priority(all_finality_and_confirmation_batch_call_ex( + 200, 200, 200 + )), + Ok(Default::default()), + ); }); } @@ -1500,12 +1624,24 @@ mod tests { run_validate_ignore_priority(all_finality_and_delivery_batch_call(200, 200, 200)), Ok(ValidTransaction::default()), ); + assert_eq!( + run_validate_ignore_priority(all_finality_and_delivery_batch_call_ex( + 200, 200, 200 + )), + Ok(ValidTransaction::default()), + ); assert_eq!( run_validate_ignore_priority(all_finality_and_confirmation_batch_call( 200, 200, 200 )), Ok(ValidTransaction::default()), ); + assert_eq!( + run_validate_ignore_priority(all_finality_and_confirmation_batch_call_ex( + 200, 200, 200 + )), + Ok(ValidTransaction::default()), + ); }); } @@ -1518,11 +1654,19 @@ mod tests { run_pre_dispatch(all_finality_and_delivery_batch_call(100, 200, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_delivery_batch_call_ex(100, 200, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_validate(all_finality_and_delivery_batch_call(100, 200, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_validate(all_finality_and_delivery_batch_call_ex(100, 200, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); }); } @@ -1535,10 +1679,18 @@ mod tests { run_pre_dispatch(all_finality_and_delivery_batch_call(101, 100, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_delivery_batch_call_ex(101, 100, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_validate(all_finality_and_delivery_batch_call(101, 100, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_validate(all_finality_and_delivery_batch_call_ex(101, 100, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_pre_dispatch(parachain_finality_and_delivery_batch_call(100, 200)), @@ -1560,19 +1712,35 @@ mod tests { run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 100)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_delivery_batch_call_ex(200, 200, 100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_pre_dispatch(all_finality_and_confirmation_batch_call(200, 200, 100)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_confirmation_batch_call_ex(200, 200, 100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_validate(all_finality_and_delivery_batch_call(200, 200, 100)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_validate(all_finality_and_delivery_batch_call_ex(200, 200, 100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_validate(all_finality_and_confirmation_batch_call(200, 200, 100)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_validate(all_finality_and_confirmation_batch_call_ex(200, 200, 100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_pre_dispatch(parachain_finality_and_delivery_batch_call(200, 100)), @@ -1609,10 +1777,18 @@ mod tests { run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_delivery_batch_call_ex(200, 200, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), + ); assert_eq!( run_pre_dispatch(all_finality_and_confirmation_batch_call(200, 200, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_confirmation_batch_call_ex(200, 200, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), + ); }); } @@ -1631,10 +1807,18 @@ mod tests { run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_delivery_batch_call_ex(200, 200, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), + ); assert_eq!( run_pre_dispatch(all_finality_and_confirmation_batch_call(200, 200, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_confirmation_batch_call_ex(200, 200, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), + ); assert_eq!( run_pre_dispatch(parachain_finality_and_delivery_batch_call(200, 200)), @@ -1662,10 +1846,18 @@ mod tests { run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_delivery_batch_call_ex(200, 200, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), + ); assert_eq!( run_pre_dispatch(all_finality_and_confirmation_batch_call(200, 200, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_confirmation_batch_call_ex(200, 200, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), + ); assert_eq!( run_pre_dispatch(parachain_finality_and_delivery_batch_call(200, 200)), @@ -1696,10 +1888,18 @@ mod tests { run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 200)), Ok(Some(all_finality_pre_dispatch_data())), ); + assert_eq!( + run_pre_dispatch(all_finality_and_delivery_batch_call_ex(200, 200, 200)), + Ok(Some(all_finality_pre_dispatch_data_ex())), + ); assert_eq!( run_pre_dispatch(all_finality_and_confirmation_batch_call(200, 200, 200)), Ok(Some(all_finality_confirmation_pre_dispatch_data())), ); + assert_eq!( + run_pre_dispatch(all_finality_and_confirmation_batch_call_ex(200, 200, 200)), + Ok(Some(all_finality_confirmation_pre_dispatch_data_ex())), + ); }); } @@ -2126,6 +2326,12 @@ mod tests { ), Ok(None), ); + assert_eq!( + TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call( + &all_finality_and_delivery_batch_call_ex(200, 200, 200) + ), + Ok(None), + ); // relay + parachain + message confirmation calls batch is ignored assert_eq!( @@ -2134,6 +2340,12 @@ mod tests { ), Ok(None), ); + assert_eq!( + TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call( + &all_finality_and_confirmation_batch_call_ex(200, 200, 200) + ), + Ok(None), + ); // parachain + message delivery call batch is ignored assert_eq!( @@ -2158,6 +2370,12 @@ mod tests { ), Ok(Some(relay_finality_pre_dispatch_data().call_info)), ); + assert_eq!( + TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call( + &relay_finality_and_delivery_batch_call_ex(200, 200) + ), + Ok(Some(relay_finality_pre_dispatch_data_ex().call_info)), + ); // relay + message confirmation call batch is accepted assert_eq!( @@ -2166,6 +2384,12 @@ mod tests { ), Ok(Some(relay_finality_confirmation_pre_dispatch_data().call_info)), ); + assert_eq!( + TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call( + &relay_finality_and_confirmation_batch_call_ex(200, 200) + ), + Ok(Some(relay_finality_confirmation_pre_dispatch_data_ex().call_info)), + ); // message delivery call batch is accepted assert_eq!( @@ -2194,11 +2418,19 @@ mod tests { run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call(100, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call_ex(100, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_grandpa_validate(relay_finality_and_delivery_batch_call(100, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_grandpa_validate(relay_finality_and_delivery_batch_call_ex(100, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); }); } @@ -2211,19 +2443,35 @@ mod tests { run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call(200, 100)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call_ex(200, 100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_grandpa_pre_dispatch(relay_finality_and_confirmation_batch_call(200, 100)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_grandpa_pre_dispatch(relay_finality_and_confirmation_batch_call_ex(200, 100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_grandpa_validate(relay_finality_and_delivery_batch_call(200, 100)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_grandpa_validate(relay_finality_and_delivery_batch_call_ex(200, 100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_grandpa_validate(relay_finality_and_confirmation_batch_call(200, 100)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_grandpa_validate(relay_finality_and_confirmation_batch_call_ex(200, 100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_grandpa_pre_dispatch(message_delivery_call(100)), @@ -2254,19 +2502,35 @@ mod tests { run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call(200, 200)), Ok(Some(relay_finality_pre_dispatch_data()),) ); + assert_eq!( + run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call_ex(200, 200)), + Ok(Some(relay_finality_pre_dispatch_data_ex()),) + ); assert_eq!( run_grandpa_pre_dispatch(relay_finality_and_confirmation_batch_call(200, 200)), Ok(Some(relay_finality_confirmation_pre_dispatch_data())), ); + assert_eq!( + run_grandpa_pre_dispatch(relay_finality_and_confirmation_batch_call_ex(200, 200)), + Ok(Some(relay_finality_confirmation_pre_dispatch_data_ex())), + ); assert_eq!( run_grandpa_validate(relay_finality_and_delivery_batch_call(200, 200)), Ok(Default::default()), ); + assert_eq!( + run_grandpa_validate(relay_finality_and_delivery_batch_call_ex(200, 200)), + Ok(Default::default()), + ); assert_eq!( run_grandpa_validate(relay_finality_and_confirmation_batch_call(200, 200)), Ok(Default::default()), ); + assert_eq!( + run_grandpa_validate(relay_finality_and_confirmation_batch_call_ex(200, 200)), + Ok(Default::default()), + ); assert_eq!( run_grandpa_pre_dispatch(message_delivery_call(200)), diff --git a/bridges/modules/grandpa/README.md b/bridges/modules/grandpa/README.md index 27b4d2389c4..43ee5c316d1 100644 --- a/bridges/modules/grandpa/README.md +++ b/bridges/modules/grandpa/README.md @@ -38,11 +38,11 @@ There are two main things in GRANDPA that help building light clients: ## Pallet Operations -The main entrypoint of the pallet is the `submit_finality_proof` call. It has two arguments - the finalized -headers and associated GRANDPA justification. The call simply verifies the justification using current -validators set and checks if header is better than the previous best header. If both checks are passed, the -header (only its useful fields) is inserted into the runtime storage and may be used by other pallets to verify -storage proofs. +The main entrypoint of the pallet is the `submit_finality_proof_ex` call. It has three arguments - the finalized +headers, associated GRANDPA justification and ID of the authority set that has generated this justification. The +call simply verifies the justification using current validators set and checks if header is better than the +previous best header. If both checks are passed, the header (only its useful fields) is inserted into the runtime +storage and may be used by other pallets to verify storage proofs. The submitter pays regular fee for submitting all headers, except for the mandatory header. Since it is required for the pallet operations, submitting such header is free. So if you're ok with session-length diff --git a/bridges/modules/grandpa/src/benchmarking.rs b/bridges/modules/grandpa/src/benchmarking.rs index 182b2f56eb1..11033373ce4 100644 --- a/bridges/modules/grandpa/src/benchmarking.rs +++ b/bridges/modules/grandpa/src/benchmarking.rs @@ -16,8 +16,9 @@ //! Benchmarks for the GRANDPA Pallet. //! -//! The main dispatchable for the GRANDPA pallet is `submit_finality_proof`, so these benchmarks are -//! based around that. There are to main factors which affect finality proof verification: +//! The main dispatchable for the GRANDPA pallet is `submit_finality_proof_ex`. Our benchmarks +//! are based around `submit_finality_proof`, though - from weight PoV they are the same calls. +//! There are to main factors which affect finality proof verification: //! //! 1. The number of `votes-ancestries` in the justification //! 2. The number of `pre-commits` in the justification diff --git a/bridges/modules/grandpa/src/call_ext.rs b/bridges/modules/grandpa/src/call_ext.rs index c1585020be1..e3c778b480b 100644 --- a/bridges/modules/grandpa/src/call_ext.rs +++ b/bridges/modules/grandpa/src/call_ext.rs @@ -14,7 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . -use crate::{weights::WeightInfo, BridgedBlockNumber, BridgedHeader, Config, Error, Pallet}; +use crate::{ + weights::WeightInfo, BridgedBlockNumber, BridgedHeader, Config, CurrentAuthoritySet, Error, + Pallet, +}; use bp_header_chain::{ justification::GrandpaJustification, max_expected_submit_finality_proof_arguments_size, ChainWithGrandpa, GrandpaConsensusLogReader, @@ -22,6 +25,7 @@ use bp_header_chain::{ use bp_runtime::{BlockNumberOf, OwnedBridgeModule}; use codec::Encode; use frame_support::{dispatch::CallableCallFor, traits::IsSubType, weights::Weight}; +use sp_consensus_grandpa::SetId; use sp_runtime::{ traits::{Header, Zero}, transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, @@ -33,6 +37,9 @@ use sp_runtime::{ pub struct SubmitFinalityProofInfo { /// Number of the finality target. pub block_number: N, + /// An identifier of the validators set that has signed the submitted justification. + /// It might be `None` if deprecated version of the `submit_finality_proof` is used. + pub current_set_id: Option, /// Extra weight that we assume is included in the call. /// /// We have some assumptions about headers and justifications of the bridged chain. @@ -61,9 +68,11 @@ pub struct SubmitFinalityProofHelper, I: 'static> { impl, I: 'static> SubmitFinalityProofHelper { /// Check that the GRANDPA head provided by the `SubmitFinalityProof` is better than the best - /// one we know. + /// one we know. Additionally, checks if `current_set_id` matches the current authority set + /// id, if specified. pub fn check_obsolete( finality_target: BlockNumberOf, + current_set_id: Option, ) -> Result<(), Error> { let best_finalized = crate::BestFinalized::::get().ok_or_else(|| { log::trace!( @@ -85,6 +94,20 @@ impl, I: 'static> SubmitFinalityProofHelper { return Err(Error::::OldHeader) } + if let Some(current_set_id) = current_set_id { + let actual_set_id = >::get().set_id; + if current_set_id != actual_set_id { + log::trace!( + target: crate::LOG_TARGET, + "Cannot finalize header signed by unknown authority set: bundled {:?}, best {:?}", + current_set_id, + actual_set_id, + ); + + return Err(Error::::InvalidAuthoritySetId) + } + } + Ok(()) } @@ -111,6 +134,18 @@ pub trait CallSubType, I: 'static>: return Some(submit_finality_proof_info_from_args::( finality_target, justification, + None, + )) + } else if let Some(crate::Call::::submit_finality_proof_ex { + finality_target, + justification, + current_set_id, + }) = self.is_sub_type() + { + return Some(submit_finality_proof_info_from_args::( + finality_target, + justification, + Some(*current_set_id), )) } @@ -133,7 +168,10 @@ pub trait CallSubType, I: 'static>: return InvalidTransaction::Call.into() } - match SubmitFinalityProofHelper::::check_obsolete(finality_target.block_number) { + match SubmitFinalityProofHelper::::check_obsolete( + finality_target.block_number, + finality_target.current_set_id, + ) { Ok(_) => Ok(ValidTransaction::default()), Err(Error::::OldHeader) => InvalidTransaction::Stale.into(), Err(_) => InvalidTransaction::Call.into(), @@ -150,6 +188,7 @@ impl, I: 'static> CallSubType for T::RuntimeCall where pub(crate) fn submit_finality_proof_info_from_args, I: 'static>( finality_target: &BridgedHeader, justification: &GrandpaJustification>, + current_set_id: Option, ) -> SubmitFinalityProofInfo> { let block_number = *finality_target.number(); @@ -191,7 +230,7 @@ pub(crate) fn submit_finality_proof_info_from_args, I: 'static>( ); let extra_size = actual_call_size.saturating_sub(max_expected_call_size); - SubmitFinalityProofInfo { block_number, extra_weight, extra_size } + SubmitFinalityProofInfo { block_number, current_set_id, extra_weight, extra_size } } #[cfg(test)] @@ -199,20 +238,24 @@ mod tests { use crate::{ call_ext::CallSubType, mock::{run_test, test_header, RuntimeCall, TestBridgedChain, TestNumber, TestRuntime}, - BestFinalized, Config, PalletOperatingMode, WeightInfo, + BestFinalized, Config, CurrentAuthoritySet, PalletOperatingMode, StoredAuthoritySet, + SubmitFinalityProofInfo, WeightInfo, }; use bp_header_chain::ChainWithGrandpa; use bp_runtime::{BasicOperatingMode, HeaderId}; use bp_test_utils::{ make_default_justification, make_justification_for_header, JustificationGeneratorParams, + TEST_GRANDPA_SET_ID, }; use frame_support::weights::Weight; use sp_runtime::{testing::DigestItem, traits::Header as _, SaturatedConversion}; fn validate_block_submit(num: TestNumber) -> bool { - let bridge_grandpa_call = crate::Call::::submit_finality_proof { + let bridge_grandpa_call = crate::Call::::submit_finality_proof_ex { finality_target: Box::new(test_header(num)), justification: make_default_justification(&test_header(num)), + // not initialized => zero + current_set_id: 0, }; RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa( bridge_grandpa_call, @@ -256,6 +299,18 @@ mod tests { }); } + #[test] + fn extension_rejects_new_header_if_set_id_is_invalid() { + run_test(|| { + // when set id is different from the passed one => tx is rejected + sync_to_header_10(); + let next_set = StoredAuthoritySet::::try_new(vec![], 0x42).unwrap(); + CurrentAuthoritySet::::put(next_set); + + assert!(!validate_block_submit(15)); + }); + } + #[test] fn extension_accepts_new_header() { run_test(|| { @@ -266,6 +321,42 @@ mod tests { }); } + #[test] + fn submit_finality_proof_info_is_parsed() { + // when `submit_finality_proof` is used, `current_set_id` is set to `None` + let deprecated_call = + RuntimeCall::Grandpa(crate::Call::::submit_finality_proof { + finality_target: Box::new(test_header(42)), + justification: make_default_justification(&test_header(42)), + }); + assert_eq!( + deprecated_call.submit_finality_proof_info(), + Some(SubmitFinalityProofInfo { + block_number: 42, + current_set_id: None, + extra_weight: Weight::zero(), + extra_size: 0, + }) + ); + + // when `submit_finality_proof_ex` is used, `current_set_id` is set to `Some` + let deprecated_call = + RuntimeCall::Grandpa(crate::Call::::submit_finality_proof_ex { + finality_target: Box::new(test_header(42)), + justification: make_default_justification(&test_header(42)), + current_set_id: 777, + }); + assert_eq!( + deprecated_call.submit_finality_proof_info(), + Some(SubmitFinalityProofInfo { + block_number: 42, + current_set_id: Some(777), + extra_weight: Weight::zero(), + extra_size: 0, + }) + ); + } + #[test] fn extension_returns_correct_extra_size_if_call_arguments_are_too_large() { // when call arguments are below our limit => no refund @@ -275,9 +366,10 @@ mod tests { ..Default::default() }; let small_justification = make_justification_for_header(justification_params); - let small_call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof { + let small_call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex { finality_target: Box::new(small_finality_target), justification: small_justification, + current_set_id: TEST_GRANDPA_SET_ID, }); assert_eq!(small_call.submit_finality_proof_info().unwrap().extra_size, 0); @@ -291,9 +383,10 @@ mod tests { ..Default::default() }; let large_justification = make_justification_for_header(justification_params); - let large_call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof { + let large_call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex { finality_target: Box::new(large_finality_target), justification: large_justification, + current_set_id: TEST_GRANDPA_SET_ID, }); assert_ne!(large_call.submit_finality_proof_info().unwrap().extra_size, 0); } @@ -309,9 +402,10 @@ mod tests { // when there are `REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY` headers => no refund let justification = make_justification_for_header(justification_params.clone()); - let call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof { + let call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex { finality_target: Box::new(finality_target.clone()), justification, + current_set_id: TEST_GRANDPA_SET_ID, }); assert_eq!(call.submit_finality_proof_info().unwrap().extra_weight, Weight::zero()); @@ -322,9 +416,10 @@ mod tests { justification.commit.precommits.len().saturated_into(), justification.votes_ancestries.len().saturated_into(), ); - let call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof { + let call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex { finality_target: Box::new(finality_target), justification, + current_set_id: TEST_GRANDPA_SET_ID, }); assert_eq!(call.submit_finality_proof_info().unwrap().extra_weight, call_weight); } diff --git a/bridges/modules/grandpa/src/lib.rs b/bridges/modules/grandpa/src/lib.rs index f58db2481ad..ce2c47da954 100644 --- a/bridges/modules/grandpa/src/lib.rs +++ b/bridges/modules/grandpa/src/lib.rs @@ -151,7 +151,86 @@ pub mod pallet { #[pallet::call] impl, I: 'static> Pallet { - /// Verify a target header is finalized according to the given finality proof. + /// This call is deprecated and will be removed around May 2024. Use the + /// `submit_finality_proof_ex` instead. Semantically, this call is an equivalent of the + /// `submit_finality_proof_ex` call without current authority set id check. + #[pallet::call_index(0)] + #[pallet::weight(::submit_finality_proof( + justification.commit.precommits.len().saturated_into(), + justification.votes_ancestries.len().saturated_into(), + ))] + #[allow(deprecated)] + #[deprecated( + note = "`submit_finality_proof` will be removed in May 2024. Use `submit_finality_proof_ex` instead." + )] + pub fn submit_finality_proof( + origin: OriginFor, + finality_target: Box>, + justification: GrandpaJustification>, + ) -> DispatchResultWithPostInfo { + Self::submit_finality_proof_ex( + origin, + finality_target, + justification, + // the `submit_finality_proof_ex` also reads this value, but it is done from the + // cache, so we don't treat it as an additional db access + >::get().set_id, + ) + } + + /// Bootstrap the bridge pallet with an initial header and authority set from which to sync. + /// + /// The initial configuration provided does not need to be the genesis header of the bridged + /// chain, it can be any arbitrary header. You can also provide the next scheduled set + /// change if it is already know. + /// + /// This function is only allowed to be called from a trusted origin and writes to storage + /// with practically no checks in terms of the validity of the data. It is important that + /// you ensure that valid data is being passed in. + #[pallet::call_index(1)] + #[pallet::weight((T::DbWeight::get().reads_writes(2, 5), DispatchClass::Operational))] + pub fn initialize( + origin: OriginFor, + init_data: super::InitializationData>, + ) -> DispatchResultWithPostInfo { + Self::ensure_owner_or_root(origin)?; + + let init_allowed = !>::exists(); + ensure!(init_allowed, >::AlreadyInitialized); + initialize_bridge::(init_data.clone())?; + + log::info!( + target: LOG_TARGET, + "Pallet has been initialized with the following parameters: {:?}", + init_data + ); + + Ok(().into()) + } + + /// Change `PalletOwner`. + /// + /// May only be called either by root, or by `PalletOwner`. + #[pallet::call_index(2)] + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_owner(origin: OriginFor, new_owner: Option) -> DispatchResult { + >::set_owner(origin, new_owner) + } + + /// Halt or resume all pallet operations. + /// + /// May only be called either by root, or by `PalletOwner`. + #[pallet::call_index(3)] + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_operating_mode( + origin: OriginFor, + operating_mode: BasicOperatingMode, + ) -> DispatchResult { + >::set_operating_mode(origin, operating_mode) + } + + /// Verify a target header is finalized according to the given finality proof. The proof + /// is assumed to be signed by GRANDPA authorities set with `current_set_id` id. /// /// It will use the underlying storage pallet to fetch information about the current /// authorities and best finalized header in order to verify that the header is finalized. @@ -165,18 +244,22 @@ pub mod pallet { /// /// - the pallet knows better header than the `finality_target`; /// + /// - the id of best GRANDPA authority set, known to the pallet is not equal to the + /// `current_set_id`; + /// /// - verification is not optimized or invalid; /// /// - header contains forced authorities set change or change with non-zero delay. - #[pallet::call_index(0)] + #[pallet::call_index(4)] #[pallet::weight(::submit_finality_proof( justification.commit.precommits.len().saturated_into(), justification.votes_ancestries.len().saturated_into(), ))] - pub fn submit_finality_proof( + pub fn submit_finality_proof_ex( origin: OriginFor, finality_target: Box>, justification: GrandpaJustification>, + current_set_id: sp_consensus_grandpa::SetId, ) -> DispatchResultWithPostInfo { Self::ensure_not_halted().map_err(Error::::BridgeModule)?; ensure_signed(origin)?; @@ -188,7 +271,9 @@ pub mod pallet { finality_target ); - SubmitFinalityProofHelper::::check_obsolete(number)?; + // it checks whether the `number` is better than the current best block number + // and whether the `current_set_id` matches the best known set id + SubmitFinalityProofHelper::::check_obsolete(number, Some(current_set_id))?; let authority_set = >::get(); let unused_proof_size = authority_set.unused_proof_size(); @@ -202,7 +287,7 @@ pub mod pallet { // if we have seen too many mandatory headers in this block, we don't want to refund Self::free_mandatory_headers_remaining() > 0 && // if arguments out of expected bounds, we don't want to refund - submit_finality_proof_info_from_args::(&finality_target, &justification) + submit_finality_proof_info_from_args::(&finality_target, &justification, Some(current_set_id)) .fits_limits(); if may_refund_call_fee { FreeMandatoryHeadersRemaining::::mutate(|count| { @@ -248,57 +333,6 @@ pub mod pallet { Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee }) } - - /// Bootstrap the bridge pallet with an initial header and authority set from which to sync. - /// - /// The initial configuration provided does not need to be the genesis header of the bridged - /// chain, it can be any arbitrary header. You can also provide the next scheduled set - /// change if it is already know. - /// - /// This function is only allowed to be called from a trusted origin and writes to storage - /// with practically no checks in terms of the validity of the data. It is important that - /// you ensure that valid data is being passed in. - #[pallet::call_index(1)] - #[pallet::weight((T::DbWeight::get().reads_writes(2, 5), DispatchClass::Operational))] - pub fn initialize( - origin: OriginFor, - init_data: super::InitializationData>, - ) -> DispatchResultWithPostInfo { - Self::ensure_owner_or_root(origin)?; - - let init_allowed = !>::exists(); - ensure!(init_allowed, >::AlreadyInitialized); - initialize_bridge::(init_data.clone())?; - - log::info!( - target: LOG_TARGET, - "Pallet has been initialized with the following parameters: {:?}", - init_data - ); - - Ok(().into()) - } - - /// Change `PalletOwner`. - /// - /// May only be called either by root, or by `PalletOwner`. - #[pallet::call_index(2)] - #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] - pub fn set_owner(origin: OriginFor, new_owner: Option) -> DispatchResult { - >::set_owner(origin, new_owner) - } - - /// Halt or resume all pallet operations. - /// - /// May only be called either by root, or by `PalletOwner`. - #[pallet::call_index(3)] - #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] - pub fn set_operating_mode( - origin: OriginFor, - operating_mode: BasicOperatingMode, - ) -> DispatchResult { - >::set_operating_mode(origin, operating_mode) - } } /// Number mandatory headers that we may accept in the current block for free (returning @@ -436,6 +470,9 @@ pub mod pallet { TooManyAuthoritiesInSet, /// Error generated by the `OwnedBridgeModule` trait. BridgeModule(bp_runtime::OwnedBridgeModuleError), + /// The `current_set_id` argument of the `submit_finality_proof_ex` doesn't match + /// the id of the current set, known to the pallet. + InvalidAuthoritySetId, } /// Check the given header for a GRANDPA scheduled authority set change. If a change @@ -663,6 +700,7 @@ mod tests { use bp_test_utils::{ authority_list, generate_owned_bridge_module_tests, make_default_justification, make_justification_for_header, JustificationGeneratorParams, ALICE, BOB, + TEST_GRANDPA_SET_ID, }; use codec::Encode; use frame_support::{ @@ -693,7 +731,7 @@ mod tests { let init_data = InitializationData { header: Box::new(genesis), authority_list: authority_list(), - set_id: 1, + set_id: TEST_GRANDPA_SET_ID, operating_mode: BasicOperatingMode::Normal, }; @@ -704,10 +742,11 @@ mod tests { let header = test_header(header.into()); let justification = make_default_justification(&header); - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), justification, + TEST_GRANDPA_SET_ID, ) } @@ -722,10 +761,11 @@ mod tests { ..Default::default() }); - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), justification, + set_id, ) } @@ -749,10 +789,11 @@ mod tests { ..Default::default() }); - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), justification, + set_id, ) } @@ -955,17 +996,30 @@ mod tests { let header = test_header(1); - let params = - JustificationGeneratorParams:: { set_id: 2, ..Default::default() }; + let next_set_id = 2; + let params = JustificationGeneratorParams:: { + set_id: next_set_id, + ..Default::default() + }; let justification = make_justification_for_header(params); assert_err!( - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( + RuntimeOrigin::signed(1), + Box::new(header.clone()), + justification.clone(), + TEST_GRANDPA_SET_ID, + ), + >::InvalidJustification + ); + assert_err!( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), justification, + next_set_id, ), - >::InvalidJustification + >::InvalidAuthoritySetId ); }) } @@ -980,10 +1034,11 @@ mod tests { justification.round = 42; assert_err!( - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), justification, + TEST_GRANDPA_SET_ID, ), >::InvalidJustification ); @@ -1009,10 +1064,11 @@ mod tests { let justification = make_default_justification(&header); assert_err!( - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), justification, + TEST_GRANDPA_SET_ID, ), >::InvalidAuthoritySet ); @@ -1047,10 +1103,11 @@ mod tests { let justification = make_default_justification(&header); // Let's import our test header - let result = Pallet::::submit_finality_proof( + let result = Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header.clone()), justification.clone(), + TEST_GRANDPA_SET_ID, ); assert_ok!(result); assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::No); @@ -1109,10 +1166,11 @@ mod tests { // without large digest item ^^^ the relayer would have paid zero transaction fee // (`Pays::No`) - let result = Pallet::::submit_finality_proof( + let result = Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header.clone()), justification, + TEST_GRANDPA_SET_ID, ); assert_ok!(result); assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes); @@ -1140,10 +1198,11 @@ mod tests { // without many headers in votes ancestries ^^^ the relayer would have paid zero // transaction fee (`Pays::No`) - let result = Pallet::::submit_finality_proof( + let result = Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header.clone()), justification, + TEST_GRANDPA_SET_ID, ); assert_ok!(result); assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes); @@ -1169,10 +1228,11 @@ mod tests { // Should not be allowed to import this header assert_err!( - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), - justification + justification, + TEST_GRANDPA_SET_ID, ), >::UnsupportedScheduledChange ); @@ -1194,10 +1254,11 @@ mod tests { // Should not be allowed to import this header assert_err!( - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), - justification + justification, + TEST_GRANDPA_SET_ID, ), >::UnsupportedScheduledChange ); @@ -1219,10 +1280,11 @@ mod tests { // Should not be allowed to import this header assert_err!( - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), - justification + justification, + TEST_GRANDPA_SET_ID, ), >::TooManyAuthoritiesInSet ); @@ -1283,10 +1345,11 @@ mod tests { let mut invalid_justification = make_default_justification(&header); invalid_justification.round = 42; - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), invalid_justification, + TEST_GRANDPA_SET_ID, ) }; @@ -1451,10 +1514,11 @@ mod tests { let justification = make_default_justification(&header); assert_noop!( - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::root(), Box::new(header), justification, + TEST_GRANDPA_SET_ID, ), DispatchError::BadOrigin, ); diff --git a/bridges/modules/parachains/src/lib.rs b/bridges/modules/parachains/src/lib.rs index 87c57e84622..1363a637604 100644 --- a/bridges/modules/parachains/src/lib.rs +++ b/bridges/modules/parachains/src/lib.rs @@ -731,6 +731,7 @@ pub(crate) mod tests { }; use bp_test_utils::{ authority_list, generate_owned_bridge_module_tests, make_default_justification, + TEST_GRANDPA_SET_ID, }; use frame_support::{ assert_noop, assert_ok, @@ -777,10 +778,11 @@ pub(crate) mod tests { let hash = header.hash(); let justification = make_default_justification(&header); assert_ok!( - pallet_bridge_grandpa::Pallet::::submit_finality_proof( + pallet_bridge_grandpa::Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), justification.clone(), + TEST_GRANDPA_SET_ID, ) ); diff --git a/bridges/primitives/header-chain/src/lib.rs b/bridges/primitives/header-chain/src/lib.rs index f5485aca1ee..84a6a881a83 100644 --- a/bridges/primitives/header-chain/src/lib.rs +++ b/bridges/primitives/header-chain/src/lib.rs @@ -244,6 +244,16 @@ pub enum BridgeGrandpaCall { /// All data, required to initialize the pallet. init_data: InitializationData
, }, + /// `pallet-bridge-grandpa::Call::submit_finality_proof_ex` + #[codec(index = 4)] + submit_finality_proof_ex { + /// The header that we are going to finalize. + finality_target: Box
, + /// Finality justification for the `finality_target`. + justification: justification::GrandpaJustification
, + /// An identifier of the validators set, that have signed the justification. + current_set_id: SetId, + }, } /// The `BridgeGrandpaCall` used by a chain. diff --git a/bridges/zombienet/helpers/chains/rococo-at-westend.js b/bridges/zombienet/helpers/chains/rococo-at-westend.js index eb9510e46f0..bcce3b3a303 100644 --- a/bridges/zombienet/helpers/chains/rococo-at-westend.js +++ b/bridges/zombienet/helpers/chains/rococo-at-westend.js @@ -1,6 +1,6 @@ module.exports = { - grandpaPalletName: "bridgeRococoGrandpa", - parachainsPalletName: "bridgeRococoParachains", - messagesPalletName: "bridgeRococoMessages", + grandpaPalletName: "bridgeRococoGrandpa", + parachainsPalletName: "bridgeRococoParachains", + messagesPalletName: "bridgeRococoMessages", bridgedBridgeHubParaId: 1013, } diff --git a/bridges/zombienet/helpers/chains/westend-at-rococo.js b/bridges/zombienet/helpers/chains/westend-at-rococo.js index 771a0778cb0..6a15b64a23b 100644 --- a/bridges/zombienet/helpers/chains/westend-at-rococo.js +++ b/bridges/zombienet/helpers/chains/westend-at-rococo.js @@ -1,6 +1,6 @@ module.exports = { - grandpaPalletName: "bridgeWestendGrandpa", - parachainsPalletName: "bridgeWestendParachains", - messagesPalletName: "bridgeWestendMessages", + grandpaPalletName: "bridgeWestendGrandpa", + parachainsPalletName: "bridgeWestendParachains", + messagesPalletName: "bridgeWestendMessages", bridgedBridgeHubParaId: 1002, } diff --git a/prdoc/pr_3225.prdoc b/prdoc/pr_3225.prdoc new file mode 100644 index 00000000000..1dec40ccfbe --- /dev/null +++ b/prdoc/pr_3225.prdoc @@ -0,0 +1,11 @@ +title: "Introduce submit_finality_proof_ex call to bridges GRANDPA pallet" + +doc: + - audience: Runtime Dev + description: | + New call has been added to pallet-bridge-grandpa: submit_finality_proof_ex. It should be + used instead of deprecated submit_finality_proof. submit_finality_proof will be removed + later. + +crates: + - name: "pallet-bridge-grandpa" -- GitLab From 402b64caf50b5fe3e0298950a15c5f1420a7e9c5 Mon Sep 17 00:00:00 2001 From: Koute Date: Tue, 6 Feb 2024 23:04:21 +0900 Subject: [PATCH 018/133] Build more runtimes targeting PolkaVM (#3209) This PR improves compatibility with RISC-V and PolkaVM, allowing more runtimes to successfully compile. In particular, it makes the following changes: - The `sp-mmr-primitives` and `sp-consensus-beefy` crates unconditionally required an `std`-only dependency; now they only require those dependencies when the `std` feature is actually enabled. (Our RISC-V target is, unlike WASM, a true `no_std` target where you can't accidentally use stuff from `std` anymore.) - One of our dependencies (the `bitvec` trace) uses a crate called `radium` which doesn't compile under RISC-V due to incomplete autodetection logic in their `build.rs` file. The good news is that this is already fixed in the newest upstream version of `radium`, and the newest version of `bitvec` uses it. The bad news is that the newest version of `bitvec` is not currently released on crates.io, so we can't use it. I've [created an issue](https://github.com/ferrilab/ferrilab/issues/5) asking for a new release, but in the meantime I forked the currently used `radium` 0.7, [fixed the faulty logic](https://github.com/paritytech/radium-0.7-fork/commit/ed66c8a294b138c67f93499644051d97d4c7fbda) and used cargo's patching capabilities to use it for the RISC-V runtime builds. This might be a little hacky, but it is the least intrusive way to fix the problem, doesn't affect WASM builds at all, and we can trivially remove it once a new `bitvec` is released. - The new runtimes are added to the CI to make sure their compilation doesn't break. --- .gitlab/pipeline/build.yml | 5 ++++- Cargo.lock | 6 +++--- Cargo.toml | 3 ++- substrate/primitives/consensus/beefy/Cargo.toml | 3 ++- .../primitives/merkle-mountain-range/Cargo.toml | 3 ++- substrate/utils/wasm-builder/src/wasm_project.rs | 12 ++++++++++++ 6 files changed, 25 insertions(+), 7 deletions(-) diff --git a/.gitlab/pipeline/build.yml b/.gitlab/pipeline/build.yml index 002206e328c..d9c67af7efd 100644 --- a/.gitlab/pipeline/build.yml +++ b/.gitlab/pipeline/build.yml @@ -329,13 +329,16 @@ build-linux-substrate: # - printf '\n# building node-template\n\n' # - ./scripts/ci/node-template-release.sh ./artifacts/substrate/substrate-node-template.tar.gz -build-minimal-runtime-polkavm: +build-runtimes-polkavm: stage: build extends: - .docker-env - .common-refs script: - SUBSTRATE_RUNTIME_TARGET=riscv cargo check -p minimal-runtime + - SUBSTRATE_RUNTIME_TARGET=riscv cargo check -p westend-runtime + - SUBSTRATE_RUNTIME_TARGET=riscv cargo check -p rococo-runtime + - SUBSTRATE_RUNTIME_TARGET=riscv cargo check -p polkadot-test-runtime .build-subkey: stage: build diff --git a/Cargo.lock b/Cargo.lock index 8e2551f7cc9..27a1efc3809 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13798,9 +13798,9 @@ dependencies = [ [[package]] name = "polkavm-linker" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc03593918a5890f96c276fb1e34ab77002bea1f9136cdcb55107c241011ab7" +checksum = "fdec1451cb18261d5d01de82acc15305e417fb59588cdcb3127d3dcc9672b925" dependencies = [ "gimli 0.28.0", "hashbrown 0.14.3", @@ -19712,7 +19712,7 @@ dependencies = [ "console", "filetime", "parity-wasm", - "polkavm-linker 0.8.1", + "polkavm-linker 0.8.2", "sp-maybe-compressed-blob", "strum 0.24.1", "tempfile", diff --git a/Cargo.toml b/Cargo.toml index e807171b24c..8cbfc7a23fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -533,7 +533,7 @@ extra-unused-type-parameters = { level = "allow", priority = 2 } # stylistic default_constructed_unit_structs = { level = "allow", priority = 2 } # stylistic [workspace.dependencies] -polkavm-linker = "0.8.1" +polkavm-linker = "0.8.2" polkavm-derive = "0.8.0" [profile.release] @@ -595,6 +595,7 @@ num-bigint = { opt-level = 3 } parking_lot = { opt-level = 3 } parking_lot_core = { opt-level = 3 } percent-encoding = { opt-level = 3 } +polkavm-linker = { opt-level = 3 } primitive-types = { opt-level = 3 } reed-solomon-novelpoly = { opt-level = 3 } ring = { opt-level = 3 } diff --git a/substrate/primitives/consensus/beefy/Cargo.toml b/substrate/primitives/consensus/beefy/Cargo.toml index 6953fdad53c..6232eca5ff8 100644 --- a/substrate/primitives/consensus/beefy/Cargo.toml +++ b/substrate/primitives/consensus/beefy/Cargo.toml @@ -27,7 +27,7 @@ sp-mmr-primitives = { path = "../../merkle-mountain-range", default-features = f sp-runtime = { path = "../../runtime", default-features = false } sp-std = { path = "../../std", default-features = false } strum = { version = "0.24.1", features = ["derive"], default-features = false } -lazy_static = "1.4.0" +lazy_static = { version = "1.4.0", optional = true } [dev-dependencies] array-bytes = "6.1" @@ -37,6 +37,7 @@ w3f-bls = { version = "0.1.3", features = ["std"] } default = ["std"] std = [ "codec/std", + "dep:lazy_static", "scale-info/std", "serde/std", "sp-api/std", diff --git a/substrate/primitives/merkle-mountain-range/Cargo.toml b/substrate/primitives/merkle-mountain-range/Cargo.toml index 59b48b1b839..729f458dd8d 100644 --- a/substrate/primitives/merkle-mountain-range/Cargo.toml +++ b/substrate/primitives/merkle-mountain-range/Cargo.toml @@ -25,7 +25,7 @@ sp-core = { path = "../core", default-features = false } sp-debug-derive = { path = "../debug-derive", default-features = false } sp-runtime = { path = "../runtime", default-features = false } sp-std = { path = "../std", default-features = false } -thiserror = "1.0" +thiserror = { version = "1.0", optional = true } [dev-dependencies] array-bytes = "6.1" @@ -34,6 +34,7 @@ array-bytes = "6.1" default = ["std"] std = [ "codec/std", + "dep:thiserror", "log/std", "mmr-lib/std", "scale-info/std", diff --git a/substrate/utils/wasm-builder/src/wasm_project.rs b/substrate/utils/wasm-builder/src/wasm_project.rs index 99e072f2682..bc8c9b3b4b3 100644 --- a/substrate/utils/wasm-builder/src/wasm_project.rs +++ b/substrate/utils/wasm-builder/src/wasm_project.rs @@ -462,6 +462,18 @@ fn create_project_cargo_toml( wasm_workspace_toml.insert("workspace".into(), Table::new().into()); + if target == RuntimeTarget::Riscv { + // This dependency currently doesn't compile under RISC-V, so patch it with our own fork. + // + // TODO: Remove this once a new version of `bitvec` (which uses a new version of `radium` + // which doesn't have this problem) is released on crates.io. + let patch = toml::toml! { + [crates-io] + radium = { git = "https://github.com/paritytech/radium-0.7-fork.git", rev = "a5da15a15c90fd169d661d206cf0db592487f52b" } + }; + wasm_workspace_toml.insert("patch".into(), patch.into()); + } + write_file_if_changed( wasm_workspace.join("Cargo.toml"), toml::to_string_pretty(&wasm_workspace_toml).expect("Wasm workspace toml is valid; qed"), -- GitLab From 8c1c99f07a4623c9454e40e6bc077c6ba4611e4e Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Tue, 6 Feb 2024 16:48:02 +0100 Subject: [PATCH 019/133] [pallet_xcm] Forgotten migration to XCMv4 + added `try-state` to the `pallet_xcm` (#3228) Relates to: https://github.com/paritytech/polkadot-sdk/issues/3214 ## TODO - [ ] backport to the `1.7.0` release --- .../assets/asset-hub-rococo/src/lib.rs | 2 + .../assets/asset-hub-westend/src/lib.rs | 2 + .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 2 + .../bridge-hubs/bridge-hub-westend/src/lib.rs | 2 + .../collectives-westend/src/lib.rs | 2 + .../contracts/contracts-rococo/src/lib.rs | 2 + .../coretime/coretime-rococo/src/lib.rs | 6 +- .../coretime/coretime-westend/src/lib.rs | 5 +- .../runtimes/people/people-rococo/src/lib.rs | 5 +- .../runtimes/people/people-westend/src/lib.rs | 5 +- polkadot/runtime/rococo/src/lib.rs | 3 + polkadot/runtime/westend/src/lib.rs | 2 + polkadot/xcm/pallet-xcm/src/lib.rs | 55 ++++++++++++ polkadot/xcm/pallet-xcm/src/migration.rs | 17 +++- polkadot/xcm/pallet-xcm/src/tests/mod.rs | 85 ++++++++++++++++++- prdoc/pr_3228.prdoc | 14 +++ 16 files changed, 202 insertions(+), 7 deletions(-) create mode 100644 prdoc/pr_3228.prdoc diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index 2c111c0efac..553c70bc554 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -968,6 +968,8 @@ pub type Migrations = ( InitStorageVersions, // unreleased cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, ); /// Migration to initialize storage versions for pallets added after genesis. diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index aa319d9f892..409a5f1177d 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -949,6 +949,8 @@ pub type Migrations = ( DeleteUndecodableStorage, // unreleased cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, ); /// Asset Hub Westend has some undecodable storage, delete it. diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 2be5652c434..0d4c1defa37 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -152,6 +152,8 @@ pub type Migrations = ( ConstU32, ConstU32, >, + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, ); /// Migration to initialize storage versions for pallets added after genesis. diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index edfb08f163b..6188b0acd7d 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -124,6 +124,8 @@ pub type Migrations = ( InitStorageVersions, // unreleased cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, ); /// Migration to initialize storage versions for pallets added after genesis. diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs index 6016955221c..d245576494f 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs @@ -727,6 +727,8 @@ type Migrations = ( pallet_collator_selection::migration::v1::MigrateToV1, // unreleased cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, ); /// Executive: handles dispatch to the various modules. diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs index b8203701d1a..7d0dd43ea88 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs @@ -103,6 +103,8 @@ pub type Migrations = ( pallet_contracts::Migration, // unreleased cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, ); type EventRecord = frame_system::EventRecord< diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs index 1e880cf6cce..8b952cfba22 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs @@ -106,7 +106,11 @@ pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; /// Migrations to apply on runtime upgrade. -pub type Migrations = (cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4,); +pub type Migrations = ( + cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, +); /// Executive: handles dispatch to the various modules. pub type Executive = frame_executive::Executive< diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs index 55fe3ba9fbf..d8087c05b73 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs @@ -97,7 +97,10 @@ pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; /// Migrations to apply on runtime upgrade. -pub type Migrations = (); +pub type Migrations = ( + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, +); /// Executive: handles dispatch to the various modules. pub type Executive = frame_executive::Executive< diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs index 790be31da03..652485a69fd 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -100,7 +100,10 @@ pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; /// Migrations to apply on runtime upgrade. -pub type Migrations = (); +pub type Migrations = ( + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, +); /// Executive: handles dispatch to the various modules. pub type Executive = frame_executive::Executive< diff --git a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs index 24e203c9f01..b24f6767a71 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs @@ -100,7 +100,10 @@ pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; /// Migrations to apply on runtime upgrade. -pub type Migrations = (); +pub type Migrations = ( + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, +); /// Executive: handles dispatch to the various modules. pub type Executive = frame_executive::Executive< diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 79c848221fb..184d0338eb0 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -1662,6 +1662,9 @@ pub mod migrations { parachains_configuration::migration::v11::MigrateToV11, // This needs to come after the `parachains_configuration` above as we are reading the configuration. coretime::migration::MigrateToCoretime, + + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, ); } diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index cd9e57a9bdf..7305684a0d9 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -1656,6 +1656,8 @@ pub mod migrations { // Migrate Identity pallet for Usernames pallet_identity::migration::versioned::V0ToV1, parachains_configuration::migration::v11::MigrateToV11, + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, ); } diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs index 55154198a9b..586c275ae98 100644 --- a/polkadot/xcm/pallet-xcm/src/lib.rs +++ b/polkadot/xcm/pallet-xcm/src/lib.rs @@ -62,6 +62,9 @@ use xcm_executor::{ AssetsInHolding, }; +#[cfg(any(feature = "try-runtime", test))] +use sp_runtime::TryRuntimeError; + pub trait WeightInfo { fn send() -> Weight; fn teleport_assets() -> Weight; @@ -464,6 +467,8 @@ pub mod pallet { FeesPaid { paying: Location, fees: Assets }, /// Some assets have been claimed from an asset trap AssetsClaimed { hash: H256, origin: Location, assets: VersionedAssets }, + /// A XCM version migration finished. + VersionMigrationFinished { version: XcmVersion }, } #[pallet::origin] @@ -765,6 +770,9 @@ pub mod pallet { // Consume 10% of block at most let max_weight = T::BlockWeights::get().max_block / 10; let (w, maybe_migration) = Self::check_xcm_version_change(migration, max_weight); + if maybe_migration.is_none() { + Self::deposit_event(Event::VersionMigrationFinished { version: XCM_VERSION }); + } CurrentMigration::::set(maybe_migration); weight_used.saturating_accrue(w); } @@ -791,6 +799,11 @@ pub mod pallet { } weight_used } + + #[cfg(feature = "try-runtime")] + fn try_state(_n: BlockNumberFor) -> Result<(), TryRuntimeError> { + Self::do_try_state() + } } pub mod migrations { @@ -2387,6 +2400,48 @@ impl Pallet { Self::deposit_event(Event::FeesPaid { paying: location, fees: assets }); Ok(()) } + + /// Ensure the correctness of the state of this pallet. + /// + /// This should be valid before and after each state transition of this pallet. + /// + /// ## Invariants + /// + /// All entries stored in the `SupportedVersion` / `VersionNotifiers` / `VersionNotifyTargets` + /// need to be migrated to the `XCM_VERSION`. If they are not, then `CurrentMigration` has to be + /// set. + #[cfg(any(feature = "try-runtime", test))] + pub fn do_try_state() -> Result<(), TryRuntimeError> { + // if migration has been already scheduled, everything is ok and data will be eventually + // migrated + if CurrentMigration::::exists() { + return Ok(()) + } + + // if migration has NOT been scheduled yet, we need to check all operational data + for v in 0..XCM_VERSION { + ensure!( + SupportedVersion::::iter_prefix(v).next().is_none(), + TryRuntimeError::Other( + "`SupportedVersion` data should be migrated to the `XCM_VERSION`!`" + ) + ); + ensure!( + VersionNotifiers::::iter_prefix(v).next().is_none(), + TryRuntimeError::Other( + "`VersionNotifiers` data should be migrated to the `XCM_VERSION`!`" + ) + ); + ensure!( + VersionNotifyTargets::::iter_prefix(v).next().is_none(), + TryRuntimeError::Other( + "`VersionNotifyTargets` data should be migrated to the `XCM_VERSION`!`" + ) + ); + } + + Ok(()) + } } pub struct LockTicket { diff --git a/polkadot/xcm/pallet-xcm/src/migration.rs b/polkadot/xcm/pallet-xcm/src/migration.rs index 2793afcc910..018436aa3c9 100644 --- a/polkadot/xcm/pallet-xcm/src/migration.rs +++ b/polkadot/xcm/pallet-xcm/src/migration.rs @@ -14,7 +14,9 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use crate::{Config, Pallet, VersionNotifyTargets}; +use crate::{ + pallet::CurrentMigration, Config, Pallet, VersionMigrationStage, VersionNotifyTargets, +}; use frame_support::{ pallet_prelude::*, traits::{OnRuntimeUpgrade, StorageVersion}, @@ -73,3 +75,16 @@ pub mod v1 { ::DbWeight, >; } + +/// When adding a new XCM version, we need to run this migration for `pallet_xcm` to ensure that all +/// previously stored data with subkey prefix `XCM_VERSION-1` (and below) are migrated to the +/// `XCM_VERSION`. +/// +/// NOTE: This migration can be permanently added to the runtime migrations. +pub struct MigrateToLatestXcmVersion(sp_std::marker::PhantomData); +impl OnRuntimeUpgrade for MigrateToLatestXcmVersion { + fn on_runtime_upgrade() -> Weight { + CurrentMigration::::put(VersionMigrationStage::default()); + T::DbWeight::get().writes(1) + } +} diff --git a/polkadot/xcm/pallet-xcm/src/tests/mod.rs b/polkadot/xcm/pallet-xcm/src/tests/mod.rs index 5f9c86ed7b3..e5ac80c3a14 100644 --- a/polkadot/xcm/pallet-xcm/src/tests/mod.rs +++ b/polkadot/xcm/pallet-xcm/src/tests/mod.rs @@ -19,8 +19,9 @@ pub(crate) mod assets_transfer; use crate::{ - mock::*, AssetTraps, CurrentMigration, Error, LatestVersionedLocation, Queries, QueryStatus, - VersionDiscoveryQueue, VersionMigrationStage, VersionNotifiers, VersionNotifyTargets, + mock::*, pallet::SupportedVersion, AssetTraps, Config, CurrentMigration, Error, + LatestVersionedLocation, Pallet, Queries, QueryStatus, VersionDiscoveryQueue, + VersionMigrationStage, VersionNotifiers, VersionNotifyTargets, WeightInfo, }; use frame_support::{ assert_noop, assert_ok, @@ -1113,3 +1114,83 @@ fn get_and_wrap_version_works() { assert_eq!(VersionDiscoveryQueue::::get().into_inner(), vec![(remote_b.into(), 2)]); }) } + +#[test] +fn multistage_migration_works() { + new_test_ext_with_balances(vec![]).execute_with(|| { + // An entry from a previous runtime with v3 XCM. + let v3_location = VersionedLocation::V3(xcm::v3::Junction::Parachain(1001).into()); + let v3_version = xcm::v3::VERSION; + SupportedVersion::::insert(v3_version, v3_location.clone(), v3_version); + VersionNotifiers::::insert(v3_version, v3_location.clone(), 1); + VersionNotifyTargets::::insert( + v3_version, + v3_location, + (70, Weight::zero(), v3_version), + ); + // A version to advertise. + AdvertisedXcmVersion::set(4); + + // check `try-state` + assert!(Pallet::::do_try_state().is_err()); + + // closure simulates a multistage migration process + let migrate = |expected_cycle_count| { + // A runtime upgrade which alters the version does send notifications. + CurrentMigration::::put(VersionMigrationStage::default()); + let mut maybe_migration = CurrentMigration::::take(); + let mut counter = 0; + let mut weight_used = Weight::zero(); + while let Some(migration) = maybe_migration.take() { + counter += 1; + let (w, m) = XcmPallet::check_xcm_version_change(migration, Weight::zero()); + maybe_migration = m; + weight_used.saturating_accrue(w); + } + assert_eq!(counter, expected_cycle_count); + weight_used + }; + + // run migration for the first time + let _ = migrate(4); + + // check xcm sent + assert_eq!( + take_sent_xcm(), + vec![( + Parachain(1001).into(), + Xcm(vec![QueryResponse { + query_id: 70, + max_weight: Weight::zero(), + response: Response::Version(AdvertisedXcmVersion::get()), + querier: None, + }]) + ),] + ); + + // check migrated data + assert_eq!( + SupportedVersion::::iter().collect::>(), + vec![(XCM_VERSION, Parachain(1001).into_versioned(), v3_version),] + ); + assert_eq!( + VersionNotifiers::::iter().collect::>(), + vec![(XCM_VERSION, Parachain(1001).into_versioned(), 1),] + ); + assert_eq!( + VersionNotifyTargets::::iter().collect::>(), + vec![(XCM_VERSION, Parachain(1001).into_versioned(), (70, Weight::zero(), 4)),] + ); + + // run migration again to check it can run multiple time without any harm or double sending + // messages. + let weight_used = migrate(1); + assert_eq!(weight_used, 1_u8 * ::WeightInfo::already_notified_target()); + + // check no xcm sent + assert_eq!(take_sent_xcm(), vec![]); + + // check `try-state` + assert!(Pallet::::do_try_state().is_ok()); + }) +} diff --git a/prdoc/pr_3228.prdoc b/prdoc/pr_3228.prdoc new file mode 100644 index 00000000000..42a4893af36 --- /dev/null +++ b/prdoc/pr_3228.prdoc @@ -0,0 +1,14 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "[pallet_xcm] Forgotten migration to XCMv4 + added `try-state` to the `pallet_xcm`" + +doc: + - audience: Runtime Dev + description: | + The current release includes the new `XCMv4`, so the runtimes must incorporate + a migration `pallet_xcm::migration::MigrateToLatestXcmVersion` to ensure + proper data migration in `pallet_xcm`. + +crates: + - name: pallet-xcm -- GitLab From 9e6298e7f773c61d384677e6eebfa8d7eee9d2ac Mon Sep 17 00:00:00 2001 From: Andrei Eres Date: Tue, 6 Feb 2024 17:57:38 +0100 Subject: [PATCH 020/133] subsystem-bench: Prepare CI output (#3158) 1. Benchmark results are collected in a single struct. 2. The output of the results is prettified. 3. The result struct used to save the output as a yaml and store it in artifacts in a CI job. ``` $ cargo run -p polkadot-subsystem-bench --release -- test-sequence --path polkadot/node/subsystem-bench/examples/availability_read.yaml | tee output.txt $ cat output.txt polkadot/node/subsystem-bench/examples/availability_read.yaml #1 Network usage, KiB total per block Received from peers 510796.000 170265.333 Sent to peers 221.000 73.667 CPU usage, s total per block availability-recovery 38.671 12.890 Test environment 0.255 0.085 polkadot/node/subsystem-bench/examples/availability_read.yaml #2 Network usage, KiB total per block Received from peers 413633.000 137877.667 Sent to peers 353.000 117.667 CPU usage, s total per block availability-recovery 52.630 17.543 Test environment 0.271 0.090 polkadot/node/subsystem-bench/examples/availability_read.yaml #3 Network usage, KiB total per block Received from peers 424379.000 141459.667 Sent to peers 703.000 234.333 CPU usage, s total per block availability-recovery 51.128 17.043 Test environment 0.502 0.167 ``` ``` $ cargo run -p polkadot-subsystem-bench --release -- --ci test-sequence --path polkadot/node/subsystem-bench/examples/availability_read.yaml | tee output.txt $ cat output.txt - benchmark_name: 'polkadot/node/subsystem-bench/examples/availability_read.yaml #1' network: - resource: Received from peers total: 509011.0 per_block: 169670.33333333334 - resource: Sent to peers total: 220.0 per_block: 73.33333333333333 cpu: - resource: availability-recovery total: 31.845848445 per_block: 10.615282815 - resource: Test environment total: 0.23582828799999941 per_block: 0.07860942933333313 - benchmark_name: 'polkadot/node/subsystem-bench/examples/availability_read.yaml #2' network: - resource: Received from peers total: 411738.0 per_block: 137246.0 - resource: Sent to peers total: 351.0 per_block: 117.0 cpu: - resource: availability-recovery total: 18.93596025099999 per_block: 6.31198675033333 - resource: Test environment total: 0.2541994199999979 per_block: 0.0847331399999993 - benchmark_name: 'polkadot/node/subsystem-bench/examples/availability_read.yaml #3' network: - resource: Received from peers total: 424548.0 per_block: 141516.0 - resource: Sent to peers total: 703.0 per_block: 234.33333333333334 cpu: - resource: availability-recovery total: 16.54178526900001 per_block: 5.513928423000003 - resource: Test environment total: 0.43960946299999537 per_block: 0.14653648766666513 ``` --------- Co-authored-by: Andrei Sandu <54316454+sandreim@users.noreply.github.com> --- .../node/subsystem-bench/src/approval/mod.rs | 18 ++- .../subsystem-bench/src/availability/mod.rs | 32 +++-- polkadot/node/subsystem-bench/src/cli.rs | 16 +++ .../subsystem-bench/src/core/environment.rs | 120 ++++++++++++------ .../subsystem-bench/src/subsystem-bench.rs | 62 ++++++--- 5 files changed, 173 insertions(+), 75 deletions(-) diff --git a/polkadot/node/subsystem-bench/src/approval/mod.rs b/polkadot/node/subsystem-bench/src/approval/mod.rs index 3544ce74711..055aeb19345 100644 --- a/polkadot/node/subsystem-bench/src/approval/mod.rs +++ b/polkadot/node/subsystem-bench/src/approval/mod.rs @@ -29,7 +29,9 @@ use crate::{ }, core::{ configuration::{TestAuthorities, TestConfiguration}, - environment::{TestEnvironment, TestEnvironmentDependencies, MAX_TIME_OF_FLIGHT}, + environment::{ + BenchmarkUsage, TestEnvironment, TestEnvironmentDependencies, MAX_TIME_OF_FLIGHT, + }, mock::{ dummy_builder, network_bridge::{MockNetworkBridgeRx, MockNetworkBridgeTx}, @@ -876,7 +878,11 @@ fn prepare_test_inner( ) } -pub async fn bench_approvals(env: &mut TestEnvironment, mut state: ApprovalTestState) { +pub async fn bench_approvals( + benchmark_name: &str, + env: &mut TestEnvironment, + mut state: ApprovalTestState, +) -> BenchmarkUsage { let producer_rx = state .start_message_production( env.network(), @@ -885,15 +891,16 @@ pub async fn bench_approvals(env: &mut TestEnvironment, mut state: ApprovalTestS env.registry().clone(), ) .await; - bench_approvals_run(env, state, producer_rx).await + bench_approvals_run(benchmark_name, env, state, producer_rx).await } /// Runs the approval benchmark. pub async fn bench_approvals_run( + benchmark_name: &str, env: &mut TestEnvironment, state: ApprovalTestState, producer_rx: oneshot::Receiver<()>, -) { +) -> BenchmarkUsage { let config = env.config().clone(); env.metrics().set_n_validators(config.n_validators); @@ -1054,6 +1061,5 @@ pub async fn bench_approvals_run( state.total_unique_messages.load(std::sync::atomic::Ordering::SeqCst) ); - env.display_network_usage(); - env.display_cpu_usage(&["approval-distribution", "approval-voting"]); + env.collect_resource_usage(benchmark_name, &["approval-distribution", "approval-voting"]) } diff --git a/polkadot/node/subsystem-bench/src/availability/mod.rs b/polkadot/node/subsystem-bench/src/availability/mod.rs index f7f1184448b..56ec6705b7e 100644 --- a/polkadot/node/subsystem-bench/src/availability/mod.rs +++ b/polkadot/node/subsystem-bench/src/availability/mod.rs @@ -13,7 +13,10 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use crate::{core::mock::ChainApiState, TestEnvironment}; +use crate::{ + core::{environment::BenchmarkUsage, mock::ChainApiState}, + TestEnvironment, +}; use av_store::NetworkAvailabilityState; use bitvec::bitvec; use colored::Colorize; @@ -430,7 +433,11 @@ impl TestState { } } -pub async fn benchmark_availability_read(env: &mut TestEnvironment, mut state: TestState) { +pub async fn benchmark_availability_read( + benchmark_name: &str, + env: &mut TestEnvironment, + mut state: TestState, +) -> BenchmarkUsage { let config = env.config().clone(); env.import_block(new_block_import_info(Hash::repeat_byte(1), 1)).await; @@ -490,12 +497,15 @@ pub async fn benchmark_availability_read(env: &mut TestEnvironment, mut state: T format!("{} ms", test_start.elapsed().as_millis() / env.config().num_blocks as u128).red() ); - env.display_network_usage(); - env.display_cpu_usage(&["availability-recovery"]); env.stop().await; + env.collect_resource_usage(benchmark_name, &["availability-recovery"]) } -pub async fn benchmark_availability_write(env: &mut TestEnvironment, mut state: TestState) { +pub async fn benchmark_availability_write( + benchmark_name: &str, + env: &mut TestEnvironment, + mut state: TestState, +) -> BenchmarkUsage { let config = env.config().clone(); env.metrics().set_n_validators(config.n_validators); @@ -648,15 +658,11 @@ pub async fn benchmark_availability_write(env: &mut TestEnvironment, mut state: format!("{} ms", test_start.elapsed().as_millis() / env.config().num_blocks as u128).red() ); - env.display_network_usage(); - - env.display_cpu_usage(&[ - "availability-distribution", - "bitfield-distribution", - "availability-store", - ]); - env.stop().await; + env.collect_resource_usage( + benchmark_name, + &["availability-distribution", "bitfield-distribution", "availability-store"], + ) } pub fn peer_bitfield_message_v2( diff --git a/polkadot/node/subsystem-bench/src/cli.rs b/polkadot/node/subsystem-bench/src/cli.rs index bfce8cc183a..21f5e6a8562 100644 --- a/polkadot/node/subsystem-bench/src/cli.rs +++ b/polkadot/node/subsystem-bench/src/cli.rs @@ -40,6 +40,22 @@ pub enum TestObjective { Unimplemented, } +impl std::fmt::Display for TestObjective { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::DataAvailabilityRead(_) => "DataAvailabilityRead", + Self::DataAvailabilityWrite => "DataAvailabilityWrite", + Self::TestSequence(_) => "TestSequence", + Self::ApprovalVoting(_) => "ApprovalVoting", + Self::Unimplemented => "Unimplemented", + } + ) + } +} + #[derive(Debug, clap::Parser)] #[clap(rename_all = "kebab-case")] #[allow(missing_docs)] diff --git a/polkadot/node/subsystem-bench/src/core/environment.rs b/polkadot/node/subsystem-bench/src/core/environment.rs index 59bfed7f112..cffda38f211 100644 --- a/polkadot/node/subsystem-bench/src/core/environment.rs +++ b/polkadot/node/subsystem-bench/src/core/environment.rs @@ -22,6 +22,7 @@ use colored::Colorize; use core::time::Duration; use futures::{Future, FutureExt}; use polkadot_overseer::{BlockInfo, Handle as OverseerHandle}; +use serde::{Deserialize, Serialize}; use polkadot_node_subsystem::{messages::AllMessages, Overseer, SpawnGlue, TimeoutExt}; use polkadot_node_subsystem_types::Hash; @@ -347,57 +348,102 @@ impl TestEnvironment { } } - /// Display network usage stats. - pub fn display_network_usage(&self) { - let stats = self.network().peer_stats(0); - - let total_node_received = stats.received() / 1024; - let total_node_sent = stats.sent() / 1024; - - println!( - "\nPayload bytes received from peers: {}, {}", - format!("{:.2} KiB total", total_node_received).blue(), - format!("{:.2} KiB/block", total_node_received / self.config().num_blocks) - .bright_blue() - ); + pub fn collect_resource_usage( + &self, + benchmark_name: &str, + subsystems_under_test: &[&str], + ) -> BenchmarkUsage { + BenchmarkUsage { + benchmark_name: benchmark_name.to_string(), + network_usage: self.network_usage(), + cpu_usage: self.cpu_usage(subsystems_under_test), + } + } - println!( - "Payload bytes sent to peers: {}, {}", - format!("{:.2} KiB total", total_node_sent).blue(), - format!("{:.2} KiB/block", total_node_sent / self.config().num_blocks).bright_blue() - ); + fn network_usage(&self) -> Vec { + let stats = self.network().peer_stats(0); + let total_node_received = (stats.received() / 1024) as f64; + let total_node_sent = (stats.sent() / 1024) as f64; + let num_blocks = self.config().num_blocks as f64; + + vec![ + ResourceUsage { + resource_name: "Received from peers".to_string(), + total: total_node_received, + per_block: total_node_received / num_blocks, + }, + ResourceUsage { + resource_name: "Sent to peers".to_string(), + total: total_node_sent, + per_block: total_node_sent / num_blocks, + }, + ] } - /// Print CPU usage stats in the CLI. - pub fn display_cpu_usage(&self, subsystems_under_test: &[&str]) { + fn cpu_usage(&self, subsystems_under_test: &[&str]) -> Vec { let test_metrics = super::display::parse_metrics(self.registry()); + let mut usage = vec![]; + let num_blocks = self.config().num_blocks as f64; for subsystem in subsystems_under_test.iter() { let subsystem_cpu_metrics = test_metrics.subset_with_label_value("task_group", subsystem); let total_cpu = subsystem_cpu_metrics.sum_by("substrate_tasks_polling_duration_sum"); - println!( - "{} CPU usage {}", - subsystem.to_string().bright_green(), - format!("{:.3}s", total_cpu).bright_purple() - ); - println!( - "{} CPU usage per block {}", - subsystem.to_string().bright_green(), - format!("{:.3}s", total_cpu / self.config().num_blocks as f64).bright_purple() - ); + usage.push(ResourceUsage { + resource_name: subsystem.to_string(), + total: total_cpu, + per_block: total_cpu / num_blocks, + }); } let test_env_cpu_metrics = test_metrics.subset_with_label_value("task_group", "test-environment"); let total_cpu = test_env_cpu_metrics.sum_by("substrate_tasks_polling_duration_sum"); - println!( - "Total test environment CPU usage {}", - format!("{:.3}s", total_cpu).bright_purple() - ); - println!( - "Test environment CPU usage per block {}", - format!("{:.3}s", total_cpu / self.config().num_blocks as f64).bright_purple() + + usage.push(ResourceUsage { + resource_name: "Test environment".to_string(), + total: total_cpu, + per_block: total_cpu / num_blocks, + }); + + usage + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct BenchmarkUsage { + benchmark_name: String, + network_usage: Vec, + cpu_usage: Vec, +} + +impl std::fmt::Display for BenchmarkUsage { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "\n{}\n\n{}\n{}\n\n{}\n{}\n", + self.benchmark_name.purple(), + format!("{:<32}{:>12}{:>12}", "Network usage, KiB", "total", "per block").blue(), + self.network_usage + .iter() + .map(|v| v.to_string()) + .collect::>() + .join("\n"), + format!("{:<32}{:>12}{:>12}", "CPU usage in seconds", "total", "per block").blue(), + self.cpu_usage.iter().map(|v| v.to_string()).collect::>().join("\n") ) } } + +#[derive(Debug, Serialize, Deserialize)] +pub struct ResourceUsage { + resource_name: String, + total: f64, + per_block: f64, +} + +impl std::fmt::Display for ResourceUsage { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{:<32}{:>12.3}{:>12.3}", self.resource_name.cyan(), self.total, self.per_block) + } +} diff --git a/polkadot/node/subsystem-bench/src/subsystem-bench.rs b/polkadot/node/subsystem-bench/src/subsystem-bench.rs index 6f45214bc73..433354f6525 100644 --- a/polkadot/node/subsystem-bench/src/subsystem-bench.rs +++ b/polkadot/node/subsystem-bench/src/subsystem-bench.rs @@ -100,6 +100,10 @@ struct BenchCli { /// Enable Cache Misses Profiling with Valgrind. Linux only, Valgrind must be in the PATH pub cache_misses: bool, + #[clap(long, default_value_t = false)] + /// Shows the output in YAML format + pub yaml_output: bool, + #[command(subcommand)] pub objective: cli::TestObjective, } @@ -164,34 +168,51 @@ impl BenchCli { format!("Sequence contains {} step(s)", num_steps).bright_purple() ); for (index, test_config) in test_sequence.into_iter().enumerate() { + let benchmark_name = + format!("{} #{} {}", &options.path, index + 1, test_config.objective); gum::info!(target: LOG_TARGET, "{}", format!("Step {}/{}", index + 1, num_steps).bright_purple(),); display_configuration(&test_config); - match test_config.objective { + let usage = match test_config.objective { TestObjective::DataAvailabilityRead(ref _opts) => { let mut state = TestState::new(&test_config); let (mut env, _protocol_config) = prepare_test(test_config, &mut state); env.runtime().block_on(availability::benchmark_availability_read( - &mut env, state, - )); + &benchmark_name, + &mut env, + state, + )) }, TestObjective::ApprovalVoting(ref options) => { let (mut env, state) = approval::prepare_test(test_config.clone(), options.clone()); - - env.runtime().block_on(bench_approvals(&mut env, state)); + env.runtime().block_on(bench_approvals( + &benchmark_name, + &mut env, + state, + )) }, TestObjective::DataAvailabilityWrite => { let mut state = TestState::new(&test_config); let (mut env, _protocol_config) = prepare_test(test_config, &mut state); env.runtime().block_on(availability::benchmark_availability_write( - &mut env, state, - )); + &benchmark_name, + &mut env, + state, + )) }, TestObjective::TestSequence(_) => todo!(), TestObjective::Unimplemented => todo!(), - } + }; + + let output = if self.yaml_output { + serde_yaml::to_string(&vec![usage])? + } else { + usage.to_string() + }; + println!("{}", output); } + return Ok(()) }, TestObjective::DataAvailabilityRead(ref _options) => self.create_test_configuration(), @@ -232,25 +253,28 @@ impl BenchCli { let mut state = TestState::new(&test_config); let (mut env, _protocol_config) = prepare_test(test_config, &mut state); - match self.objective { - TestObjective::DataAvailabilityRead(_options) => { - env.runtime() - .block_on(availability::benchmark_availability_read(&mut env, state)); - }, - TestObjective::DataAvailabilityWrite => { - env.runtime() - .block_on(availability::benchmark_availability_write(&mut env, state)); - }, - TestObjective::TestSequence(_options) => {}, + let benchmark_name = format!("{}", self.objective); + let usage = match self.objective { + TestObjective::DataAvailabilityRead(_options) => env.runtime().block_on( + availability::benchmark_availability_read(&benchmark_name, &mut env, state), + ), + TestObjective::DataAvailabilityWrite => env.runtime().block_on( + availability::benchmark_availability_write(&benchmark_name, &mut env, state), + ), + TestObjective::TestSequence(_options) => todo!(), TestObjective::ApprovalVoting(_) => todo!(), TestObjective::Unimplemented => todo!(), - } + }; if let Some(agent_running) = agent_running { let agent_ready = agent_running.stop()?; agent_ready.shutdown(); } + let output = + if self.yaml_output { serde_yaml::to_string(&vec![usage])? } else { usage.to_string() }; + println!("{}", output); + Ok(()) } } -- GitLab From 2556e33fb4c52ea8192de68df33c151768edc2a9 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Thu, 8 Feb 2024 10:30:48 +0100 Subject: [PATCH 021/133] [ci] Remove path from check-workspace GHA trigger (#3255) In order to make the action `Required` it should run always. cc @ggwpez --- .github/workflows/check-workspace.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/check-workspace.yml b/.github/workflows/check-workspace.yml index 3dd812d7d9b..81ec311ccce 100644 --- a/.github/workflows/check-workspace.yml +++ b/.github/workflows/check-workspace.yml @@ -2,8 +2,6 @@ name: Check workspace on: pull_request: - paths: - - "*.toml" merge_group: jobs: @@ -19,5 +17,5 @@ jobs: run: > python3 .github/scripts/check-workspace.py . --exclude - "substrate/frame/contracts/fixtures/build" + "substrate/frame/contracts/fixtures/build" "substrate/frame/contracts/fixtures/contracts/common" -- GitLab From 2ea6bcf1958b6427cd67eb63968b61782f2becc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=B3nal=20Murray?= Date: Thu, 8 Feb 2024 12:23:36 +0000 Subject: [PATCH 022/133] [pallet_broker] Remove leases that have already expired in rotate_sale (#3213) Leases can be force set, but since `Leases` is a `StorageValue`, if a lease misses its sale rotation in which it should expire, it can never be cleared. This can happen if a lease is added with an `until` timeslice that lies in a region whose sale has already started or has passed, even if the timeslice itself hasn't passed. This solves that issue in a minimal way, with all expired leases being cleaned up in each sale rotation, not just the ones that are expiring in the coming region. TODO: - [x] Write test --- substrate/frame/broker/src/tests.rs | 23 +++++++++++++++++++++++ substrate/frame/broker/src/tick_impls.rs | 6 ++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/substrate/frame/broker/src/tests.rs b/substrate/frame/broker/src/tests.rs index e5efb70ae8d..3e1e36f7d44 100644 --- a/substrate/frame/broker/src/tests.rs +++ b/substrate/frame/broker/src/tests.rs @@ -863,6 +863,29 @@ fn cannot_set_expired_lease() { }); } +#[test] +fn short_leases_are_cleaned() { + TestExt::new().region_length(3).execute_with(|| { + assert_ok!(Broker::do_start_sales(200, 1)); + advance_to(2); + + // New leases are allowed to expire within this region given expiry > `current_timeslice`. + assert_noop!( + Broker::do_set_lease(1000, Broker::current_timeslice()), + Error::::AlreadyExpired + ); + assert_eq!(Leases::::get().len(), 0); + assert_ok!(Broker::do_set_lease(1000, Broker::current_timeslice().saturating_add(1))); + assert_eq!(Leases::::get().len(), 1); + + // But are cleaned up in the next rotate_sale. + let config = Configuration::::get().unwrap(); + let timeslice_period: u64 = ::TimeslicePeriod::get(); + advance_to(timeslice_period.saturating_mul(config.region_length.into())); + assert_eq!(Leases::::get().len(), 0); + }); +} + #[test] fn leases_are_limited() { TestExt::new().execute_with(|| { diff --git a/substrate/frame/broker/src/tick_impls.rs b/substrate/frame/broker/src/tick_impls.rs index 8b7860c8e3a..388370bce4d 100644 --- a/substrate/frame/broker/src/tick_impls.rs +++ b/substrate/frame/broker/src/tick_impls.rs @@ -216,7 +216,9 @@ impl Pallet { let assignment = CoreAssignment::Task(task); let schedule = BoundedVec::truncate_from(vec![ScheduleItem { mask, assignment }]); Workplan::::insert((region_begin, first_core), &schedule); - let expiring = until >= region_begin && until < region_end; + // Separate these to avoid missed expired leases hanging around forever. + let expired = until < region_end; + let expiring = until >= region_begin && expired; if expiring { // last time for this one - make it renewable. let renewal_id = AllowedRenewalId { core: first_core, when: region_end }; @@ -231,7 +233,7 @@ impl Pallet { Self::deposit_event(Event::LeaseEnding { when: region_end, task }); } first_core.saturating_inc(); - !expiring + !expired }); Leases::::put(&leases); -- GitLab From 84d89e379b6aa18cdf038049d3c6703008d6c8cf Mon Sep 17 00:00:00 2001 From: Louis Merlin Date: Thu, 8 Feb 2024 14:05:00 +0100 Subject: [PATCH 023/133] Add try_state and integrity_test to XCM simulator fuzzer (#3222) This adds `try_state()` and `integrity_test()` to the four runtimes of the XCM-simulator fuzzer. With this, we are able to stress-test [message-queue's try_state](https://github.com/paritytech/polkadot-sdk/blob/7df1ae3b8111d534cce108b2b405b6a33fcdedc3/substrate/frame/message-queue/src/lib.rs#L1245-L1347). This also adds the `Transact` block-listing from #2424 to avoid false-positives. Thank you @ggwpez for the help with the runtime configurations. --- Cargo.lock | 2 + polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml | 13 ++++++ polkadot/xcm/xcm-simulator/fuzzer/README.md | 6 +-- polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs | 42 +++++++++++++++-- .../xcm/xcm-simulator/fuzzer/src/parachain.rs | 44 +++++++----------- .../xcm-simulator/fuzzer/src/relay_chain.rs | 45 ++++++++----------- 6 files changed, 93 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 27a1efc3809..db9a2ca3564 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22192,8 +22192,10 @@ name = "xcm-simulator-fuzzer" version = "1.0.0" dependencies = [ "arbitrary", + "frame-executive", "frame-support", "frame-system", + "frame-try-runtime", "honggfuzz", "pallet-balances", "pallet-message-queue", diff --git a/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml b/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml index 13b6e7b8652..30644dc0e0a 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml +++ b/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml @@ -18,6 +18,8 @@ scale-info = { version = "2.10.0", features = ["derive"] } frame-system = { path = "../../../../substrate/frame/system" } frame-support = { path = "../../../../substrate/frame/support" } +frame-executive = { path = "../../../../substrate/frame/executive" } +frame-try-runtime = { path = "../../../../substrate/frame/try-runtime" } pallet-balances = { path = "../../../../substrate/frame/balances" } pallet-message-queue = { path = "../../../../substrate/frame/message-queue" } sp-std = { path = "../../../../substrate/primitives/std" } @@ -35,6 +37,17 @@ polkadot-runtime-parachains = { path = "../../../runtime/parachains" } polkadot-parachain-primitives = { path = "../../../parachain" } [features] +try-runtime = [ + "frame-executive/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "frame-try-runtime/try-runtime", + "pallet-balances/try-runtime", + "pallet-message-queue/try-runtime", + "pallet-xcm/try-runtime", + "polkadot-runtime-parachains/try-runtime", + "sp-runtime/try-runtime", +] runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", diff --git a/polkadot/xcm/xcm-simulator/fuzzer/README.md b/polkadot/xcm/xcm-simulator/fuzzer/README.md index 0b3fdd8ec77..9c15ee881c1 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/README.md +++ b/polkadot/xcm/xcm-simulator/fuzzer/README.md @@ -14,7 +14,7 @@ cargo install honggfuzz In this directory, run this command: ``` -cargo hfuzz run xcm-fuzzer +HFUZZ_BUILD_ARGS="--features=try-runtime" cargo hfuzz run xcm-fuzzer ``` ## Run a single input @@ -22,7 +22,7 @@ cargo hfuzz run xcm-fuzzer In this directory, run this command: ``` -cargo hfuzz run-debug xcm-fuzzer hfuzz_workspace/xcm-fuzzer/fuzzer_input_file +cargo run --features=try-runtime -- hfuzz_workspace/xcm-fuzzer/fuzzer_input_file ``` ## Generate coverage @@ -31,7 +31,7 @@ In this directory, run these four commands: ``` RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" \ -CARGO_INCREMENTAL=0 SKIP_WASM_BUILD=1 CARGO_HOME=./cargo cargo build +CARGO_INCREMENTAL=0 SKIP_WASM_BUILD=1 CARGO_HOME=./cargo cargo build --features=try-runtime ../../../target/debug/xcm-fuzzer hfuzz_workspace/xcm-fuzzer/input/ zip -0 ccov.zip `find ../../../target/ \( -name "*.gc*" -o -name "test-*.gc*" \) -print` grcov ccov.zip -s ../../../ -t html --llvm --branch --ignore-not-existing -o ./coverage diff --git a/polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs b/polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs index 7026d5467c8..adf6cacd278 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs +++ b/polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs @@ -23,7 +23,9 @@ use polkadot_parachain_primitives::primitives::Id as ParaId; use sp_runtime::{traits::AccountIdConversion, BuildStorage}; use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain, TestExt}; -use frame_support::assert_ok; +#[cfg(feature = "try-runtime")] +use frame_support::traits::{TryState, TryStateSelect::All}; +use frame_support::{assert_ok, traits::IntegrityTest}; use xcm::{latest::prelude::*, MAX_XCM_DECODE_DEPTH}; use arbitrary::{Arbitrary, Error, Unstructured}; @@ -98,7 +100,7 @@ impl<'a> Arbitrary<'a> for XcmMessage { if let Ok(message) = DecodeLimit::decode_with_depth_limit(MAX_XCM_DECODE_DEPTH, &mut encoded_message) { - return Ok(XcmMessage { source, destination, message }) + return Ok(XcmMessage { source, destination, message }); } Err(Error::IncorrectFormat) } @@ -148,6 +150,21 @@ pub fn relay_ext() -> sp_io::TestExternalities { pub type RelayChainPalletXcm = pallet_xcm::Pallet; pub type ParachainPalletXcm = pallet_xcm::Pallet; +// We check XCM messages recursively for blocklisted messages +fn recursively_matches_blocklisted_messages(message: &Instruction<()>) -> bool { + match message { + DepositReserveAsset { xcm, .. } | + ExportMessage { xcm, .. } | + InitiateReserveWithdraw { xcm, .. } | + InitiateTeleport { xcm, .. } | + TransferReserveAsset { xcm, .. } | + SetErrorHandler(xcm) | + SetAppendix(xcm) => xcm.iter().any(recursively_matches_blocklisted_messages), + // The blocklisted message is the Transact instruction. + m => matches!(m, Transact { .. }), + } +} + fn run_input(xcm_messages: [XcmMessage; 5]) { MockNet::reset(); @@ -155,6 +172,11 @@ fn run_input(xcm_messages: [XcmMessage; 5]) { println!(); for xcm_message in xcm_messages { + if xcm_message.message.iter().any(recursively_matches_blocklisted_messages) { + println!(" skipping message\n"); + continue; + } + if xcm_message.source % 4 == 0 { // We get the destination for the message let parachain_id = (xcm_message.destination % 3) + 1; @@ -197,8 +219,22 @@ fn run_input(xcm_messages: [XcmMessage; 5]) { } #[cfg(not(fuzzing))] println!(); + // We run integrity tests and try_runtime invariants + [ParaA::execute_with, ParaB::execute_with, ParaC::execute_with].iter().for_each( + |execute_with| { + execute_with(|| { + #[cfg(feature = "try-runtime")] + parachain::AllPalletsWithSystem::try_state(Default::default(), All).unwrap(); + parachain::AllPalletsWithSystem::integrity_test(); + }); + }, + ); + Relay::execute_with(|| { + #[cfg(feature = "try-runtime")] + relay_chain::AllPalletsWithSystem::try_state(Default::default(), All).unwrap(); + relay_chain::AllPalletsWithSystem::integrity_test(); + }); } - Relay::execute_with(|| {}); } fn main() { diff --git a/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs b/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs index d8327c9b401..fbb60a25f44 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs +++ b/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs @@ -24,10 +24,11 @@ use frame_support::{ }; use frame_system::EnsureRoot; -use sp_core::{ConstU32, H256}; +use sp_core::ConstU32; use sp_runtime::{ - traits::{Hash, IdentityLookup}, - AccountId32, + generic, + traits::{AccountIdLookup, BlakeTwo256, Hash, IdentifyAccount, Verify}, + MultiAddress, MultiSignature, }; use sp_std::prelude::*; @@ -47,38 +48,29 @@ use xcm_builder::{ }; use xcm_executor::{Config, XcmExecutor}; -pub type AccountId = AccountId32; +pub type SignedExtra = (frame_system::CheckNonZeroSender,); + +pub type BlockNumber = u64; +pub type Address = MultiAddress; +pub type Header = generic::Header; +pub type UncheckedExtrinsic = + generic::UncheckedExtrinsic; +pub type Block = generic::Block; + +pub type Signature = MultiSignature; +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; pub type Balance = u128; parameter_types! { - pub const BlockHashCount: u64 = 250; + pub const BlockHashCount: u32 = 250; } #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Runtime { - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; - type Lookup = IdentityLookup; + type Lookup = AccountIdLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = BlockHashCount; - type BlockWeights = (); - type BlockLength = (); - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type DbWeight = (); - type BaseCallFilter = Everything; - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } parameter_types! { @@ -356,8 +348,6 @@ impl pallet_xcm::Config for Runtime { type AdminOrigin = EnsureRoot; } -type Block = frame_system::mocking::MockBlock; - construct_runtime!( pub enum Runtime { diff --git a/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs b/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs index 7e42f558dd6..e8294560dfc 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs +++ b/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs @@ -23,8 +23,12 @@ use frame_support::{ }; use frame_system::EnsureRoot; -use sp_core::{ConstU32, H256}; -use sp_runtime::{traits::IdentityLookup, AccountId32}; +use sp_core::ConstU32; +use sp_runtime::{ + generic, + traits::{BlakeTwo256, IdentifyAccount, Verify}, + MultiAddress, MultiSignature, +}; use polkadot_parachain_primitives::primitives::Id as ParaId; use polkadot_runtime_parachains::{ @@ -43,38 +47,29 @@ use xcm_builder::{ }; use xcm_executor::{Config, XcmExecutor}; -pub type AccountId = AccountId32; +pub type SignedExtra = (frame_system::CheckNonZeroSender,); + +pub type BlockNumber = u64; +pub type Address = MultiAddress; +pub type Header = generic::Header; +pub type UncheckedExtrinsic = + generic::UncheckedExtrinsic; +pub type Block = generic::Block; + +pub type Signature = MultiSignature; +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; pub type Balance = u128; parameter_types! { - pub const BlockHashCount: u64 = 250; + pub const BlockHashCount: u32 = 250; } #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Runtime { - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; - type Lookup = IdentityLookup; + type Lookup = sp_runtime::traits::AccountIdLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = BlockHashCount; - type BlockWeights = (); - type BlockLength = (); - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type DbWeight = (); - type BaseCallFilter = Everything; - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } parameter_types! { @@ -202,8 +197,6 @@ parameter_types! { impl origin::Config for Runtime {} -type Block = frame_system::mocking::MockBlock; - parameter_types! { /// Amount of weight that can be spent per block to service messages. pub MessageQueueServiceWeight: Weight = Weight::from_parts(1_000_000_000, 1_000_000); -- GitLab From 07f8592974d92c520e30b2e17a72079c6381fe53 Mon Sep 17 00:00:00 2001 From: Andrei Eres Date: Thu, 8 Feb 2024 14:33:32 +0100 Subject: [PATCH 024/133] subsystem-bench: run cli benchmarks only using config files (#3239) This PR removes the configuration of subsystem benchmarks via CLI arguments. After this, we only keep configurations only in yaml files. It removes unnecessary code duplication --- .../src/approval/message_generator.rs | 3 +- .../node/subsystem-bench/src/approval/mod.rs | 3 +- .../subsystem-bench/src/availability/mod.rs | 2 +- polkadot/node/subsystem-bench/src/cli.rs | 82 ------- .../subsystem-bench/src/core/configuration.rs | 91 +------ .../subsystem-bench/src/subsystem-bench.rs | 230 ++++++------------ 6 files changed, 84 insertions(+), 327 deletions(-) delete mode 100644 polkadot/node/subsystem-bench/src/cli.rs diff --git a/polkadot/node/subsystem-bench/src/approval/message_generator.rs b/polkadot/node/subsystem-bench/src/approval/message_generator.rs index 4318dcdf890..0d308a2fecc 100644 --- a/polkadot/node/subsystem-bench/src/approval/message_generator.rs +++ b/polkadot/node/subsystem-bench/src/approval/message_generator.rs @@ -62,10 +62,11 @@ use crate::{ GeneratedState, BUFFER_FOR_GENERATION_MILLIS, LOG_TARGET, SLOT_DURATION_MILLIS, }, core::{ - configuration::{TestAuthorities, TestConfiguration, TestObjective}, + configuration::{TestAuthorities, TestConfiguration}, mock::session_info_for_peers, NODE_UNDER_TEST, }, + TestObjective, }; use polkadot_node_network_protocol::v3 as protocol_v3; use polkadot_primitives::Hash; diff --git a/polkadot/node/subsystem-bench/src/approval/mod.rs b/polkadot/node/subsystem-bench/src/approval/mod.rs index 055aeb19345..b1ab5363870 100644 --- a/polkadot/node/subsystem-bench/src/approval/mod.rs +++ b/polkadot/node/subsystem-bench/src/approval/mod.rs @@ -28,7 +28,7 @@ use crate::{ mock_chain_selection::MockChainSelection, }, core::{ - configuration::{TestAuthorities, TestConfiguration}, + configuration::TestAuthorities, environment::{ BenchmarkUsage, TestEnvironment, TestEnvironmentDependencies, MAX_TIME_OF_FLIGHT, }, @@ -43,6 +43,7 @@ use crate::{ }, NODE_UNDER_TEST, }, + TestConfiguration, }; use colored::Colorize; use futures::channel::oneshot; diff --git a/polkadot/node/subsystem-bench/src/availability/mod.rs b/polkadot/node/subsystem-bench/src/availability/mod.rs index 56ec6705b7e..8ed39525a1e 100644 --- a/polkadot/node/subsystem-bench/src/availability/mod.rs +++ b/polkadot/node/subsystem-bench/src/availability/mod.rs @@ -67,7 +67,7 @@ use super::core::{configuration::TestConfiguration, mock::dummy_builder, network const LOG_TARGET: &str = "subsystem-bench::availability"; -use super::{cli::TestObjective, core::mock::AlwaysSupportsParachains}; +use super::{core::mock::AlwaysSupportsParachains, TestObjective}; use polkadot_node_subsystem_test_helpers::{ derive_erasure_chunks_with_proofs_and_root, mock::new_block_import_info, }; diff --git a/polkadot/node/subsystem-bench/src/cli.rs b/polkadot/node/subsystem-bench/src/cli.rs deleted file mode 100644 index 21f5e6a8562..00000000000 --- a/polkadot/node/subsystem-bench/src/cli.rs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . -use super::availability::DataAvailabilityReadOptions; -use crate::approval::ApprovalsOptions; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Serialize, Deserialize, clap::Parser)] -#[clap(rename_all = "kebab-case")] -#[allow(missing_docs)] -pub struct TestSequenceOptions { - #[clap(short, long, ignore_case = true)] - pub path: String, -} - -/// Supported test objectives -#[derive(Debug, Clone, clap::Parser, Serialize, Deserialize)] -#[command(rename_all = "kebab-case")] -pub enum TestObjective { - /// Benchmark availability recovery strategies. - DataAvailabilityRead(DataAvailabilityReadOptions), - /// Benchmark availability and bitfield distribution. - DataAvailabilityWrite, - /// Run a test sequence specified in a file - TestSequence(TestSequenceOptions), - /// Benchmark the approval-voting and approval-distribution subsystems. - ApprovalVoting(ApprovalsOptions), - Unimplemented, -} - -impl std::fmt::Display for TestObjective { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Self::DataAvailabilityRead(_) => "DataAvailabilityRead", - Self::DataAvailabilityWrite => "DataAvailabilityWrite", - Self::TestSequence(_) => "TestSequence", - Self::ApprovalVoting(_) => "ApprovalVoting", - Self::Unimplemented => "Unimplemented", - } - ) - } -} - -#[derive(Debug, clap::Parser)] -#[clap(rename_all = "kebab-case")] -#[allow(missing_docs)] -pub struct StandardTestOptions { - #[clap(long, ignore_case = true, default_value_t = 100)] - /// Number of cores to fetch availability for. - pub n_cores: usize, - - #[clap(long, ignore_case = true, default_value_t = 500)] - /// Number of validators to fetch chunks from. - pub n_validators: usize, - - #[clap(long, ignore_case = true, default_value_t = 5120)] - /// The minimum pov size in KiB - pub min_pov_size: usize, - - #[clap(long, ignore_case = true, default_value_t = 5120)] - /// The maximum pov size bytes - pub max_pov_size: usize, - - #[clap(short, long, ignore_case = true, default_value_t = 1)] - /// The number of blocks the test is going to run. - pub num_blocks: usize, -} diff --git a/polkadot/node/subsystem-bench/src/core/configuration.rs b/polkadot/node/subsystem-bench/src/core/configuration.rs index 0c8a78c504c..d9eec43873a 100644 --- a/polkadot/node/subsystem-bench/src/core/configuration.rs +++ b/polkadot/node/subsystem-bench/src/core/configuration.rs @@ -22,7 +22,7 @@ use sc_network::PeerId; use sp_consensus_babe::AuthorityId; use std::{collections::HashMap, path::Path}; -pub use crate::cli::TestObjective; +use crate::TestObjective; use polkadot_primitives::{AssignmentId, AuthorityDiscoveryId, ValidatorId}; use rand::thread_rng; use rand_distr::{Distribution, Normal, Uniform}; @@ -240,95 +240,6 @@ impl TestConfiguration { peer_id_to_authority, } } - - /// An unconstrained standard configuration matching Polkadot/Kusama - pub fn ideal_network( - objective: TestObjective, - num_blocks: usize, - n_validators: usize, - n_cores: usize, - min_pov_size: usize, - max_pov_size: usize, - ) -> TestConfiguration { - Self { - objective, - n_cores, - n_validators, - max_validators_per_core: 5, - pov_sizes: generate_pov_sizes(n_cores, min_pov_size, max_pov_size), - bandwidth: 50 * 1024 * 1024, - peer_bandwidth: 50 * 1024 * 1024, - // No latency - latency: None, - num_blocks, - min_pov_size, - max_pov_size, - connectivity: 100, - needed_approvals: default_needed_approvals(), - n_delay_tranches: default_n_delay_tranches(), - no_show_slots: default_no_show_slots(), - relay_vrf_modulo_samples: default_relay_vrf_modulo_samples(), - zeroth_delay_tranche_width: default_zeroth_delay_tranche_width(), - } - } - - pub fn healthy_network( - objective: TestObjective, - num_blocks: usize, - n_validators: usize, - n_cores: usize, - min_pov_size: usize, - max_pov_size: usize, - ) -> TestConfiguration { - Self { - objective, - n_cores, - n_validators, - max_validators_per_core: 5, - pov_sizes: generate_pov_sizes(n_cores, min_pov_size, max_pov_size), - bandwidth: 50 * 1024 * 1024, - peer_bandwidth: 50 * 1024 * 1024, - latency: Some(PeerLatency { mean_latency_ms: 50, std_dev: 12.5 }), - num_blocks, - min_pov_size, - max_pov_size, - connectivity: 95, - needed_approvals: default_needed_approvals(), - n_delay_tranches: default_n_delay_tranches(), - no_show_slots: default_no_show_slots(), - relay_vrf_modulo_samples: default_relay_vrf_modulo_samples(), - zeroth_delay_tranche_width: default_zeroth_delay_tranche_width(), - } - } - - pub fn degraded_network( - objective: TestObjective, - num_blocks: usize, - n_validators: usize, - n_cores: usize, - min_pov_size: usize, - max_pov_size: usize, - ) -> TestConfiguration { - Self { - objective, - n_cores, - n_validators, - max_validators_per_core: 5, - pov_sizes: generate_pov_sizes(n_cores, min_pov_size, max_pov_size), - bandwidth: 50 * 1024 * 1024, - peer_bandwidth: 50 * 1024 * 1024, - latency: Some(PeerLatency { mean_latency_ms: 150, std_dev: 40.0 }), - num_blocks, - min_pov_size, - max_pov_size, - connectivity: 67, - needed_approvals: default_needed_approvals(), - n_delay_tranches: default_n_delay_tranches(), - no_show_slots: default_no_show_slots(), - relay_vrf_modulo_samples: default_relay_vrf_modulo_samples(), - zeroth_delay_tranche_width: default_zeroth_delay_tranche_width(), - } - } } /// Sample latency (in milliseconds) from a normal distribution with parameters diff --git a/polkadot/node/subsystem-bench/src/subsystem-bench.rs b/polkadot/node/subsystem-bench/src/subsystem-bench.rs index 433354f6525..a5dfbc52d60 100644 --- a/polkadot/node/subsystem-bench/src/subsystem-bench.rs +++ b/polkadot/node/subsystem-bench/src/subsystem-bench.rs @@ -17,6 +17,7 @@ //! A tool for running subsystem benchmark tests designed for development and //! CI regression testing. use clap::Parser; +use serde::{Deserialize, Serialize}; use colored::Colorize; @@ -28,24 +29,23 @@ use std::path::Path; pub(crate) mod approval; pub(crate) mod availability; -pub(crate) mod cli; pub(crate) mod core; mod valgrind; const LOG_TARGET: &str = "subsystem-bench"; use availability::{prepare_test, NetworkEmulation, TestState}; -use cli::TestObjective; +use approval::{bench_approvals, ApprovalsOptions}; +use availability::DataAvailabilityReadOptions; use core::{ configuration::TestConfiguration, + display::display_configuration, environment::{TestEnvironment, GENESIS_HASH}, }; use clap_num::number_range; -use crate::{approval::bench_approvals, core::display::display_configuration}; - fn le_100(s: &str) -> Result { number_range(s, 0, 100) } @@ -54,6 +54,34 @@ fn le_5000(s: &str) -> Result { number_range(s, 0, 5000) } +/// Supported test objectives +#[derive(Debug, Clone, Parser, Serialize, Deserialize)] +#[command(rename_all = "kebab-case")] +pub enum TestObjective { + /// Benchmark availability recovery strategies. + DataAvailabilityRead(DataAvailabilityReadOptions), + /// Benchmark availability and bitfield distribution. + DataAvailabilityWrite, + /// Benchmark the approval-voting and approval-distribution subsystems. + ApprovalVoting(ApprovalsOptions), + Unimplemented, +} + +impl std::fmt::Display for TestObjective { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::DataAvailabilityRead(_) => "DataAvailabilityRead", + Self::DataAvailabilityWrite => "DataAvailabilityWrite", + Self::ApprovalVoting(_) => "ApprovalVoting", + Self::Unimplemented => "Unimplemented", + } + ) + } +} + #[derive(Debug, Parser)] #[allow(missing_docs)] struct BenchCli { @@ -61,9 +89,6 @@ struct BenchCli { /// The type of network to be emulated pub network: NetworkEmulation, - #[clap(flatten)] - pub standard_configuration: cli::StandardTestOptions, - #[clap(short, long)] /// The bandwidth of emulated remote peers in KiB pub peer_bandwidth: Option, @@ -104,42 +129,12 @@ struct BenchCli { /// Shows the output in YAML format pub yaml_output: bool, - #[command(subcommand)] - pub objective: cli::TestObjective, + #[arg(required = true)] + /// Path to the test sequence configuration file + pub path: String, } impl BenchCli { - fn create_test_configuration(&self) -> TestConfiguration { - let configuration = &self.standard_configuration; - - match self.network { - NetworkEmulation::Healthy => TestConfiguration::healthy_network( - self.objective.clone(), - configuration.num_blocks, - configuration.n_validators, - configuration.n_cores, - configuration.min_pov_size, - configuration.max_pov_size, - ), - NetworkEmulation::Degraded => TestConfiguration::degraded_network( - self.objective.clone(), - configuration.num_blocks, - configuration.n_validators, - configuration.n_cores, - configuration.min_pov_size, - configuration.max_pov_size, - ), - NetworkEmulation::Ideal => TestConfiguration::ideal_network( - self.objective.clone(), - configuration.num_blocks, - configuration.n_validators, - configuration.n_cores, - configuration.min_pov_size, - configuration.max_pov_size, - ), - } - } - fn launch(self) -> eyre::Result<()> { let is_valgrind_running = valgrind::is_valgrind_running(); if !is_valgrind_running && self.cache_misses { @@ -156,125 +151,56 @@ impl BenchCli { None }; - let mut test_config = match self.objective { - TestObjective::TestSequence(options) => { - let test_sequence = - core::configuration::TestSequence::new_from_file(Path::new(&options.path)) - .expect("File exists") - .into_vec(); - let num_steps = test_sequence.len(); - gum::info!( - "{}", - format!("Sequence contains {} step(s)", num_steps).bright_purple() - ); - for (index, test_config) in test_sequence.into_iter().enumerate() { - let benchmark_name = - format!("{} #{} {}", &options.path, index + 1, test_config.objective); - gum::info!(target: LOG_TARGET, "{}", format!("Step {}/{}", index + 1, num_steps).bright_purple(),); - display_configuration(&test_config); - - let usage = match test_config.objective { - TestObjective::DataAvailabilityRead(ref _opts) => { - let mut state = TestState::new(&test_config); - let (mut env, _protocol_config) = prepare_test(test_config, &mut state); - env.runtime().block_on(availability::benchmark_availability_read( - &benchmark_name, - &mut env, - state, - )) - }, - TestObjective::ApprovalVoting(ref options) => { - let (mut env, state) = - approval::prepare_test(test_config.clone(), options.clone()); - env.runtime().block_on(bench_approvals( - &benchmark_name, - &mut env, - state, - )) - }, - TestObjective::DataAvailabilityWrite => { - let mut state = TestState::new(&test_config); - let (mut env, _protocol_config) = prepare_test(test_config, &mut state); - env.runtime().block_on(availability::benchmark_availability_write( - &benchmark_name, - &mut env, - state, - )) - }, - TestObjective::TestSequence(_) => todo!(), - TestObjective::Unimplemented => todo!(), - }; - - let output = if self.yaml_output { - serde_yaml::to_string(&vec![usage])? - } else { - usage.to_string() - }; - println!("{}", output); - } - - return Ok(()) - }, - TestObjective::DataAvailabilityRead(ref _options) => self.create_test_configuration(), - TestObjective::DataAvailabilityWrite => self.create_test_configuration(), - TestObjective::ApprovalVoting(_) => todo!(), - TestObjective::Unimplemented => todo!(), - }; - - let mut latency_config = test_config.latency.clone().unwrap_or_default(); - - if let Some(latency) = self.peer_mean_latency { - latency_config.mean_latency_ms = latency; + let test_sequence = core::configuration::TestSequence::new_from_file(Path::new(&self.path)) + .expect("File exists") + .into_vec(); + let num_steps = test_sequence.len(); + gum::info!("{}", format!("Sequence contains {} step(s)", num_steps).bright_purple()); + for (index, test_config) in test_sequence.into_iter().enumerate() { + let benchmark_name = format!("{} #{} {}", &self.path, index + 1, test_config.objective); + gum::info!(target: LOG_TARGET, "{}", format!("Step {}/{}", index + 1, num_steps).bright_purple(),); + display_configuration(&test_config); + + let usage = match test_config.objective { + TestObjective::DataAvailabilityRead(ref _opts) => { + let mut state = TestState::new(&test_config); + let (mut env, _protocol_config) = prepare_test(test_config, &mut state); + env.runtime().block_on(availability::benchmark_availability_read( + &benchmark_name, + &mut env, + state, + )) + }, + TestObjective::ApprovalVoting(ref options) => { + let (mut env, state) = + approval::prepare_test(test_config.clone(), options.clone()); + env.runtime().block_on(bench_approvals(&benchmark_name, &mut env, state)) + }, + TestObjective::DataAvailabilityWrite => { + let mut state = TestState::new(&test_config); + let (mut env, _protocol_config) = prepare_test(test_config, &mut state); + env.runtime().block_on(availability::benchmark_availability_write( + &benchmark_name, + &mut env, + state, + )) + }, + TestObjective::Unimplemented => todo!(), + }; + + let output = if self.yaml_output { + serde_yaml::to_string(&vec![usage])? + } else { + usage.to_string() + }; + println!("{}", output); } - if let Some(std_dev) = self.peer_latency_std_dev { - latency_config.std_dev = std_dev; - } - - // Write back the updated latency. - test_config.latency = Some(latency_config); - - if let Some(connectivity) = self.connectivity { - test_config.connectivity = connectivity; - } - - if let Some(bandwidth) = self.peer_bandwidth { - // CLI expects bw in KiB - test_config.peer_bandwidth = bandwidth * 1024; - } - - if let Some(bandwidth) = self.bandwidth { - // CLI expects bw in KiB - test_config.bandwidth = bandwidth * 1024; - } - - display_configuration(&test_config); - - let mut state = TestState::new(&test_config); - let (mut env, _protocol_config) = prepare_test(test_config, &mut state); - - let benchmark_name = format!("{}", self.objective); - let usage = match self.objective { - TestObjective::DataAvailabilityRead(_options) => env.runtime().block_on( - availability::benchmark_availability_read(&benchmark_name, &mut env, state), - ), - TestObjective::DataAvailabilityWrite => env.runtime().block_on( - availability::benchmark_availability_write(&benchmark_name, &mut env, state), - ), - TestObjective::TestSequence(_options) => todo!(), - TestObjective::ApprovalVoting(_) => todo!(), - TestObjective::Unimplemented => todo!(), - }; - if let Some(agent_running) = agent_running { let agent_ready = agent_running.stop()?; agent_ready.shutdown(); } - let output = - if self.yaml_output { serde_yaml::to_string(&vec![usage])? } else { usage.to_string() }; - println!("{}", output); - Ok(()) } } -- GitLab From 9cd02a07c9a3f5f5de56611c3852f0eeefae9013 Mon Sep 17 00:00:00 2001 From: dharjeezy Date: Thu, 8 Feb 2024 15:03:30 +0100 Subject: [PATCH 025/133] Try State Hook for Ranked Collective (#3007) Part of: paritytech/polkadot-sdk#239 Polkadot address: 12GyGD3QhT4i2JJpNzvMf96sxxBLWymz4RdGCxRH5Rj5agKW --------- Co-authored-by: Liam Aharon --- prdoc/pr_3007.prdoc | 16 +++ .../ranked-collective/src/benchmarking.rs | 2 +- substrate/frame/ranked-collective/src/lib.rs | 134 ++++++++++++++++++ .../frame/ranked-collective/src/tests.rs | 53 ++++--- 4 files changed, 186 insertions(+), 19 deletions(-) create mode 100644 prdoc/pr_3007.prdoc diff --git a/prdoc/pr_3007.prdoc b/prdoc/pr_3007.prdoc new file mode 100644 index 00000000000..17870469a21 --- /dev/null +++ b/prdoc/pr_3007.prdoc @@ -0,0 +1,16 @@ +title: Try State Hook for Ranked Collective. + +doc: + - audience: Runtime User + description: | + Invariants for storage items in the ranked collective pallet. Enforces the following Invariants: + 1. Total number of `Members` in storage should be >= [`MemberIndex`] of a [`Rank`] in `MemberCount`. + 2. `Rank` in Members should be in `MemberCount`. + 3.`Sum` of `MemberCount` index should be the same as the sum of all the index attained for + rank possessed by `Members` + 4. `Member` in storage of `IdToIndex` should be the same as `Member` in `IndexToId`. + 5. `Rank` in `IdToIndex` should be the same as the the `Rank` in `IndexToId`. + 6. `Rank` of the member `who` in `IdToIndex` should be the same as the `Rank` of + the member `who` in `Members` +crates: +- name: pallet-ranked-collective diff --git a/substrate/frame/ranked-collective/src/benchmarking.rs b/substrate/frame/ranked-collective/src/benchmarking.rs index f093fd93e6e..462f55a238d 100644 --- a/substrate/frame/ranked-collective/src/benchmarking.rs +++ b/substrate/frame/ranked-collective/src/benchmarking.rs @@ -181,5 +181,5 @@ benchmarks_instance_pallet! { assert_has_event::(Event::MemberExchanged { who, new_who }.into()); } - impl_benchmark_test_suite!(RankedCollective, crate::tests::new_test_ext(), crate::tests::Test); + impl_benchmark_test_suite!(RankedCollective, crate::tests::ExtBuilder::default().build(), crate::tests::Test); } diff --git a/substrate/frame/ranked-collective/src/lib.rs b/substrate/frame/ranked-collective/src/lib.rs index 65ea886acc4..2b4fb2dbff4 100644 --- a/substrate/frame/ranked-collective/src/lib.rs +++ b/substrate/frame/ranked-collective/src/lib.rs @@ -716,6 +716,14 @@ pub mod pallet { } } + #[pallet::hooks] + impl, I: 'static> Hooks> for Pallet { + #[cfg(feature = "try-runtime")] + fn try_state(_n: BlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { + Self::do_try_state() + } + } + impl, I: 'static> Pallet { fn ensure_member(who: &T::AccountId) -> Result { Members::::get(who).ok_or(Error::::NotMember.into()) @@ -847,6 +855,132 @@ pub mod pallet { } } + #[cfg(any(feature = "try-runtime", test))] + impl, I: 'static> Pallet { + /// Ensure the correctness of the state of this pallet. + pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> { + Self::try_state_members()?; + Self::try_state_index()?; + + Ok(()) + } + + /// ### Invariants of Member storage items + /// + /// Total number of [`Members`] in storage should be >= [`MemberIndex`] of a [`Rank`] in + /// [`MemberCount`]. + /// [`Rank`] in Members should be in [`MemberCount`] + /// [`Sum`] of [`MemberCount`] index should be the same as the sum of all the index attained + /// for rank possessed by [`Members`] + fn try_state_members() -> Result<(), sp_runtime::TryRuntimeError> { + MemberCount::::iter().try_for_each(|(_, member_index)| -> DispatchResult { + let total_members = Members::::iter().count(); + ensure!( + total_members as u32 >= member_index, + "Total count of `Members` should be greater than or equal to the number of `MemberIndex` of a particular `Rank` in `MemberCount`." + ); + + Ok(()) + })?; + + let mut sum_of_member_rank_indexes = 0; + Members::::iter().try_for_each(|(_, member_record)| -> DispatchResult { + ensure!( + Self::is_rank_in_member_count(member_record.rank.into()), + "`Rank` in Members should be in `MemberCount`" + ); + + sum_of_member_rank_indexes += Self::determine_index_of_a_rank(member_record.rank); + + Ok(()) + })?; + + let sum_of_all_member_count_indexes = + MemberCount::::iter_values().fold(0, |sum, index| sum + index); + ensure!( + sum_of_all_member_count_indexes == sum_of_member_rank_indexes as u32, + "Sum of `MemberCount` index should be the same as the sum of all the index attained for rank possessed by `Members`" + ); + Ok(()) + } + + /// ### Invariants of Index storage items + /// [`Member`] in storage of [`IdToIndex`] should be the same as [`Member`] in [`IndexToId`] + /// [`Rank`] in [`IdToIndex`] should be the same as the the [`Rank`] in [`IndexToId`] + /// [`Rank`] of the member [`who`] in [`IdToIndex`] should be the same as the [`Rank`] of + /// the member [`who`] in [`Members`] + fn try_state_index() -> Result<(), sp_runtime::TryRuntimeError> { + IdToIndex::::iter().try_for_each( + |(rank, who, member_index)| -> DispatchResult { + let who_from_index = IndexToId::::get(rank, member_index).unwrap(); + ensure!( + who == who_from_index, + "`Member` in storage of `IdToIndex` should be the same as `Member` in `IndexToId`." + ); + + ensure!( + Self::is_rank_in_index_to_id_storage(rank.into()), + "`Rank` in `IdToIndex` should be the same as the `Rank` in `IndexToId`" + ); + Ok(()) + }, + )?; + + Members::::iter().try_for_each(|(who, member_record)| -> DispatchResult { + ensure!( + Self::is_who_rank_in_id_to_index_storage(who, member_record.rank), + "`Rank` of the member `who` in `IdToIndex` should be the same as the `Rank` of the member `who` in `Members`" + ); + + Ok(()) + })?; + + Ok(()) + } + + /// Checks if a rank is part of the `MemberCount` + fn is_rank_in_member_count(rank: u32) -> bool { + for (r, _) in MemberCount::::iter() { + if r as u32 == rank { + return true; + } + } + + return false; + } + + /// Checks if a rank is the same as the rank `IndexToId` + fn is_rank_in_index_to_id_storage(rank: u32) -> bool { + for (r, _, _) in IndexToId::::iter() { + if r as u32 == rank { + return true; + } + } + + return false; + } + + /// Checks if a member(who) rank is the same as the rank of a member(who) in `IdToIndex` + fn is_who_rank_in_id_to_index_storage(who: T::AccountId, rank: u16) -> bool { + for (rank_, who_, _) in IdToIndex::::iter() { + if who == who_ && rank == rank_ { + return true; + } + } + + return false; + } + + /// Determines the total index for a rank + fn determine_index_of_a_rank(rank: u16) -> u16 { + let mut sum = 0; + for _ in 0..rank + 1 { + sum += 1; + } + sum + } + } + impl, I: 'static> RankedMembers for Pallet { type AccountId = T::AccountId; type Rank = Rank; diff --git a/substrate/frame/ranked-collective/src/tests.rs b/substrate/frame/ranked-collective/src/tests.rs index c5fccd3f772..31add52d90a 100644 --- a/substrate/frame/ranked-collective/src/tests.rs +++ b/substrate/frame/ranked-collective/src/tests.rs @@ -183,11 +183,28 @@ impl Config for Test { type BenchmarkSetup = (); } -pub fn new_test_ext() -> sp_io::TestExternalities { - let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext +pub struct ExtBuilder {} + +impl Default for ExtBuilder { + fn default() -> Self { + Self {} + } +} + +impl ExtBuilder { + pub fn build(self) -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext + } + + pub fn build_and_execute(self, test: impl FnOnce() -> ()) { + self.build().execute_with(|| { + test(); + Club::do_try_state().expect("All invariants must hold after a test"); + }) + } } fn next_block() { @@ -225,14 +242,14 @@ fn completed_poll_should_panic() { #[test] fn basic_stuff() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { assert_eq!(tally(3), Tally::from_parts(0, 0, 0)); }); } #[test] fn member_lifecycle_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); assert_ok!(Club::demote_member(RuntimeOrigin::root(), 1)); @@ -244,7 +261,7 @@ fn member_lifecycle_works() { #[test] fn add_remove_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { assert_noop!(Club::add_member(RuntimeOrigin::signed(1), 1), DispatchError::BadOrigin); assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); assert_eq!(member_count(0), 1); @@ -274,7 +291,7 @@ fn add_remove_works() { #[test] fn promote_demote_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { assert_noop!(Club::add_member(RuntimeOrigin::signed(1), 1), DispatchError::BadOrigin); assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); assert_eq!(member_count(0), 1); @@ -305,7 +322,7 @@ fn promote_demote_works() { #[test] fn promote_demote_by_rank_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); for _ in 0..7 { assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); @@ -372,7 +389,7 @@ fn promote_demote_by_rank_works() { #[test] fn voting_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { assert_ok!(Club::add_member(RuntimeOrigin::root(), 0)); assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); @@ -406,7 +423,7 @@ fn voting_works() { #[test] fn cleanup_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); assert_ok!(Club::add_member(RuntimeOrigin::root(), 2)); @@ -433,7 +450,7 @@ fn cleanup_works() { #[test] fn remove_member_cleanup_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); assert_ok!(Club::add_member(RuntimeOrigin::root(), 2)); @@ -459,7 +476,7 @@ fn remove_member_cleanup_works() { #[test] fn ensure_ranked_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); assert_ok!(Club::add_member(RuntimeOrigin::root(), 2)); @@ -528,7 +545,7 @@ fn ensure_ranked_works() { #[test] fn do_add_member_to_rank_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { let max_rank = 9u16; assert_ok!(Club::do_add_member_to_rank(69, max_rank / 2, true)); assert_ok!(Club::do_add_member_to_rank(1337, max_rank, true)); @@ -545,7 +562,7 @@ fn do_add_member_to_rank_works() { #[test] fn tally_support_correct() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { // add members, // rank 1: accounts 1, 2, 3 // rank 2: accounts 2, 3 @@ -585,7 +602,7 @@ fn tally_support_correct() { #[test] fn exchange_member_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); assert_eq!(member_count(0), 1); @@ -613,7 +630,7 @@ fn exchange_member_works() { #[test] fn exchange_member_same_noops() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); assert_ok!(Club::add_member(RuntimeOrigin::root(), 2)); -- GitLab From 28463a12f08e81fb7ffd455d59d66c2b274b07e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Thu, 8 Feb 2024 15:05:00 +0100 Subject: [PATCH 026/133] contracts: Don't fail fast if the `Weight` limit of a cross contract call is too big (#3243) When doing a cross contract call you can supply an optional Weight limit for that call. If one doesn't specify the limit (setting it to 0) the sub call will have all the remaining gas available. If one does specify the limit we subtract that amount eagerly from the Weight meter and fail fast if not enough `Weight` is available. This is quite annoying because setting a fixed limit will set the `gas_required` in the gas estimation according to the specified limit. Even if in that dry-run the actual call didn't consume that whole amount. It effectively discards the more precise measurement it should have from the dry-run. This PR changes the behaviour so that the supplied limit is an actual limit: We do the cross contract call even if the limit is higher than the remaining `Weight`. We then fail and roll back in the cub call in case there is not enough weight. This makes the weight estimation in the dry-run no longer dependent on the weight limit supplied when doing a cross contract call. --------- Co-authored-by: PG Herveou --- prdoc/pr_3243.prdoc | 13 ++ .../fixtures/contracts/call_with_limit.rs | 4 +- substrate/frame/contracts/src/exec.rs | 2 +- substrate/frame/contracts/src/gas.rs | 34 +-- substrate/frame/contracts/src/tests.rs | 203 +++++++++++------- 5 files changed, 153 insertions(+), 103 deletions(-) create mode 100644 prdoc/pr_3243.prdoc diff --git a/prdoc/pr_3243.prdoc b/prdoc/pr_3243.prdoc new file mode 100644 index 00000000000..5bad985a267 --- /dev/null +++ b/prdoc/pr_3243.prdoc @@ -0,0 +1,13 @@ +title: Don't fail fast if the weight limit of a cross contract call is too big + +doc: + - audience: Runtime Dev + description: | + Cross contracts calls will now be executed even if the supplied weight + limit is bigger than the reamining weight. If the **actual** weight is too low + they will fail in the cross contract call and roll back. This is different from the + old behaviour where the limit for the cross contract call must be smaller than + the remaining weight. + +crates: + - name: pallet-contracts diff --git a/substrate/frame/contracts/fixtures/contracts/call_with_limit.rs b/substrate/frame/contracts/fixtures/contracts/call_with_limit.rs index 5e98aa614e9..40cde234b44 100644 --- a/substrate/frame/contracts/fixtures/contracts/call_with_limit.rs +++ b/substrate/frame/contracts/fixtures/contracts/call_with_limit.rs @@ -31,9 +31,11 @@ pub extern "C" fn deploy() {} #[polkavm_derive::polkavm_export] pub extern "C" fn call() { input!( + 256, callee_addr: [u8; 32], ref_time: u64, proof_size: u64, + forwarded_input: [u8], ); #[allow(deprecated)] @@ -44,7 +46,7 @@ pub extern "C" fn call() { proof_size, None, // No deposit limit. &0u64.to_le_bytes(), // value transferred to the contract. - &[0u8; 0], // input data. + forwarded_input, None, ) .unwrap(); diff --git a/substrate/frame/contracts/src/exec.rs b/substrate/frame/contracts/src/exec.rs index 2183d6b96cc..6f7131f3ea4 100644 --- a/substrate/frame/contracts/src/exec.rs +++ b/substrate/frame/contracts/src/exec.rs @@ -834,7 +834,7 @@ where contract_info: CachedContract::Cached(contract_info), account_id, entry_point, - nested_gas: gas_meter.nested(gas_limit)?, + nested_gas: gas_meter.nested(gas_limit), nested_storage: storage_meter.nested(deposit_limit), allows_reentry: true, }; diff --git a/substrate/frame/contracts/src/gas.rs b/substrate/frame/contracts/src/gas.rs index f78fb03a401..11292dc03f0 100644 --- a/substrate/frame/contracts/src/gas.rs +++ b/substrate/frame/contracts/src/gas.rs @@ -104,9 +104,7 @@ impl GasMeter { /// # Note /// /// Passing `0` as amount is interpreted as "all remaining gas". - pub fn nested(&mut self, amount: Weight) -> Result { - // NOTE that it is ok to allocate all available gas since it still ensured - // by `charge` that it doesn't reach zero. + pub fn nested(&mut self, amount: Weight) -> Self { let amount = Weight::from_parts( if amount.ref_time().is_zero() { self.gas_left().ref_time() @@ -118,33 +116,17 @@ impl GasMeter { } else { amount.proof_size() }, - ); - self.gas_left = self.gas_left.checked_sub(&amount).ok_or_else(|| >::OutOfGas)?; - Ok(GasMeter::new(amount)) + ) + .min(self.gas_left); + self.gas_left -= amount; + GasMeter::new(amount) } /// Absorb the remaining gas of a nested meter after we are done using it. pub fn absorb_nested(&mut self, nested: Self) { - if self.gas_left.ref_time().is_zero() { - // All of the remaining gas was inherited by the nested gas meter. When absorbing - // we can therefore safely inherit the lowest gas that the nested gas meter experienced - // as long as it is lower than the lowest gas that was experienced by the parent. - // We cannot call `self.gas_left_lowest()` here because in the state that this - // code is run the parent gas meter has `0` gas left. - *self.gas_left_lowest.ref_time_mut() = - nested.gas_left_lowest().ref_time().min(self.gas_left_lowest.ref_time()); - } else { - // The nested gas meter was created with a fixed amount that did not consume all of the - // parents (self) gas. The lowest gas that self will experience is when the nested - // gas was pre charged with the fixed amount. - *self.gas_left_lowest.ref_time_mut() = self.gas_left_lowest().ref_time(); - } - if self.gas_left.proof_size().is_zero() { - *self.gas_left_lowest.proof_size_mut() = - nested.gas_left_lowest().proof_size().min(self.gas_left_lowest.proof_size()); - } else { - *self.gas_left_lowest.proof_size_mut() = self.gas_left_lowest().proof_size(); - } + self.gas_left_lowest = (self.gas_left + nested.gas_limit) + .saturating_sub(nested.gas_required()) + .min(self.gas_left_lowest); self.gas_left += nested.gas_left; } diff --git a/substrate/frame/contracts/src/tests.rs b/substrate/frame/contracts/src/tests.rs index 5711d3ccc83..eba959e8d46 100644 --- a/substrate/frame/contracts/src/tests.rs +++ b/substrate/frame/contracts/src/tests.rs @@ -40,7 +40,7 @@ use crate::{ MigrationInProgress, Origin, Pallet, PristineCode, Schedule, }; use assert_matches::assert_matches; -use codec::Encode; +use codec::{Decode, Encode}; use frame_support::{ assert_err, assert_err_ignore_postinfo, assert_err_with_weight, assert_noop, assert_ok, derive_impl, @@ -217,8 +217,6 @@ impl ChainExtension for TestExtension { where E: Ext, { - use codec::Decode; - let func_id = env.func_id(); let id = env.ext_id() as u32 | func_id as u32; match func_id { @@ -2924,12 +2922,13 @@ fn debug_message_invalid_utf8() { } #[test] -fn gas_estimation_nested_call_fixed_limit() { +fn gas_estimation_for_subcalls() { let (caller_code, _caller_hash) = compile_module::("call_with_limit").unwrap(); - let (callee_code, _callee_hash) = compile_module::("dummy").unwrap(); + let (call_runtime_code, _caller_hash) = compile_module::("call_runtime").unwrap(); + let (dummy_code, _callee_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + let _ = ::Currency::set_balance(&ALICE, 2_000 * min_balance); let addr_caller = Contracts::bare_instantiate( ALICE, @@ -2938,7 +2937,7 @@ fn gas_estimation_nested_call_fixed_limit() { None, Code::Upload(caller_code), vec![], - vec![0], + vec![], DebugInfo::Skip, CollectEvents::Skip, ) @@ -2946,14 +2945,14 @@ fn gas_estimation_nested_call_fixed_limit() { .unwrap() .account_id; - let addr_callee = Contracts::bare_instantiate( + let addr_dummy = Contracts::bare_instantiate( ALICE, min_balance * 100, GAS_LIMIT, None, - Code::Upload(callee_code), + Code::Upload(dummy_code), + vec![], vec![], - vec![1], DebugInfo::Skip, CollectEvents::Skip, ) @@ -2961,68 +2960,136 @@ fn gas_estimation_nested_call_fixed_limit() { .unwrap() .account_id; - let input: Vec = AsRef::<[u8]>::as_ref(&addr_callee) - .iter() - .cloned() - .chain((GAS_LIMIT / 5).ref_time().to_le_bytes()) - .chain((GAS_LIMIT / 5).proof_size().to_le_bytes()) - .collect(); - - // Call in order to determine the gas that is required for this call - let result = Contracts::bare_call( + let addr_call_runtime = Contracts::bare_instantiate( ALICE, - addr_caller.clone(), - 0, + min_balance * 100, GAS_LIMIT, None, - input.clone(), + Code::Upload(call_runtime_code), + vec![], + vec![], DebugInfo::Skip, CollectEvents::Skip, - Determinism::Enforced, - ); - assert_ok!(&result.result); + ) + .result + .unwrap() + .account_id; - // We have a subcall with a fixed gas limit. This constitutes precharging. - assert!(result.gas_required.all_gt(result.gas_consumed)); + // Run the test for all of those weight limits for the subcall + let weights = [ + Weight::zero(), + GAS_LIMIT, + GAS_LIMIT * 2, + GAS_LIMIT / 5, + Weight::from_parts(0, GAS_LIMIT.proof_size()), + Weight::from_parts(GAS_LIMIT.ref_time(), 0), + ]; - // Make the same call using the estimated gas. Should succeed. - assert_ok!( - Contracts::bare_call( - ALICE, - addr_caller.clone(), - 0, - result.gas_required, - Some(result.storage_deposit.charge_or_zero()), - input.clone(), - DebugInfo::Skip, - CollectEvents::Skip, - Determinism::Enforced, - ) - .result - ); + // This call is passed to the sub call in order to create a large `required_weight` + let runtime_call = RuntimeCall::Dummy(pallet_dummy::Call::overestimate_pre_charge { + pre_charge: Weight::from_parts(10_000_000_000, 512 * 1024), + actual_weight: Weight::from_parts(1, 1), + }) + .encode(); - // Make the same call using proof_size but less than estimated. Should fail with OutOfGas. - let result = Contracts::bare_call( - ALICE, - addr_caller, - 0, - result.gas_required.sub_proof_size(1), - Some(result.storage_deposit.charge_or_zero()), - input, - DebugInfo::Skip, - CollectEvents::Skip, - Determinism::Enforced, - ) - .result; - assert_err!(result, >::OutOfGas); + // Encodes which contract should be sub called with which input + let sub_calls: [(&[u8], Vec<_>, bool); 2] = [ + (addr_dummy.as_ref(), vec![], false), + (addr_call_runtime.as_ref(), runtime_call, true), + ]; + + for weight in weights { + for (sub_addr, sub_input, out_of_gas_in_subcall) in &sub_calls { + let input: Vec = sub_addr + .iter() + .cloned() + .chain(weight.ref_time().to_le_bytes()) + .chain(weight.proof_size().to_le_bytes()) + .chain(sub_input.clone()) + .collect(); + + // Call in order to determine the gas that is required for this call + let result = Contracts::bare_call( + ALICE, + addr_caller.clone(), + 0, + GAS_LIMIT, + None, + input.clone(), + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, + ); + assert_ok!(&result.result); + + // If the out of gas happens in the subcall the caller contract + // will just trap. Otherwise we would need to forward an error + // code to signal that the sub contract ran out of gas. + let error: DispatchError = if *out_of_gas_in_subcall { + assert!(result.gas_required.all_gt(result.gas_consumed)); + >::ContractTrapped.into() + } else { + assert_eq!(result.gas_required, result.gas_consumed); + >::OutOfGas.into() + }; + + // Make the same call using the estimated gas. Should succeed. + assert_ok!( + Contracts::bare_call( + ALICE, + addr_caller.clone(), + 0, + result.gas_required, + Some(result.storage_deposit.charge_or_zero()), + input.clone(), + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, + ) + .result + ); + + // Check that it fails with too little ref_time + assert_err!( + Contracts::bare_call( + ALICE, + addr_caller.clone(), + 0, + result.gas_required.sub_ref_time(1), + Some(result.storage_deposit.charge_or_zero()), + input.clone(), + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, + ) + .result, + error, + ); + + // Check that it fails with too little proof_size + assert_err!( + Contracts::bare_call( + ALICE, + addr_caller.clone(), + 0, + result.gas_required.sub_proof_size(1), + Some(result.storage_deposit.charge_or_zero()), + input, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, + ) + .result, + error, + ); + } + } }); } #[test] fn gas_estimation_call_runtime() { - use codec::Decode; let (caller_code, _caller_hash) = compile_module::("call_runtime").unwrap(); - let (callee_code, _callee_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = Contracts::min_balance(); let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); @@ -3043,25 +3110,11 @@ fn gas_estimation_call_runtime() { .unwrap() .account_id; - Contracts::bare_instantiate( - ALICE, - min_balance * 100, - GAS_LIMIT, - None, - Code::Upload(callee_code), - vec![], - vec![1], - DebugInfo::Skip, - CollectEvents::Skip, - ) - .result - .unwrap(); - // Call something trivial with a huge gas limit so that we can observe the effects // of pre-charging. This should create a difference between consumed and required. let call = RuntimeCall::Dummy(pallet_dummy::Call::overestimate_pre_charge { - pre_charge: Weight::from_parts(10_000_000, 0), - actual_weight: Weight::from_parts(100, 0), + pre_charge: Weight::from_parts(10_000_000, 1_000), + actual_weight: Weight::from_parts(100, 100), }); let result = Contracts::bare_call( ALICE, @@ -3077,7 +3130,7 @@ fn gas_estimation_call_runtime() { // contract encodes the result of the dispatch runtime let outcome = u32::decode(&mut result.result.unwrap().data.as_ref()).unwrap(); assert_eq!(outcome, 0); - assert!(result.gas_required.ref_time() > result.gas_consumed.ref_time()); + assert!(result.gas_required.all_gt(result.gas_consumed)); // Make the same call using the required gas. Should succeed. assert_ok!( -- GitLab From 7fa05518b00e69664d9beec8d4c8f8e754951fdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Thu, 8 Feb 2024 15:39:38 +0100 Subject: [PATCH 027/133] contracts: Remove unused benchmarks (#3185) Those were used for some adhoc comparison of solang vs ink! with regards to ERC20 transfers. Not been used for a while. Benchmarking is done here now: [smart-bench](https://github.com/paritytech/smart-bench): Weight based benchmark to test how much transaction actually fit into a block with the current Weights [schlau](https://github.com/ascjones/schlau): Time based benchmarks to compare performance --- .../frame/contracts/benchmarks/README.md | 8 - .../frame/contracts/benchmarks/ink_erc20.json | 819 ------------------ .../frame/contracts/benchmarks/ink_erc20.wasm | Bin 30276 -> 0 bytes .../contracts/benchmarks/ink_erc20_test.wasm | Bin 29726 -> 0 bytes .../contracts/benchmarks/solang_erc20.json | 581 ------------- .../contracts/benchmarks/solang_erc20.wasm | Bin 12505 -> 0 bytes .../frame/contracts/src/benchmarking/code.rs | 22 +- .../frame/contracts/src/benchmarking/mod.rs | 100 +-- 8 files changed, 3 insertions(+), 1527 deletions(-) delete mode 100644 substrate/frame/contracts/benchmarks/README.md delete mode 100644 substrate/frame/contracts/benchmarks/ink_erc20.json delete mode 100644 substrate/frame/contracts/benchmarks/ink_erc20.wasm delete mode 100644 substrate/frame/contracts/benchmarks/ink_erc20_test.wasm delete mode 100644 substrate/frame/contracts/benchmarks/solang_erc20.json delete mode 100644 substrate/frame/contracts/benchmarks/solang_erc20.wasm diff --git a/substrate/frame/contracts/benchmarks/README.md b/substrate/frame/contracts/benchmarks/README.md deleted file mode 100644 index e4441d6bab2..00000000000 --- a/substrate/frame/contracts/benchmarks/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Benchmarks - -This directory contains real world ([ink!](https://use.ink), [solang](https://github.com/hyperledger/solang)) contracts -which are used in macro benchmarks. Those benchmarks are not used to determine weights but rather to compare different -contract languages and execution engines with larger wasm modules. - -Files in this directory are used by `#[extra]` benchmarks in `src/benchmarking`. The json files are for informational -purposes only and are not consumed by the benchmarks. diff --git a/substrate/frame/contracts/benchmarks/ink_erc20.json b/substrate/frame/contracts/benchmarks/ink_erc20.json deleted file mode 100644 index 390dd9b06cd..00000000000 --- a/substrate/frame/contracts/benchmarks/ink_erc20.json +++ /dev/null @@ -1,819 +0,0 @@ -{ - "metadataVersion": "0.1.0", - "source": { - "hash": "0x6be8492017fe96b7a92bb39b4ede04b96effb8fcaf9237bfdccef7d9e732c760", - "language": "ink! 3.0.0-rc4", - "compiler": "rustc 1.56.0-nightly" - }, - "contract": { - "name": "erc20", - "version": "3.0.0-rc4", - "authors": [ - "Parity Technologies " - ] - }, - "spec": { - "constructors": [ - { - "args": [ - { - "name": "initial_supply", - "type": { - "displayName": [ - "Balance" - ], - "type": 1 - } - } - ], - "docs": [ - "Creates a new ERC-20 contract with the specified initial supply." - ], - "name": [ - "new" - ], - "selector": "0x9bae9d5e" - } - ], - "docs": [], - "events": [ - { - "args": [ - { - "docs": [], - "indexed": true, - "name": "from", - "type": { - "displayName": [ - "Option" - ], - "type": 15 - } - }, - { - "docs": [], - "indexed": true, - "name": "to", - "type": { - "displayName": [ - "Option" - ], - "type": 15 - } - }, - { - "docs": [], - "indexed": false, - "name": "value", - "type": { - "displayName": [ - "Balance" - ], - "type": 1 - } - } - ], - "docs": [ - " Event emitted when a token transfer occurs." - ], - "name": "Transfer" - }, - { - "args": [ - { - "docs": [], - "indexed": true, - "name": "owner", - "type": { - "displayName": [ - "AccountId" - ], - "type": 5 - } - }, - { - "docs": [], - "indexed": true, - "name": "spender", - "type": { - "displayName": [ - "AccountId" - ], - "type": 5 - } - }, - { - "docs": [], - "indexed": false, - "name": "value", - "type": { - "displayName": [ - "Balance" - ], - "type": 1 - } - } - ], - "docs": [ - " Event emitted when an approval occurs that `spender` is allowed to withdraw", - " up to the amount of `value` tokens from `owner`." - ], - "name": "Approval" - } - ], - "messages": [ - { - "args": [], - "docs": [ - " Returns the total token supply." - ], - "mutates": false, - "name": [ - "total_supply" - ], - "payable": false, - "returnType": { - "displayName": [ - "Balance" - ], - "type": 1 - }, - "selector": "0xdb6375a8" - }, - { - "args": [ - { - "name": "owner", - "type": { - "displayName": [ - "AccountId" - ], - "type": 5 - } - } - ], - "docs": [ - " Returns the account balance for the specified `owner`.", - "", - " Returns `0` if the account is non-existent." - ], - "mutates": false, - "name": [ - "balance_of" - ], - "payable": false, - "returnType": { - "displayName": [ - "Balance" - ], - "type": 1 - }, - "selector": "0x0f755a56" - }, - { - "args": [ - { - "name": "owner", - "type": { - "displayName": [ - "AccountId" - ], - "type": 5 - } - }, - { - "name": "spender", - "type": { - "displayName": [ - "AccountId" - ], - "type": 5 - } - } - ], - "docs": [ - " Returns the amount which `spender` is still allowed to withdraw from `owner`.", - "", - " Returns `0` if no allowance has been set `0`." - ], - "mutates": false, - "name": [ - "allowance" - ], - "payable": false, - "returnType": { - "displayName": [ - "Balance" - ], - "type": 1 - }, - "selector": "0x6a00165e" - }, - { - "args": [ - { - "name": "to", - "type": { - "displayName": [ - "AccountId" - ], - "type": 5 - } - }, - { - "name": "value", - "type": { - "displayName": [ - "Balance" - ], - "type": 1 - } - } - ], - "docs": [ - " Transfers `value` amount of tokens from the caller's account to account `to`.", - "", - " On success a `Transfer` event is emitted.", - "", - " # Errors", - "", - " Returns `InsufficientBalance` error if there are not enough tokens on", - " the caller's account balance." - ], - "mutates": true, - "name": [ - "transfer" - ], - "payable": false, - "returnType": { - "displayName": [ - "Result" - ], - "type": 12 - }, - "selector": "0x84a15da1" - }, - { - "args": [ - { - "name": "spender", - "type": { - "displayName": [ - "AccountId" - ], - "type": 5 - } - }, - { - "name": "value", - "type": { - "displayName": [ - "Balance" - ], - "type": 1 - } - } - ], - "docs": [ - " Allows `spender` to withdraw from the caller's account multiple times, up to", - " the `value` amount.", - "", - " If this function is called again it overwrites the current allowance with `value`.", - "", - " An `Approval` event is emitted." - ], - "mutates": true, - "name": [ - "approve" - ], - "payable": false, - "returnType": { - "displayName": [ - "Result" - ], - "type": 12 - }, - "selector": "0x681266a0" - }, - { - "args": [ - { - "name": "from", - "type": { - "displayName": [ - "AccountId" - ], - "type": 5 - } - }, - { - "name": "to", - "type": { - "displayName": [ - "AccountId" - ], - "type": 5 - } - }, - { - "name": "value", - "type": { - "displayName": [ - "Balance" - ], - "type": 1 - } - } - ], - "docs": [ - " Transfers `value` tokens on the behalf of `from` to the account `to`.", - "", - " This can be used to allow a contract to transfer tokens on ones behalf and/or", - " to charge fees in sub-currencies, for example.", - "", - " On success a `Transfer` event is emitted.", - "", - " # Errors", - "", - " Returns `InsufficientAllowance` error if there are not enough tokens allowed", - " for the caller to withdraw from `from`.", - "", - " Returns `InsufficientBalance` error if there are not enough tokens on", - " the account balance of `from`." - ], - "mutates": true, - "name": [ - "transfer_from" - ], - "payable": false, - "returnType": { - "displayName": [ - "Result" - ], - "type": 12 - }, - "selector": "0x0b396f18" - } - ] - }, - "storage": { - "struct": { - "fields": [ - { - "layout": { - "cell": { - "key": "0x0000000000000000000000000000000000000000000000000000000000000000", - "ty": 1 - } - }, - "name": "total_supply" - }, - { - "layout": { - "struct": { - "fields": [ - { - "layout": { - "struct": { - "fields": [ - { - "layout": { - "cell": { - "key": "0x0100000000000000000000000000000000000000000000000000000000000000", - "ty": 2 - } - }, - "name": "header" - }, - { - "layout": { - "struct": { - "fields": [ - { - "layout": { - "cell": { - "key": "0x0200000000000000000000000000000000000000000000000000000000000000", - "ty": 3 - } - }, - "name": "len" - }, - { - "layout": { - "array": { - "cellsPerElem": 1, - "layout": { - "cell": { - "key": "0x0200000001000000000000000000000000000000000000000000000000000000", - "ty": 4 - } - }, - "len": 4294967295, - "offset": "0x0300000000000000000000000000000000000000000000000000000000000000" - } - }, - "name": "elems" - } - ] - } - }, - "name": "entries" - } - ] - } - }, - "name": "keys" - }, - { - "layout": { - "hash": { - "layout": { - "cell": { - "key": "0x0300000001000000000000000000000000000000000000000000000000000000", - "ty": 9 - } - }, - "offset": "0x0200000001000000000000000000000000000000000000000000000000000000", - "strategy": { - "hasher": "Blake2x256", - "postfix": "", - "prefix": "0x696e6b20686173686d6170" - } - } - }, - "name": "values" - } - ] - } - }, - "name": "balances" - }, - { - "layout": { - "struct": { - "fields": [ - { - "layout": { - "struct": { - "fields": [ - { - "layout": { - "cell": { - "key": "0x0300000001000000000000000000000000000000000000000000000000000000", - "ty": 2 - } - }, - "name": "header" - }, - { - "layout": { - "struct": { - "fields": [ - { - "layout": { - "cell": { - "key": "0x0400000001000000000000000000000000000000000000000000000000000000", - "ty": 3 - } - }, - "name": "len" - }, - { - "layout": { - "array": { - "cellsPerElem": 1, - "layout": { - "cell": { - "key": "0x0400000002000000000000000000000000000000000000000000000000000000", - "ty": 10 - } - }, - "len": 4294967295, - "offset": "0x0500000001000000000000000000000000000000000000000000000000000000" - } - }, - "name": "elems" - } - ] - } - }, - "name": "entries" - } - ] - } - }, - "name": "keys" - }, - { - "layout": { - "hash": { - "layout": { - "cell": { - "key": "0x0500000002000000000000000000000000000000000000000000000000000000", - "ty": 9 - } - }, - "offset": "0x0400000002000000000000000000000000000000000000000000000000000000", - "strategy": { - "hasher": "Blake2x256", - "postfix": "", - "prefix": "0x696e6b20686173686d6170" - } - } - }, - "name": "values" - } - ] - } - }, - "name": "allowances" - } - ] - } - }, - "types": [ - { - "def": { - "primitive": "u128" - } - }, - { - "def": { - "composite": { - "fields": [ - { - "name": "last_vacant", - "type": 3, - "typeName": "Index" - }, - { - "name": "len", - "type": 3, - "typeName": "u32" - }, - { - "name": "len_entries", - "type": 3, - "typeName": "u32" - } - ] - } - }, - "path": [ - "ink_storage", - "collections", - "stash", - "Header" - ] - }, - { - "def": { - "primitive": "u32" - } - }, - { - "def": { - "variant": { - "variants": [ - { - "fields": [ - { - "type": 8, - "typeName": "VacantEntry" - } - ], - "name": "Vacant" - }, - { - "fields": [ - { - "type": 5, - "typeName": "T" - } - ], - "name": "Occupied" - } - ] - } - }, - "params": [ - 5 - ], - "path": [ - "ink_storage", - "collections", - "stash", - "Entry" - ] - }, - { - "def": { - "composite": { - "fields": [ - { - "type": 6, - "typeName": "[u8; 32]" - } - ] - } - }, - "path": [ - "ink_env", - "types", - "AccountId" - ] - }, - { - "def": { - "array": { - "len": 32, - "type": 7 - } - } - }, - { - "def": { - "primitive": "u8" - } - }, - { - "def": { - "composite": { - "fields": [ - { - "name": "next", - "type": 3, - "typeName": "Index" - }, - { - "name": "prev", - "type": 3, - "typeName": "Index" - } - ] - } - }, - "path": [ - "ink_storage", - "collections", - "stash", - "VacantEntry" - ] - }, - { - "def": { - "composite": { - "fields": [ - { - "name": "value", - "type": 1, - "typeName": "V" - }, - { - "name": "key_index", - "type": 3, - "typeName": "KeyIndex" - } - ] - } - }, - "params": [ - 1 - ], - "path": [ - "ink_storage", - "collections", - "hashmap", - "ValueEntry" - ] - }, - { - "def": { - "variant": { - "variants": [ - { - "fields": [ - { - "type": 8, - "typeName": "VacantEntry" - } - ], - "name": "Vacant" - }, - { - "fields": [ - { - "type": 11, - "typeName": "T" - } - ], - "name": "Occupied" - } - ] - } - }, - "params": [ - 11 - ], - "path": [ - "ink_storage", - "collections", - "stash", - "Entry" - ] - }, - { - "def": { - "tuple": [ - 5, - 5 - ] - } - }, - { - "def": { - "variant": { - "variants": [ - { - "fields": [ - { - "type": 13, - "typeName": "T" - } - ], - "name": "Ok" - }, - { - "fields": [ - { - "type": 14, - "typeName": "E" - } - ], - "name": "Err" - } - ] - } - }, - "params": [ - 13, - 14 - ], - "path": [ - "Result" - ] - }, - { - "def": { - "tuple": [] - } - }, - { - "def": { - "variant": { - "variants": [ - { - "discriminant": 0, - "name": "InsufficientBalance" - }, - { - "discriminant": 1, - "name": "InsufficientAllowance" - } - ] - } - }, - "path": [ - "erc20", - "erc20", - "Error" - ] - }, - { - "def": { - "variant": { - "variants": [ - { - "name": "None" - }, - { - "fields": [ - { - "type": 5, - "typeName": "T" - } - ], - "name": "Some" - } - ] - } - }, - "params": [ - 5 - ], - "path": [ - "Option" - ] - } - ] -} \ No newline at end of file diff --git a/substrate/frame/contracts/benchmarks/ink_erc20.wasm b/substrate/frame/contracts/benchmarks/ink_erc20.wasm deleted file mode 100644 index ffd522760a02dd8416f8ee04db02c32e2172b8ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30276 zcmds=f3O}`dEfV(-S@|R-}hdvZDC}~!TT=4T!9r|jFE&y!rfa-vZ7K9ai~MVmcRmW zud!uWc1QYI^+GF@AM*>=@Pjas9gE?wJ3s8+0B$c;Lag zT)+1}dhqD|j~;#a$bpX@OkB9J-fkQ^@aUoYKlI>%rGvNMfBW0E);F)Un-3g3{P3e6 zJ9_`Y#}7VqG|9qqL%a3i0}noU@JNz`vg@tv@dFP&cJThAM-Dvn=tmA7IdbrU`gUmT z!w()jaHOFkZIk!W7J1!pV_;2w?4iSt9j%)QJs&xE^syrkB`!Vq(Bs{YAN=^kM}F2j zpQn!;hOf!MbK$)2IUi4xk)xddraV7A{$^A#6-qPRN;3*v+U<0FC+T)v#%cYNWtm@> zq%KXVlP0~i-|MF?n;v#uw-2kU<>hSHyT*56)`JiKZ1Qj>)+CP%zWU$Y;Sag=`0JA@ zUn>2kr0`XLv2gjIa^uv`7k<|FrzxHFqj6Sv-O)u7%CfO5k|Nvcvm&wQw)&(>#_5pK zlyh3k7dIutyPRLuMY7e8Xxwpsv{+^N-d&k>H5$8-8>(xy;H=$M4b3E!`+THF^U?4Y z=i2QSYIoA=uDbivUDs2+WxrHTZA$1lsV*lP=1iGwPLl=q9{Oi^+tPlKRmqN|;E*pC z>9!Qf42!A4RVzvUGvmz9H-E>iom&^-M%kyuIi2mHp`_ZrSPqJ0*3XvxeBHQ1V{S)6 z57qoq<&JPvV>fbq+AS8kJ-?LaMYbh*+LfKk@19PJPUUt_cMIs5_0LecyMocxDQDeL zzI)?zRt$=6eofh%c2zR+L+)Rqy`;Lx*!uak(8t4m}zc`Nyz zTiomuh6)R^&Hg)XTRP7zO=vE9)14wIx?BCM%IB^upBrUEn9{4V#iF+*NnqHZ%5LS- zD&E?V`0i;JZqw?mFf}TVt7fY{JYAz`MSrW`P-F|RT{4Mk$3s$%@aT;kVRjOWxRGC9 zxLH3RL|a!yyRsvQS-KUkhwUzb`i^>Jip!_HxFTCEckJ^NE&nUQpPf+da+2M2Is4b6vQ1cDB!4Ypk|xS^N@BiR-`G>8Gu^`25Z52~TqeJE5x*9EH=USx z9M*55$%BS?ToR9M((}PD&*}Q6+VI6LzkXYaN<|UEo64IzrXEz%ZcC2h&?+fPLRXRD zdC5>-hF@y9v-xb{rRidPU?Q?bC1UAa+tNdNewhgIb@lM{r_kdS z2k~f~J+XpzZZ4B5#SLgo)o{0jt*2M${sd>zWfaUW=5H$emZV>~N2ZBWUiG=2n(>Ll z1=KpIic`||6F5|PXj3=%Ip%cMf6k3Fys!!Gq_10kvsyao^?-UjxBAAp=Ei4qV@vXj zUe4B6{}^8R>RWh>FZ|!jm<0tjFR3>z($MId>!fVlK*FJgm z&eX%tL);ZDo9I`!thzW7ez#8yBye-CQBJAOESe)%YM~Klsu5eJgMr61NaH`@cP0*< zOVuPqL~;|79SUl8P=N;Sq~rvBC;56f9`t8}sG$5N9VdZQWL`8s{bS@k(icfcKK<*; zO*o-~oyrnY@(5I3<5xxj%bmsTA%f#+YUk^W@8T-gA*d$=_u=rV)J@5;6ia1FzI0r% zCM!EM$B7Z?@}DS)t(h9dq43bSFWbSeJGZ&}hyY}joGLhE(9fMIF(o5LWjqI)$wo14cMxaz%I+ z=^`$Fn_9}qXgDNeb*e0Y8VYJiSK*X1Jc|;K$|)l9oJN2^Lp6Lf9jF=)Shd8e5lzfD zCpdgnw@P?8#)0d_-Cx5qNbNwGNUrM`@4xB0YbB=(NeBR>Bgt55g11-mThOnxk*yxpEP_{k)3!H~^VZ2TycaBFo%rk5bbK^Asi7|O8PbyDa zD#=3kk*YFfAiXXII`0=#JKcSjHo|Lixh~05x9BaEGdj-ejdLAv0OBo{M6LX%uxOpYolM+>vprNF&T`Os zTJ%IdGYT0%CKANi;Zix$x#JE7R(9c?_(}^g3x|nHIZ`pT2UAU^H;Fv@-@t=7=J;SS z5?`+id_`STF|T=MeS9XjrVFb^w@@XTdLjje8?72huNu9ZXh{*b^=NAIldk=W*w76o zgR98jCu(e}DT_sQ#E)@TW{#2cG%@r=9wdi>z&yAAEPx<4ro7>#&<)G>w9Wz}UcdFy^^^|uC%gF}gp60IDQPEi}N7WsR z`JXTQ)eG(sfdW(L;A zbk~SqD@{&*PSKst+s>Hj(6frxN^jL{8RyouWB#%$Q?YHj_RY-CWEtn2d1rD_8Mpbx zCp{X@tz{ql^RsAs{w|_`H2}AkKqoLqJe?GDRP=Q|EJi!s?J_w8BX@Df;%q`hgC=#= zC4yA%kY(C*d`d2yaLM1)QrXqHoQ&_R&TvMHt|zIEPWIZDul^ltqO93M3*IeA?j`k%I%DY=!5Unn2A1=R^RK;mHmqFCi;+? z=6^}dLzX)9$FM(Y8yy9KcE!$dPd)}mBvn8}griSzz#kmtfB+A3ASYb*#3QL&tqcvl z#kyjcf57gAJ|?Y?P_q>4og()l_?dGGexhk9fKONNVxqaB3&Nmze+{ZbCXAIRW|%Mu zHfG6+8R14&W-(h%#!3Euq8X$4p8Q}U?q>T)r4 z3=PPzNeXCGE2o;$lQ1s~xKmxD>wqP~fKGLTt^?d?3?b@FFl*-cxZ=3@w_Tx;LGg7JG$g&(xR2oq-Z2%=e?$|xqSizyy#yp6cTTY& z*n!Jm7ne0=qlVZzk0}U|NJ*g9JWd%5`|ldm{8?N@0Aht?whTXb@Rr>NoU#3(cS z{Wv=k)d;dPru;`<5@bK42@2I);4D-z16u{pGJHJIRRG$Pa*P2o72xIau{?UU+`!Tq zR}4n>1z+)YSHZ=t5upbH-~+@yzY1c9B5uO#*h~$#U6_;zUIW*qsR*%4oMJ^SnW!mO zLD)~W5q3rzJBbK8eK)_RBPrwO~S!B__9M|d>F5eQmv?|tU4q@um*^!q*o=*iRu4p&`Pd0=Q24unJsv4Y&>(8tNIgex49v z*!ryhCab_zGFImo%uNT8HTB}^%OT^%$*S# z=KeJwT+M3CeKpXs?+T!0g0S?f&e8D{7Sl#prnCeAjwWOzq9=k`$P+si08K~s%w$NI z%ge)3w!bKr5=|_rZWZH03YzAarw(&F1J7-__|B7nXkUoh)G5fF59TFm4q z`ML|>!HNqQ&0#R~eSnkp_>3T$ZZRWRM)Uk~olsxR9oL0vu3R2cL%srPQ0baL`JS%n zbY1eCm7v+PNkslNf3gQwlr77-1T1F;R2?{eN;AM3{Q-%fj%HT@JQie36r0v$ejSr} zbI}u2N02Tw>FyLK4XQJ($$C?Ng6gP#Xq@_C=1qTLj7nqVU>R%KcqF-1xDni+&1pzM zSVg5&_ymJ3uOXJ3OOa#M%I+z-KPHBXKQOrHH;@o3IR*(`!=q0(-dW!5WOF62dZM{9 zZ@o;T@Gi!8iP+`u9;*>Ku{s$IK5kv@OD&}uwP{mKygP(23CTdew541YNCZzd(v{dX zH?0ebw3cJYCIdJTi9zeY$5Ay@tA?irEqKZ{x0wGcuY+_FfSNcIs$5S|D+54f%GZPe z)&mlI?`8#wQdl5JON(}WS%N`Ywm4$Ah;)oeb%3-`@HwrG+>128NABT3?tC{<2|SB^ zH6W8MJWI@tizDaV`1)u$uhnsdY{2T~cLO@;AtE4HPbVN+T5CZkIswr#Xfx>2wS=$K zEJvtu%}O!51dUp2JLL_OwXLVqvYu2U1Dpu}SbwPmq)~qz3;=UusYP8)tVfDlLt(-= z%@a(ZuwEgLZPqhvtfz*;n)(wcO!aA;NjcV~wHnzQJx=UrSX^6+%PeP9Y-p}!GuIY3 z)Iy(Fiw2b!j3zDe1~uyk)C`N^c0kRZBGh&&{c4OY)Xe4YVW1cd%?z9VFe&&mYOXOj z8i`Nu+Bz1i=i0g_~1}r}?XY@^B zX&t=xIs=cDA22$8NiOP+V&)yv4|7^Dh${Hvn8XFzVL(9h){LdDSr5lgGMRytYE@^# zh;3BLXxgJnH3>6x8j~^b&lbP;XNB}!0RJeJ zz*UL5I*H&P>pwBKvysA*h8b8JbWOahnIUvY_T|%Yv!Ct<@PO zZ>mY;F?D3%sAHHC{iGP!23WPA*8&(XDwQm*5y1EhZq>F}c4`#pIm~A7n4kAbCz&-t z0hHNv5!QDsge)whjR3B3US?JL7E`Fo&e($UxQT?lvNElbVnihwLMfbJ9t?z`L~N67 zC3sV_l{SSw*e^T=YQo%tz?AQT9^1|PM=oqT)2;)nT-~?`Dxi#A5v}@z-((iS`NHpg z`|PKG?(B2Fafw#6g@O$bBQ>q0UJDClwqWgReYxz^=d;K%p6gK1kPY`vp0R1@yb;0= zyJj~r96Nb~+=5DOQAT=~LJ(`~`?7gP$=STs1Bi3Rng}4m1%}jsqPCbGe6n04mKubCe^+kr;B8}8APjr6Wyn;lQ!v6Nlq zJ54^e0UG8ZKXiavxj8|rF))F#@c^A!2*?GZ9BU*sG6v!mb{XOuWQGfF7^aGIQM^Cg zGJE3>OlAY7lJIGm?;6I_g3~^N$C%zwZ>hP3DuoieTZSjtm(L3dJmruDe#n2a1e??Y zb`WGo)}g!)kPV%?=WJcUM5?O7`RY0nde*9g#9})v4unqDv?BLTE5p;SF-gM|3vI%Z z1<`>FSbOL5?pOoFphx+G)nM%8H?I6El9@r`e42H-z5di-SPj|PImL=OhrVsd8})`& z%2k&oSq>di5)N#z?s7PF^z&FgFd^Yu6UI$^~Hoq9*Q@jW%CLpHGZ_}#KJl0p&ieDtxjrBkbhbY1{v%E6S$s0RxXtA| z6SSxrpl6X-^sYyep(wNsxw93)PRfX38$g%Se zYpjG4+eE>-98YdZ0@HDmgiBJW4TusQKq6A6`6XQ3Z)dKn8MM~C0+21U2!1Iz6ge2w z8}$#@=(C~N@md}m?`Fnh9*LIwi}{vP))ZXS>J|;9ZMA4Vt=(c+uBVM34NcuPmI|;| zRHk%hc5pe7%tH?vijvXJYO|!OK#6m*y046?yR6; zMWVTDn->&VF2efnW!sV;r;w|M5(hCMe-+`YNWMC;m)Hbw1+mNI>8pr+o?5H8eAx<^ zU52p>;#NBiu}Ss0bTKd(K*cYHwV0&(udXRQ$0;0xISV+fSq^q(c@VlJC%fSCw+v5u z*O5}isOj>LE5rqGxg9$_ft_-Tn<)VklTWLZt*c5@T9H!@Qjyc%QIhYwNwJbmY}3*V zIas@^qFhC@e!YRWb;KR0s2}c(1UK0-yYd9f7Ko+x(n&qyIoT+BW=Ix*Bxe)#R(CCJ zy;7T3Yr7Z>Yer(tGXLeU=R4Q=esF)2w)kv=XRBK;vjgytbj?9)t$X@>{`NAof!9lmg&JjP4c)Bxb;xgu8z)`bmYu4}2@2IqY*jIFY zuYm^-gn9CQwMeaTrncB)S#-1wNl8K2$H?lZTs+x6ZiU^cJf_&L&B~f8*^7OdWt^h? z1g&Wx6E@l$W9=kru9A-0g;54)pB4|^7hh`LTkYB`P^cb&Xyvwc}7(CZgJTxBm zia~JTk{274!pw`DIa_?2Px7s>$RPNVfVl~u0moPYfL%l|O^g&rOW=r*9MDLuZrc_9O|%5i1reCmZ85F8RBPSEwOWUp75nH+bi7in`~G!qTlM|v z96QEdQs?GgU7a(RF98GVnsvk44dU-j`Lk-^&1wLCBv5AAp0gdzaqZ@X82Nmm=LW<+T=IFnuz(0=t2~&;z|i6idM;0qE)ggTBR@TH;9omM@*7DzRwaln#wy5vu~@a{Ruii%<&)0snD`BhSkvXVU*R_*d)l!|>o0+A zY%ZR}Dl&0&F6?OLwbVb+Z`cNbfV1s){+P0{VvGcYvK1V(v|32Ua zYT;>An3sg=fH_c@heEx);ei;or5lQ5cmR;fCevXdnUoN$h`pKh>sSCqSRLKL5ljEM zl`jGr(k>kcR-mFK7A=47u2SK$(*^M$_>khHW5@KIT_1yeh>aBeT~ zBZGbk4FV-C?dFUj<|4~bfe(jX!YHC5HiH03)W&`^xxKO9-p(4qSSUB70$tLu#k~0P zmB~1cV>o;}$VJGTd;p!j?dJ~J3DU7_fVK<}#)2S`oe08i-`A!N#@Mvj;Jd&mn-(-F zb6{v-x7}|ht1uOI%+{=Z8H0{1@IRnIv*0rs^vj?wzH9LVqK-|#6Y3uDfJH))T3@m- zj6kZciAmz;Whp?}_Pq(Tn#~es3S+$lvS8b}f%}8CVJGOL=64;PMXfx?S381v)$iP4 zyQaA@YlyoI@*ksN3{MNn^^1L8x<~?3Oxa43o-z%yoZ`oI9`lQLQU`Oel~bG9ZV$QT z9nwzE6sHExJqB3==UQyEQHH_h8f-NVZ}u@6tTA=3$zLy46b9dKJv1kqq6cyuE$-BN zD|w$O!UVRWS??XPIr$v!#O4d)6i>4<$UMu~m5W$|&Sf#187;3IgekT-A?|7>z>5iX zp`LleGaKRqIL+^rTcp8V!`D-_&e<7Mk}s|sYBE5NB=XF{z-@m%E=u4p1Y98#dA+3i zyj~6039v10K@vAFA`UHP)t`n?TT3vp^$YMTu2 zrg>Urf~=4Jz$z_($VUlFP^eNLl_SDpeLE7p{~WGp#Ti#cz;7g3b zW-T&uhVCAz`pn+d`*y`*5iCG|FE649(g;#*2B1^EhE&4wj{s4nWA`~zkE+F}Km ze;*wLHxsHnm0nTh_DPj{7p!r3Hc%OE5ImlNN5UN zEwbIyq6H-xmh^hpMjlT#hJL@tj*T^>wfcP+G3t-DIuam_u_@M&t@tzTV%qJ=%?`idf(nJa$`nZB5#RMvj=9G=nF+hfq#!&_ zGE-k;aRxrdYYZ)%C0dgg!bG@&7r7+Bi{2pGT>AWeRDhLXtvAf{fk^)4Cm^@#8xZy{L~7lMPDPc^XZ?a@yUN2+At7$Akx@dYd?)n?-moWM27C zqRG-w?#srdyEJH6?KvNoL||AJTv8t<8N;*<34~5Bfxr;5zrY6jhEI!OTK{h|41_7x z?Aay;F--5#WiZUXW~2+)g;GRj^7bXPsUE!4Ia?A~Dr3JBgF`p9f6!}fpI5<^$nu6J zFTrWc0=uMaJA>dgWwExIA^(JcM!OC>dNf@27ui-KgoVBB-KhDAJZ%C?DcCK0WE+VX z1dH?pV`bTGbK9Knqz+B$9+=$maUkn$l?UD{jfj7BbcG3eb%(8e{^ z*>W6L=-qvHqtL4jGSmqZ zB{o@xx7yJ})KCz@6`IqN>`x)gV=>bqPtf=W+R>Wh4QVU0?)9R3w5 z^(wov55BlceLA`_M&78Db}xoEgiT}m5}i^C`G;ogw&zgq3J2Y?)djXSG`fX68?VHp z(k<%BE7^{twpZ%t%}sR6wgNZ0Wp8<-TUuY2Zt3k#TO#NLuS9G7Wq2iX9>FV-lbKfn zevxjWb><#c>6Xg_KZ2Z;XS{OV+R@UjwO;A-j%>u^l%+^eEw*4#!P?B;@ZXLiyyjAA z=eRX(1eIRdkqp4>EM7|w6)qBG0{F{%)39A@&J`oGT`L#HdxrWN?N7ffaug`ky^abK zGW+^kVo}QQ&#mWnHJHa5t*)f9CM+ex4D-KJ5nNz*dr_M@mCz+{buL zh+AmDyEaaYE3lRgx7LtTLydyfg+uiiOL@1t0=Xn_AD(0nQ2+^W-VXQRG{@$1 zHe<^DJOR~P+6xD4{-KFVnVzmPUR5WJgf#YVgjFirzhMDU>-F^(4)dF`^U96AeRYhk zVa7sn;`DG#f96RGf3`@@F7!Y1B+2!v4`o__jh{V}x7Oy6skkwRrM0jkW)qH|>xK#0 zinLWD(W7h#h~F5@$%6fSp#j7lidy?9eZ4}h zeGf~~kALRTsw$0L2&n(l_DOW&nSgf;M_O3bwzWWa9vE@{AhbEs0jh?gz zsxi|AKCIxX>+Hh{uKI^+?OBza6ojB2^LM~gPG7wtd@Bs0A(&0gUsRVD>!GxUP*MI8 zL(p1i9Kwb;gg?5<5GoCT#aHFL(I3JGy&}1+j$uEF!UtjHo{*)?h0IfjIM>C^YBB(G z^p1r!p~+`($LKB5f@zLCEeglRB6GcZMU*y_)A3pi`7wtF)e&96O>5rNK+gFPwhHXp z=nUW9!MlfrSlpt%pCCn(ro0nf8%sLj!f72{GU5@8JS@qsdE^#q&()9U0tAXlnE-Fi zq;w3;6(aJm%o|!Yy@d}tKZN;-MJw8=D`)hrRhXZ01`-2?L>+rihNCeO%r6z@H!vR@ zSeNPmM9b;{yT_%Q`qA74jP1wmpn`t2PWx*mH2VYy75EYt`5OmA{ZgTR%@*0Bf%6m% z0)Ut5kYkKKpnkR$x{OF)D%39`eYW#pzakUi+r8nVWn%}7%e*0h*9v;I zJK;TUxs&x2Qk~@wsiR@Ej;$WbT=X-_4<$o#9eZ=P_Q0#!-;yNbBy?e&x4MWPYdxe` z7n1F`M_n4~m;+>Qr_3OriSavkL8>GtJ_3#kM^Gk!!#-NrmBOY$VMDRoJ?8BSeL)1E z_1&nSjH&3G@73lJ(>7bTmk;5~t5<46ko7OmWrDMlwHrbz+k?1}k`o3H%rnhVV(; z&-=;ny_zp+G=XrbAOqy4=3*#xjg-<8*@}fB1dE{?U}T_|&7;&I#PaBH3nDbGqp0vb zQ;dZZ5UQsr;Q~u!NHU}U8IgyJNgYa-mofHR>Dr_^%ZZiD>6!~HM0Cy41ZI&FMC3GP zxA9hgG;zE^{$_PHK#lJ+(f;x$*!%po13-?lt^ay)<)OErb?{{ zFdE~K7zaj2U+P3GGieU@yY!fr{E9IZwHK`rAu0jpv^7Cd#${Y6)E?tM)k5Kq2BIe7 zFk5;qME2kwT>b#V3u7210PlyT(fM7&-+67LTrsqSJ9zA1j5VwvScT6MwOxUN{p&Ofy9ss95Odh@3jmjB6D{^0k9@8w&~dfdXZ zD(vKn-9>O8dYJPM7P27)!@i5V%HpA|9V- z#-r;Cn)#fv{gZrVt9k1hI;?D2z)SRtqz+d0yb_PPOoUxV5GSO&7x+k_4XzmQ#UB=G ztJ7VTP|iq%!tyy$bjo(?yXqftDgkeEBK_}iWnJ0TuW@Ca*wtU>Ds-evMVA#JJlNW= z=m;?OJmadKXS3-;99N(-_Hm9=s;h(ZDS9XkchawCA=nrq!oOhyLtfRFBAHKh8=hY3 z=+v6{ww)TMpPiJR>Cmt_m+Dkgsz;ueU7xd-?D1zOr)Qc|RWx-$=2Vx`%vHjgxiAOu zO=*m2)p?zrN9s|l^v3zqIG}Oj=T(Pq_I{`woEBW74P%1^f$eBmIZ&!hf-+dxz>)C0 zkaNr$H4e%w0IvVIbenmgLR8Ph){GM@p@YHhq|Anap zH~zh2Mxhr2RrZI;?&%I&?F(Q1+oZ1ehpHIv|E{JVGL$fKk#Ft|L!o`Ls_K#{)UT#B z)`)dl$*;6Zp0koKOiIv{rt4caHq< z{jO?vk043IU3C=#T2fszHt7Xs#P6B3-nmb<$4DB(vXM?qPnXUGb3>j)W~|czpC<0=yO6&6)kcn-)#CXN5|1doDvzRS zzbZT;XQdnQ2%pEBx2M@tXpT5i%ojT3`b-@fCahu5D(0ybxm;9$w*vJ!0AHPI#&~Tr z#Q&xts;{dKQ8!*KH6Wv>wPuKiPBRHW2kZk4!Mz`T*+b-8p>h|jR@X-M_GX0N)dlo;_~Ti)vXoxylA?LvQo+{PWNgx zS$3z%GHpUDJt3XeH}Ls>VeK~<88?FG4t@gzZc2jRzmN_UmqI^80okqVFYNC1a(;csyltAt$*=pq2}RpytCVco|B?V~Da3y9NGS zP*A57!peI4gTWT10xQHd-zlKm%@C!Jx&*>5LD)(-hp4T|Z$-i`!IfuSJI2^@%}j^o zzN`b8@qNfR&x><63(6MFgT^YRD`^hm3X0JdX{|RJXb2bRTbqX`Efgp~ZYJmx3hf_4 zuq1(M<`78|bQ;#7QKA%dhgaQe+h~gt4Whg)N;D{t*X~q;Gxxu&r2li`tVQq>sR&V! z=a&IC-xMOb9`f$TXf#~FGl%HU(5ThcVunviTZ=A2~_N6$WXCodLX%OP9;Bu5lzTKvT5GAe|lOuS9p07eb>vIqXlwPzDF*; zkx&+V`APjN2|HQ${}Ci^BC3-qm{^}8+<2;k#j7BFGDnlXoYchgO9+$DC;>EKQ}ie; zGUgW|JT#=V>QRl036=?JnL(&>5iHDd$3WsQ%4^M{#(E8n=eeA8O+wu2S{~zyu0!b6 zM;x|#KJ1UYd_B7A%;fY`bE+{vZ6;~rm4lPUqxt-j8ZNH!%Hf*B{$uV8HAi&SUu6*e#)A&F zCEsOel>b&YQ1knC3456JO94gNvu<1Rf;eDMR*=|vnhe@`)SllS^;dUiq>qT|I!8GZs-b8H>ziG8W8*m8xbm)p_nVI$T}gYGPW?an4O2hO_sk*43p~IR03C4JqBZ(na~fr@Pto*vs)MO`qm^h4m{6pPCA@gXtf!{6 z!tz=!q$anHC5lkxrU$$>S9LE`e=(mEGr8qS_*0}%2mD(n zbsi*VS!0_fMR|GSjlFZ%mo+ zac3--*m>|L|5U26W;}caYLq`_Gh9}%_ZZo-m5JoUu?eZ-Rzsv&iseELH)SoW`74V- zViOtE!mmjazgN0^5_#qcJ{hy_eSEfQ^aN{HPn3MV{(blXv9qIA8>=F)=F?2p6hI3y zQxn%j9r;X)$?BsC@2|^wT$qYbeloO}+b|(lRp}U2KjQ)*Hdo`$mK$s$1F%G;;KWL1 zbSN@^img%`DXL#=d3-2It_=)Xj z_u%%{y=SB57VMsitxOelANu}L7^}t*TDBK+i8g8)+e@E7@3SY0>9bFO4zi7+O}94Q zv`E%`b``aOlcHAI7OCZ6uN~;~;qZ;g-=9QU-Ar1{e6q~t(NN^G8rmm6$)MNW1D%YL znPnuKIuP5@TwR|XQO{qq$$XGyTrcB#X2ETS4eR1ybL`JPF{rHyq$Y;BvB=0tEMv&>UfN%#zWJuGS#7CVn25VVek8 z=m>jrNt)Q(rw`$w?_qg^wHnqiY}rCCHv3Gw;Xlyj_kYTBb$Cnm4Acr7c!#Ste=HiFdXVf0%J4+p0sSibPqa zE$!+DMbU~AB;vYK4JI~Rx>gbW;kHcSKr&#pbek5m6Cku<@6AN$nNhn!CiKlE7t!NU(eeDs%s z4|(#)5pe5-T(`On%Zd4f8JX zNkX5*4U(C`OtK*#&G!4gKLqd9+FF_ot_XdQ^}xfw&qpg&nm=}HYEzBIe&jPvtr5J9J2lX}qdw##b>x7w9NT3*_yaz|3T(Yim)RY7D?rHxlMpk3=3bK zb@?v}5$)e`>wUutI4B238-24P$kv#R43K{vLQs0q&jCcKlCeGgWHi%QzTo% zANw825|5z9RX_ve5Vm6A2JBRLxrN)M)gsL|ULy=8)eAN*1%?9=RXDa1QSzg%A3_Ub znyTy->HfK1;fmh&u%PbyGHfQ-8#(mbE7Co)GFOGEKIL&4N z6;$0i=dQU-C(V;;9kEBeq!FTGuy-y13S;X3{hI*^Y&$U~klnzoHN2q(@wPc1A2aF= zAhl*5Ulr95oF>Ls)DN}?s9rPMGH8d>jU{4-Yq$f&uC9Vt@~aEK5SAiAo2KwodKBV{ zqhei>ztM=|MPO(__4lk`L;+orNVpm?kW4<2g!FiL9I?3I8__qRsM(aoR|_kM5S97W z;kb#t!LS_0{lc|!6RPvjE}?m`LZ#Lds^_VaQrI-9I%gC{(X>?>WVj)J??u;) zYCR8G&;EFj@l4APEn2HcH&+a_Bp3~~0Pz|3EJPbpldCzywyV|vE`KA)7gq&TqUazc z*T_byk{(^~BEo+k_*9zd0-7mmU&b`NdnDO#o=3U|)C=9VsDIR~i@LwI5n`<-Y}|q9 zql%0+thx%OP~&ur+aVA%qoP9|C}vA)7E=@FoQNZh5fSL0%NL3ut`$llgT|hqv?e2T zXojhg`uwCu7~HobxljGq(DC2V3{QUsnLXPtDW>2C1^O8iLHdzzZ0ahZHUr!yPu)^ zNp+7hrzzDPjTFpO7QJrklb4@NGyFQhT{5zfetKlJl~t9Lv;q>bx!AIzSZ85J0`+S% zhlz{@ElbsJ~X% ziBS}o7fk#9G@gt2MHG^z{*L?%3N)~yBq6~kfaSG*c?Y1}n+?t&9KC6sZ!o+ItK3He z?Ru5VvZoS1BgImzmI)ruaaoYG>@ge#1JdUQO4&aRibA2&cqrLHuzNT9yHN31tnFbF zy10k2Es;-|trXLXk>4IR2E>fU2~s$o#p@dw$T=;U0Z)ia3j92o5mNA57)Gj#2g7n8 zgP#?H+W;voG6+B;G~;YBBcNtwCLs)dXF0fa&Z{&Wr(vl&UJNw;pa^^6`oaFq^C9u9 zxJae#hqof8NztzX2TbmU5%dIwoxfWnrQ@D1ZmdaV(P<%T1$E^*P(rhS9Ox@kM?b9q zg2ra(XeLlII#@Gj&4w$6O6oVRsJS&l!?6xjFY5jZ`arcqr4wB@u-<<+^jC^b&%0;< za4pj32I_X^AD|qafbj;^+%g{N zvc$5vERK`>z<5}wXW1`$tL1FbKY-hb)4vMgW>p>*GrRp=kRCD(@PDY%-(ab?r+x1W-f=tXkV) zOWmPh=z)eVtR?Q4Knul&?ExPjP}5?Be|C$Qka0k_-PagFZ1TT0-ToqJVC%pdAPzWBh(%>NW@^nOCWUsQjMn9 z3OwfDL`M{J-LV)6uUAF9!mf#s*Y??1_sMPO!kRHHP(^A33J5n?wU9wIx=pbOMDr`^ znpmvSw_gDpr$J;;75VLg#)3sjEUY7ZjQbLEgk+%5E);pB90CIJq<(8!%@%XTF#n)+ zN@3CY@mw(jYhjv4vOHjbh13+q8p##UOMcQaxfSY|`1t%b%o2kh0wXKxf@ur<|S|!}37oQApIFHss)X%5k_WWnD0yY5HB5NUWMAL~uN5xR(tQhU~H%a6m zjMT-=D+>;b227f&j|HjTE6Fq!PR?LIod*2Ptd@P1)e|1B)eLq*8arc{VuW%rO^wFJ zi<8DqP+?4?r+o^o!p_9@ZF>~9wJ)rF9Gv!1LolBJhD7G50c3;zBxoY+Uup3f1 z8Ygia8ygU7;Dk&G#huwg^VbVmn0U1{3nzy25~PN*~e{ zelN}Nu5PNb{T6?SQfV|MCqlF)a^{T}&^NTqKMI?xfGL6a(BS}MBw~bFG!F|vZB$G|jfOATUOR?&s9yE!N~u^y}sx>IY$`cPZ_ zu*X#PE5KXpgKL`qypRVibs3I9f6Z-76bafFI>!U)7!;9M0TN-3KFtMvaEuECJi-Ot zy%w-XVz-7FT6l{MMV9{!dlu)Ij6QrJcBGg{y8ILq&W%%G22J~G zQ9V3iq(nBuh>5W=NtVrs57IKNy=6Cc`A4y4tmcQ(gHG5@58z765T+4RPC8V$N=y7q zl}!^y&WSQ-Is`VCWlRr1haQ&E7fvkcWf>_)D{JDda-)wxp+f=7mz@xq)=}?>iunCN z*7Bb`I@wD^=bYdKCS?Z%)Y+Pr`JL<6FU}as-;P%Btm3O;URLl+!RnQUg}@0}+_E^RJZ=@NzQ=0qmdmpiFsEJjj2MFy;sN&Y;^ zp`u!tP9&3B*aD^_)bfyK)Dz^z1*g>&(X_YsZ_<5CPZ>f84klS=B6}2YoSK4ZFH6LW zorTEG(1uG9u*D$goErMF7}Y?+sL9@sEkBJm6Z-#Ob|o8z1+lUNKG1s@A8;VPrJTR2~)qL^>~%CSQ{V6ddSM@Z)oYpE5`mXM;u*Stq@% z#2iSjpW#%H58sj4AWlbINs_O1ljMxr zj3-1DE`Kv}8y&7!NRmg2eQxN7a>!q3=|vncog~LGmWHZ5L(95FYha|=*ytx=A&gK@ zG6ev%>|e(uc_hs_k)} zmn1(0{*};BDkG5;udrOZmgzvExTe-0&f8KO%Bw5@39hKL^ zZ7%lFZ2v-IZFB$HQmdhM#Q&f*5lw3R52>kB<4R+H7l+VHeW<=)w|$m@BLwa2is(Z1 z_YGR+*;Z$)1snCJJ4|-oY6#pKwo_L3J52IrMitjT3wqA^bqVc%i0moyWrlLlj?TCQ zV1|KGToZS=7#o*7ZhkdNj*)LHrtXSKLQO!?_RJIr-GvH-oeGeoaU~!KUX+cgzQ(6RL)O^Rs`2Vbr zX_h0T!S|D2wC?lDvh<62WmXibmm8G0Rrt3dMygVIP!0Mj*8)q&$iTD$J;c*ZjdV|6 zW{!MJjAs-A+2gZy645g1BtG~fqdub`u1VmKOsE5-nEzGQ7b`ZWczgr#xT)w#Vn$h6 zVA9hW3TI&MSLQO-~?>cS?QczZNqB9fe}(n5iTbk9?3u|iWJvRg1wSKz6t zWUye;nX&^6Tt|uyW zYy!num6g)I+fb4amJ%XahAS<#p;(`|IA*0_@I`HJ9E2M9>347;2YNeJiImh(DU|^d z)TE@eo_c@doF89ZOU@g0J&QMBZSy-&HtK{0Bwf>sNhxiYz!SZgl(Ot(ns$ZVVJC)50L^t zqxK$wgFaVw=a)pRAy~2>h`y3$1aKlV6b{AJ+XL=PDBj5a@=ysVazMznyixfBr+hK1 zRV>Q7&!XasH)t}u0gD^k8%S_fJ1q&(vkXw3g43e>%olU{M~XKTc@65ub`e)i3$XOS zqQN(Vr1j9=8!Z1T|A5u;iz03&sy*-MewcayK{P=Z*G^o79R$RL+`6?iEt}Yd$Z2!BPze~ZKv=R^4kW&6ml1%>Y4j2MaeJUwr zv!R}k`8#Swa5bW>3U&T2jk_DBmVpA4w8)TZ>uOmOO=>Q$X{8%9`2k|g;UwW_TVWn( zq9IhSnT0!`qXzi;5`=id&~9NRf$wd5fX`ZK&U4!XKPLl z*cLkl@V52mSS^i015OkS^L=&8?9Y8XEvCZ)2@6Q7YgPy><)zDtRe$nrA_vOLfAj~> zJ@Gf6JO7P~m?}GKsB^K#O4>TWu%yd8QLjaGSyNtsesx)wf`M#_x6sa(xI_W5wu7r% zSc(vUX(BJZO<7ZYZpgniQwJ!#4Q|Sc*ezQ3w;q zCVq?4(i|Sgsa$L_HUPFgYymL_Siv+dJZ_T(yhmbUZwJS)aoJw&w20RVPRAK4`B_-W zcZ(Fcojl%}i0DFhd^D>^I!XeAEL)nwb`Gf=MQ%(W(6Y~BB)C{p@h6)+Z~L*4XO>72 z;%522tS%N5>$5J`^Q4fwc6~O4?>0mK@^nl}!omb5!5qQK`(rIo z#Lri&*|C>exb&}Z^Rv!}B<=MF! zb4jvlz$IiA+g9k8Y9^kslZq&O_P!}C6|`5eK|sQ<8&)cxKUSg|Du)T*hDO=tydFw1 zL?x5`S`zwEgqBw7q$dgk50a4H!q=oK6sCsiN}()NZ*9W)P|YiC83-Y_HTjlVWi3PX zuC_+$fNOVqTv!3{=3NfIkp^#BxzV}ZKDV2vNFfj9Dd~u`Cce7+y&BAasu<&JLqQI2k z9K|-;aA@0Hi7txcTy1TJv5oXq+lC#9AzCROy2pjiKc=-}DeRa7J7@IV<|5M3h*(2B zB)v!g4?q!-Vrdm^_TAL?6(CR2&yo*xqco$rDNJk0=jI-MwvDFxUdkCoX2N3!kr{co zl5Z;|O;%wNHyP0sZ)e1K41zxGjElgoG5$)t+&C5su&+}lOoqL({3SGxGw5EvW^Wqo zH8}v&kQ4f|qdNsW9^8G92wynP)qpL!$w%p6!YLB%4(QCU>I~;VDZ{Wq`+}W>uLH#C zmDFpPKtBcd6vUT@^#Z%}Qz#a!FH$)L>kC#9_nD;2s*6_9VwnnCa|^7io=jFEeqD7w zti){-SCK4jYEM%HVT`tnzFOd7cjo25O46vyK7VI+GWec&sBbt{9vx@~l%}A(OLe$oP>eC>;Bd*SsMGT+9~h<4m`ker); z^`xkdqrIlfM@@b99O_}rTT{Pr!BT>1lo{f99(t8R@j6Vgn ztG;3tI*F$V|Cl-yr-~P}C@ZSwP9gu+%5z?Hd$^AqE(a?ZW%F2Ife_iVL0C$b3NKRz zLi{QxJHis=>CY$5rvzDco`ta%!lC@;O6Pk+#Uuw2fQ9h!7z7{EhzsG)ajLO616lNR zOh_(4JZG4Suoyu*7s%;Rx+P||GmC@l)w$GMOF@2u(X@~W8ZCfW|42e%7%plIb67cI z8dwSev?ae;VFRe-7cv<<5kdI3YrdSd+}l9b&~P{?W~2HRy*NMwbV^@%(iiUzF5eD{ zY(t>LTyB9RV2t!W=)!&}>X8*K$u&gRfI%95GtHv7D>BF_AC^Q4`h`84H5xpRcF|M$ zDxxL`AYhY&#Wc;PAIy+va(C3Qc3A3$AedfiaJ|vySue+AfoShQJUgZ53*i)FS~R8X zn>;6B2(H^^Tz8@2y7TL~4lOJ6F`0mStz7q`^W2W=N8>q8biE?aExx`yXBuAwCTDBX z4Qe+Dzt`qFUNg_d6dN1XH3Pza;66^2U#D3b(#Z|%YK~!nS72u)DV%HHX)%50s_$9F zUp@_gLiL8(Wzn#sH}JhN&aX*tkPv(&y>UyUH*Wb!(;IJlb$Ubb#)-r@)KzO%Fbfl{ z6+WBvM!knBy-}+WS*v=#hZ^mJ{NHdi9rIQ%=YRXF1CTs(2i z`=EcJ-mu;Om`pq5!{hSCiZK)@@RtiN+BYPd;F~;xZaGTNz~Lh#7ifj1QDt6Ys$<51 z$~;!;gi|2Fup{9O(Cnj4HY)BC;}P;gk;JUg?leyov17QQR_K*nQW2JH_j5F zuNklfo>!c7;2sX{9=Nx+rbZwX$Spqs4{5nVTK!U#(Y9Vkmc563Mf8_K%p?apXC1u~ zsA66KW62$np9hE>FHl1EBVFh~jCuQQaBK<4j;kCI3J=wxERfeFkP12$8q~guMc3Qv zKc+>qi!)jDtAMV42ja&B9fxx#(7hjqw>QO`Z$u~v8HOcE1*j)Q9Y5dzGxMH~&z9>C zIPk)n7=&bL%X??|>P3vinm68%OykemC#Vk!ScUn z9>dS5Tze1K(*~xuO-~D3cSg_gX7OMkfNApI0kLcH-`XeCx&T#|T+6G*@x3}?#U?c# z2>Z%G-B4lPu+wQ#lEVymFxr=~W59jcuh;tqNW9KFf^X?7 zSA9YI7AlZ#MX))wg0$4AW@OF<7;VZV|Eoa40~|srHdIz_>jcKP0)Uw*rCOtm*XM=e z*o}3Re$z7kQW&86|B(US0_B>x3f6S_{Tng`Z2A3AkXT45_FwZc4+N!sL+NB4rDYD3 z&54v&C1tNqSSvc&-e2p^Wv zL3vb7%M=}z^6fVP zhRL+c?f`=7c!g=MaE_9t3W(Wl*1NZURcnU5aOTBjrXP4$4h3sql!t+K`lm;+98>ZlXRU!f57ci8gss6W{#BTg}N5 zKR|kQ5I=3<0>c%tU-WA755b;dJDxWTX+Y!S{ZAh^8L-%cS`qMC2rK0<&|Mr~VXerm zIFS4|i%YRt#jqe5mfr;xI0DBuQNeqH;%~}4P?_|RwukzNKZxK3$sR`p;7vB`kt*ghi~RccRROHa=@SG&b>Bn|qrFpS8Y6)5en1c?u(r z*e=O`0bb)hdyp)=K_;MxCoE)6!MeRF9$E1_uPFMLu;NXxFDw4s%d_H{5Lpp~r47Q& zu}DiDAtM2swoZOTLkcO45H(tKc7yvCVcN-wEeCc0C2}*^0AT|$x!!4vj9eoV#uNcz zWN@i6%qa$!9g|Vh+0P(G=1qJtGK|kVEjn%jBU{c?*;8X=dz}?V)>e^%7dwq5{@`qw z9Rq_{@a_K1@a83q3=fNu;TQE_9b)8K!=`yeVrqOQi}BhSd1r@_*|L~oXTIrB3Gl;jbGsl2J3WVN8$gt z8S>7TXUHdNhGgZfZq)g~_?oS$zM;uA=0H?#*KVi&^ulfAE!zU%QA{mw(N(-f zTk)2i{ps>nPp_}STXqD#c2Gc@RsSas3dW%Oi7}J#alk| z7A)9Nl0J|4h;({=cx%@y@YWYR-bg^S#7IDG;s+MYYyjG7i4;ms;QhJ`bLuFj_{y$$ z0A?`id-0)!-pLa{zc$Bq?Ovf1I-`A#x{xy!@3q>GzAA8JzcjtB5_Z|I=3Zpc$alV~ zkx6XXNV@8M*=A(jC&qBsXz<_lco1QU6{Ed$9*+yBJystnEuG@;=S3YJ!2uuGJjWic z%feDvs!o1J=LbvhyaL*!@2aP)s&M$@EC`2LPaq52QgL`BS#tPv$M>1fWUIVoZ)>LMiv99F><%;wo?ss;QV?Hs~#|!bO2d_|>Z^DeJ4@=nhV9qfZ zurNaLMwvWQrM&NrrwM2rv4~qqcErL$744}vrz~Kuagdx>ejM!6T}%x!mWvb5WMk$t zm!JsG74Es^;b))Bc0w$6$(uBWnU(t6VPmG#`Hh+S>I43dzPTxHm4>#3iW?zU@1S*D$(Olm8&4sKwbB5*pLYr{w3W&4 zDM)>CEzLhp`3)Qb)sru;fvJ-Q>h|#Tq%9|Xb6~Jj85Yn?4Z3C>sxbk~v+8q|9hr31 z*EQNH)sVpDml^XV@JaFYTf+C_5~Pj{V2%{qQZ6)0sh6uIyqB5T){HUPrAyG8BJ~nB z)l2yE%Pir&S^y_cJrxU07u}G)(xGrl8l(_thj4xbp`u<^ zuku06nr7w9NsKAKKb5PKlMq=;@~Q=KHT03IOO&gdHKiL7mgqtPm7G2Yw&iQ<2cK5C zv7fPvDu&etj<=FQvTuC~k(71<8yCygCCb*dCsd0z4JY*<6K{zw%GQCW7)2{vS7+!F zW$Wq;ogI9g>2z9@b&e1zf@yMeeEX`-)>-noc@f55Qf{(!=F-#KvUR2*m92{*YD7vD zYPJUK{FEJlj##~`)j_5br8fCyZT!^N*rT2BlO4F0m0PDnHd0A*HYJ#YA9=|fz<_q< zuZ;3fa-KQFH`p4LJ2n0`=f*BhVUu^J;2WaK25tltn?kf552CW{Np~|_8K{hz!usoQ zk*atjp-v{s{s5Ws9`>m}UTqh8YBp81-!tZg27Pyf1nF(CACWK~yD3{8=^(I+8J3NP1TXB3vKg^-}AO-RVVlO!!4y6i)W^tGA@xLA+{@=&p$ zT-!Pe%1pQ?W_KVg;%&(mOI9HsQeV2r>}T5`pIlXu+WVCS~Xu=bdBTO=KuiRbT+{O|58Jjy!q@ z%I6w1g#?4XPM>RN4!_yG&30Ymf%yuRM;78;Ew4A9mWD12Ck*kElHX!3O(K*THM2uL zd0eWo7}HP(#0uI_Wa^T0!c@>#xApd&&izse-c|V{Y~Q^z0yQBAVkEVyyjt{7SQ;k? z@8uGgF>+*E9~M*Rd$Zr%60+loeK(gEb`T@6y#2uwN>`0oP?-nWcjZqW} zVJTC}&c6981yDUXlqLj$eD8PA7M~u}yC$}z^TaXIe5dN`$@+Sc>`!%8%xW(y3CWRr z^qo^c!xn2JGuEB9I-CUiXtvb}tf#^Lvoc>7`__k6qn!M4^?O7wR?9es-0ELRZD~9& zXumD_ZtuRyoqZm}p8XT<;@G+^cxV4Sj4D0ybzM19bW+!o(CI<07x+k%9Wz2UG`|5S z=6N2#v~UCP4{ZTZqWZ@*Kws;%2wStFiOvtxw)UJAt@do%V6-~Z)@tmN658h{-Oo*m zXWOD08Y4hXnu{G4E9IBDG3h@>!Pw;p1)P(HTaM^$!^)3cSmME1l;J()=ozK(bUyqaq10SzNZj zH^l}2Grk04jjurZ>Ra&(w}$8!jgP+3sd>R_zCNjeH{4e_5qptno$tTNmHYoAD*aMQ{pvhThYw!0soG*td7Ch3Be^;Ke<0SBAEtuUHU!{CT455{~A5-gER z57lqB!RxiG!sy&At9VB4JSO{9FN;rQN;R-u+^p(7?W+D% zs}hS|c2#=7s`%}S6kJWsI-aKH|B9nwEq#Q6-!4bLXf+*Er_G|pUX%T%7BI`^ zC3&yV*wh)ImQB!OK4obpm84M<^I%wMJIe@wTsAKj1I$)HRX8mOfmG+Q4cIRM?5c`e zlM|@t+q>4sGVAj__Vm&ofY+}e*0Eh3ubHhj%O06q_Fay0Byqz)T4-d0E+a-(ff2h{ zJ#9t;>jp&62aKfsV*utQ&}=+s2AI#R1addLM&8Iym&Q?cx-^E>B23+xL_f0zYs&k4 zO=vfB{;aY?X7KrCRqgkBa5^*IycmtLWi)j31XvHPy6n!Hsp+zNbi+FAuC+yqpx8SHi5r?l8=_Z-qmFjZ%+2W8a6Gr{$84pKgev^U zVyS)I>vmPEDVt2=VHoE@qeLLUSBOqptMTMDYvX6U8rFx8_lB;13DSbdQ6` zlR6qj$K1gP$?r%jEx1*^)MdVHX9DSh$pq3B>nDgfSP1vor##^n->g8dC^hRk*A`WC zb^_8_a3`*Xd-(;E)%DT;6^l+5Di)r=Ar^1ps6J<2(LW`>;_Q8CBM(a#_O!6mgu*8v zf*eVH!DKrJsO`)#wN`2Gc8(gErj7proh<8N=z4~`*@>d8exJi&0^@Tqj|Bca8x3M4 z{A~BPtxn&iaKB;2nk*NYDstMRJCX~+%>}R$iF3L|oFa?{oe-if>LL=6+mb%CcX>8gv|by$bHmgLQ%qmY=qoJU@&lVn*UyNvS2vQ8!!FHR<(!eVt2d{3q( zu zh_P<1N{mOy(9|lK9kjYcq|&JA3VE7ng_{2xn~SWUmH~*QMsyp2I)Qo<>fqXTcvy2v zuwG7y)UMd6iBXa&Z@+GVC&gWBZ3U6c<{5&!I^K(`LjnLmSbDVNs}gq*_(gb-u&EGT zP=VoYh+YY{X!Kih*Go4U`!_&f)?ZjM@&6B2gXQn20Q8ZmfZs9I%RiyQKDKcfD&Y65 z&Q!p^6sN6&nQwa>vw)Mm;k^dA_`V^J*|rXegpd|a<9b-PgFbP-xg#x6U$B}EitE)i z0w9bc0pRUd%TY1^5sL|@q=D#2=BpqgGj^P?)FN{U?VsSpkg?^3VVxY;W?OfyxtfYq zX!^@B!_+2n*JOj0yfueF>07JWqn7EWaPv)k?)I{EIVu-AMZBCdq&B*YFG_) zH@tmwzPIvCT8egdf_##3tFy~w+3bDx1xpa|Haoqg!+Dn-fNgVY>^&Jb<8OT#S0bc; z5zVr2L;SNWd)7Bc-j{G4wGj~0iyX_2=P)cEZVAHX-b`P6Vnb>CB9K*U+vD^r;n&{p z)cs^UXjqQ~r>Il_;d$&*!oS(pK)7UxP%2q@e!xnzZzj!~o;qKgs*X|7q{>#}?Z!cM zz{H$gE;DT%?(i(_xNz8(De2pQ5tc|(bDtkAM+eFANi)YLd`i16%_7$8x&iZ&eK)z_ z^BccPQjAk3T{v`zsRP0+w^OsnrNa)InPBoSnzm`Lk9~}Or5t^PP)fTy%EzS@FR)wA z79gGQVmaJ)*W5nItJP}}_Pm57x*-FXR&N)5;zPT?N%yg{-S5(Uq@V6}=zAaCNZ5_z zMU_TW$NpOWATrle*2c~^7d<7)x4W=}6x!A{+4y+y6Q2S)>H}#E5OeTsD3EBk+`6Q2 z=j13~5%%-_Yvo`iO11+oo1;AjW%QN6XMA;L>g*keX|km420K4ND-gs_t@cQVuhgIK zcG-#pHo@RmflIx#WyfvZm~@t%pj00w*Tuzuv4Y&)YOGovyV#KiK*3yX8Yp(R4q5ga z-b+KI*%$n+NAJC9ao4Up>VK}%eZhGb|MPC$vt7GBarEH_FR48Ca9vd$Idb%2{`Jdt ej9VCM-LqZ09{$CL4j;Xw_R%AUANu&=qyGo`4<;r6 diff --git a/substrate/frame/contracts/benchmarks/solang_erc20.json b/substrate/frame/contracts/benchmarks/solang_erc20.json deleted file mode 100644 index 9d8fd5ce70e..00000000000 --- a/substrate/frame/contracts/benchmarks/solang_erc20.json +++ /dev/null @@ -1,581 +0,0 @@ -{ - "contract": { - "authors": [ - "unknown" - ], - "name": "ERC20PresetFixedSupply", - "version": "0.0.1" - }, - "metadataVersion": "0.1.0", - "source": { - "compiler": "solang 0.1.7", - "hash": "0x9c55e342566e89c741eb641eec3af796836da750fc930c55bccc0604a47ef700", - "language": "Solidity 0.1.7" - }, - "spec": { - "constructors": [ - { - "args": [ - { - "name": "name", - "type": { - "display_name": [ - "String" - ], - "type": 2 - } - }, - { - "name": "symbol", - "type": { - "display_name": [ - "String" - ], - "type": 2 - } - }, - { - "name": "initialSupply", - "type": { - "display_name": [ - "u256" - ], - "type": 1 - } - }, - { - "name": "owner", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - } - ], - "docs": [ - "" - ], - "name": "new", - "selector": "0xa6f1f5e1" - } - ], - "events": [ - { - "args": [ - { - "indexed": true, - "name": "owner", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - }, - { - "indexed": true, - "name": "spender", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - }, - { - "indexed": false, - "name": "value", - "type": { - "display_name": [ - "u256" - ], - "type": 1 - } - } - ], - "docs": [ - "" - ], - "name": "Approval" - }, - { - "args": [ - { - "indexed": true, - "name": "from", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - }, - { - "indexed": true, - "name": "to", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - }, - { - "indexed": false, - "name": "value", - "type": { - "display_name": [ - "u256" - ], - "type": 1 - } - } - ], - "docs": [ - "" - ], - "name": "Transfer" - } - ], - "messages": [ - { - "args": [ - { - "name": "account", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - }, - { - "name": "amount", - "type": { - "display_name": [ - "u256" - ], - "type": 1 - } - } - ], - "docs": [ - "" - ], - "mutates": true, - "name": "burnFrom", - "payable": false, - "return_type": null, - "selector": "0x0f1354f3" - }, - { - "args": [ - { - "name": "account", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - } - ], - "docs": [ - "" - ], - "mutates": false, - "name": "balanceOf", - "payable": false, - "return_type": { - "display_name": [ - "u256" - ], - "type": 1 - }, - "selector": "0x6c7f1542" - }, - { - "args": [], - "docs": [ - "" - ], - "mutates": false, - "name": "totalSupply", - "payable": false, - "return_type": { - "display_name": [ - "u256" - ], - "type": 1 - }, - "selector": "0x18160ddd" - }, - { - "args": [], - "docs": [ - "" - ], - "mutates": false, - "name": "decimals", - "payable": false, - "return_type": { - "display_name": [ - "u8" - ], - "type": 3 - }, - "selector": "0x313ce567" - }, - { - "args": [ - { - "name": "owner", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - }, - { - "name": "spender", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - } - ], - "docs": [ - "" - ], - "mutates": false, - "name": "allowance", - "payable": false, - "return_type": { - "display_name": [ - "u256" - ], - "type": 1 - }, - "selector": "0xf2a9a8c7" - }, - { - "args": [], - "docs": [ - "" - ], - "mutates": false, - "name": "name", - "payable": false, - "return_type": { - "display_name": [ - "String" - ], - "type": 2 - }, - "selector": "0x06fdde03" - }, - { - "args": [ - { - "name": "spender", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - }, - { - "name": "subtractedValue", - "type": { - "display_name": [ - "u256" - ], - "type": 1 - } - } - ], - "docs": [ - "" - ], - "mutates": true, - "name": "decreaseAllowance", - "payable": false, - "return_type": { - "display_name": [ - "bool" - ], - "type": 6 - }, - "selector": "0x4b76697b" - }, - { - "args": [ - { - "name": "sender", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - }, - { - "name": "recipient", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - }, - { - "name": "amount", - "type": { - "display_name": [ - "u256" - ], - "type": 1 - } - } - ], - "docs": [ - "" - ], - "mutates": true, - "name": "transferFrom", - "payable": false, - "return_type": { - "display_name": [ - "bool" - ], - "type": 6 - }, - "selector": "0x2fb840f5" - }, - { - "args": [], - "docs": [ - "" - ], - "mutates": false, - "name": "symbol", - "payable": false, - "return_type": { - "display_name": [ - "String" - ], - "type": 2 - }, - "selector": "0x95d89b41" - }, - { - "args": [ - { - "name": "spender", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - }, - { - "name": "addedValue", - "type": { - "display_name": [ - "u256" - ], - "type": 1 - } - } - ], - "docs": [ - "" - ], - "mutates": true, - "name": "increaseAllowance", - "payable": false, - "return_type": { - "display_name": [ - "bool" - ], - "type": 6 - }, - "selector": "0xb936c899" - }, - { - "args": [ - { - "name": "recipient", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - }, - { - "name": "amount", - "type": { - "display_name": [ - "u256" - ], - "type": 1 - } - } - ], - "docs": [ - "" - ], - "mutates": true, - "name": "transfer", - "payable": false, - "return_type": { - "display_name": [ - "bool" - ], - "type": 6 - }, - "selector": "0x6a467394" - }, - { - "args": [ - { - "name": "spender", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - }, - { - "name": "amount", - "type": { - "display_name": [ - "u256" - ], - "type": 1 - } - } - ], - "docs": [ - "" - ], - "mutates": true, - "name": "approve", - "payable": false, - "return_type": { - "display_name": [ - "bool" - ], - "type": 6 - }, - "selector": "0x47144421" - }, - { - "args": [ - { - "name": "amount", - "type": { - "display_name": [ - "u256" - ], - "type": 1 - } - } - ], - "docs": [ - "" - ], - "mutates": true, - "name": "burn", - "payable": false, - "return_type": null, - "selector": "0x42966c68" - } - ] - }, - "storage": { - "struct": { - "fields": [ - { - "layout": { - "cell": { - "key": "0x0000000000000000000000000000000000000000000000000000000000000002", - "ty": 1 - } - }, - "name": "_totalSupply" - }, - { - "layout": { - "cell": { - "key": "0x0000000000000000000000000000000000000000000000000000000000000003", - "ty": 2 - } - }, - "name": "_name" - }, - { - "layout": { - "cell": { - "key": "0x0000000000000000000000000000000000000000000000000000000000000004", - "ty": 2 - } - }, - "name": "_symbol" - } - ] - } - }, - "types": [ - { - "def": { - "primitive": "u256" - } - }, - { - "def": { - "primitive": "str" - } - }, - { - "def": { - "primitive": "u8" - } - }, - { - "def": { - "array": { - "len": 32, - "type": 3 - } - } - }, - { - "def": { - "composite": { - "fields": [ - { - "type": 4 - } - ] - } - }, - "path": [ - "AccountId" - ] - }, - { - "def": { - "primitive": "bool" - } - } - ] -} diff --git a/substrate/frame/contracts/benchmarks/solang_erc20.wasm b/substrate/frame/contracts/benchmarks/solang_erc20.wasm deleted file mode 100644 index 0796085d33249b78871f4a336623444a0ef94e85..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12505 zcmdT~ZH!#kSw8n;X79|scjoTwmlFdyb8RPU(l~aU*!d7*kHK*xsFGA>QS--HuO}a~ zw%6TVyD@k@tFlHFN>!^WYKw|ULkS3FMT?3A5(T&AqY|RBL=i#o!xbP!5TK$Ftq2gF z=RN1%nO(0pPLm>tm3z+Id+s^!dC&7c@5fy=vv|r=N_jsOKk4^+>dBzTt5Q#TJ+CLf z&?FZJ{=;t%LqZHhgO_k4DxRZXEBbigKkvtt#m>yhy)m!HW@k>G>?|l{?~Tg6XJ!_k zIX2gsot>FGcF%nWlxH7i^6=@-(y_&*`GuLMJ1QJ}usHC*y)iJ|@sl%ioqGnSYsqvc zI%nn=pItiEIoCP8q=xK-_R;Lg&dfr&R@FYL%UjQ$K67@d5ZZ2Jp|f;$;dCMExC}ct zbMkEG*wVtx>BXly3k#hSN(G(M=c=bVr{)*V`(Bm>X`-UQ4}*H8R`LC61SOl5l`tL} z_N$QXOsXxJv%9yfk_iD=2*qCW^iDC zSslbLcwX4!KH8o?g`wW0pB%<2XrtWM>e1AX`9tL%1U0G++3~B5yLf7by5_kL=KfqB znr)BrHSKpz*3FfDCph3^1Gpb(g)PNe>Cj|z2mFXK9q<#}@VHlx&(Y`3BP?ieH$jti z<2{PvakMh4Md$gV^O2SCN0rHz%;l93^Q!nIPx(_m)MTzGO5~e2-&F5IN1^L48E@es zPhoW*I!m|+}sLenS zfz5{mQ2^QSj)P4=*x)A{=)J@u_66nRpyE#ke$ewS?oj5Mn#*fDm9CkoZXV}#bYpXW zR|f=jZ1#4wZ+nwn?b)90YC`^yYIDT4?jbRNm`$doiO{&gS7hqB%)<&_Q(ag~J3;rV z?FQ(+V!OVs?GC0OZ=I4*jymNS;+l2JQMEnDQL#P95!&81CuN;tXS%MFgF%EwWT*o! zSVng<&wMR3ni{A>$$-g^*p{J#;+F#|dudY5uBMxgpl&{?s$MS@E(sPly_&lkKKPx@njfzWvcZ zp?wi$JI&rLhXD8qb)V!Zw4anHNJFbfQP7;Y7En??eyt z<~tCDhap;NaEVx!*m|*su2X`Bu2F)9j;vS>qqD%YaW;u%?!??uPB`49%FNnm^?X$_<}_{qz$8I zk=8`dB5ed5h8H6z3N!`i&{UgLWJKuDVl!&EppR%B5e$O}nwAJG0W_`Qw}%}Ov@Aiy z?aaUE({@|ac?|0vp62E8?RlcBx;dSvdPrx}yga_0cP_DeY4x$~w0I%tLdyFcLBAUc zYh0(Wky2r!rNYKag^d#kg~IBDR;GuRb-K0;NLH6~nB7|5Ji4|lYUJ~AtI#p4P#7o` zDq61N-6jT-vxjwMS;u^h)ZNmdodDzylfmlcnm5J)UDfr61mBGla;Ma8;iN!@xw#gA z4eJ)Zbw`*{pD@FM)?1F&o9;A+3tA_xZc-@g%UaYEPr6W56Id9wSiregc zjiP6XXbe3|MB}93!q12+6T3;Y*iAymMps6=DKVa`Bcf5jd~A@2#vBoiI_ORmL^L8q z6zPd+IF*_no#q8ZL^~Xt&W8bok!fB)M7VQ_-GYeH4USnN#qTvn7*ts6x)@=+RM@ss zVYieDyH&UP3KPc+E??Heu&yy(U(QE#ww8}MYZ8UtN_Z#w7Ggfp+pI!G%MCrE$6!?S z*dRSnnI)ozp3spHQ9{}S5pCT5O~xhGM{R0M-y>couEWcO-QLaM#d4wpOnbs|;_c?L z4^C`YPHZ?%O!}NyDL8S$apIT*)@Z?rg!uXp^82Wtw}Qt;d)n&wBRzT{2NbjbU>tU_ z2AdeUa3P1?P2wiZ00Y3iBQzIWz||LWSmRy*1O9kKj~`tF@W!XtU@a3DE?lsu!KkUE z=)_X=|AnAWDEcjfKDx?X=JU)gZVU^I*YLDPOJ1F6LlJ%tM*+C5*a8MJGG<<@Hg3*>l z5Y3RNq>0dwfa&KT9&$gmJrAVJlT}&D@KD`=nu9E0?FDlwY)h4MA&ci+(jZAa#C!pB zQ6-qmcXKH_7j6vBg=D~=f`G7hoR`MLcr(?_Rd8#==BeenI&YhW4GjtA~D8q>YQa~MoyFv9LwY^Co-u93`Bg5G0EW|g; z<~%_yuuUj>QgduqOF-4ov-qJdET)EtN*s2n6&n#E@RH-US*eoXum~pPtOg4lC~mZC zHk_~p4wNaJ?(G@W2hV^g_+Q!hP`Pm;+HQQb-1yja8;3iDmKi<)=gi%KbE^#>qz*X1 zLus7+kggx?!vTr-q~HZ`fHE0tPxB&hz{9;baU3vS0uZWt2-n(rIP_UxS=Lt!%$Y|5 z5eUqsCZQOZqe#Jf2+Sq8W2zrmyObu<(y(6aC_rL&mWYL6TtwWtBfw^T4 z3Cz7RFb6cCFA2<14@1`m<{krcWn5r>o4LYZpkxADQBq|vfRW&EA%t>_a5(NUTu)S- zRAV*p&wx^l$|4oN@xy-98)li{1=h(C+k@(}KGD|-QvZImmOw0YS04+f-masuGC^yb zxEs%eb8Qc*x%3}z{LoJ#YgguzFMsjXW!wG7=l}dy&p({jaQFBA`Gv2e3n-d@`->O9 zbm55jlyATRDf4>xNV*LVK2dojy+zt1>8-!K5|D^XNP$xvOFu6y9k^7=QJ^aviFF_-`itIKN2B7?A}_5Yei%NQvtZsh$_sMR zDu}gt6eNs_!ckt3lj6=LcA1k3dTn5{IY_*l2MHpV45gf}<>k;)${`L7#tU0}%s<6Q`Z1z*0 z3mtvBmCK%%_9JW&@U5W5pcT|@3isbLs)8991zv@u@d5w2YAx{-$P}~3b8kCt$^v;yM;sv}fC5JC2_TF`7{sWT*d^e>i-9a^{pPw2 z^40$K@OjT}1d{Z(M|HUE74lNLLa;)XLJVxCzKp#9+bGgb=L!Ian9JA=!YLN4Jyb2T zWo#KX06|&a%rH_cz=Hu4tH3^J8d(eEo{AytL_DxXjFCLRuekQB1Ctrs*B&ea;e~^d zb(jgneI4LW9e82~p2&fx=D4Nn)W@9dDze0Vk`{pR@tPnIh-E+WR`-3z}LYF7xYn+EJd{$wTQ&@O_N!N$u1(U}1 zm}9uKz<#F+xFv*;Hu7f(X?KSGGfHbg(#X*-k+ef>!o8iOIpZyS8j&1&X9!$Lz{7^nPf8|?U8g_ zNj5HoX}b`n?LwHg3t{jrM}ioTk|mp_BO8M9-x(5ZWs&*Ya6vbSQ>13#;*=E96SI;P zdQ3yMMlEwsfG6DowGyiY+(VEvR`Hl?2FNv~3<3B!+N+^wsSp6OG=;E9VwC}LBy18R zlCVjPNWvyDA_<#lK=xZLzW9@W@v)9L^=we{4n)Bo3Q@LNqJ%u_KKS9j>JM1hTg56q zs~vAQT47Wxn*-w%A~*r4b-1UX&);63zr8+xQ((s-L zglhwpUFO=BNnmoX3sme&606*@ftj_lxwf;pytKKxymsM_?*ZvMnz-9@ZTX0prm%QlF=u^j$FCfH!$ z&KbciNz5-6C1tGAqTa$Sy?q0x5bKhW!m7YGQq2+rml~FcBXuni$FUKtPm&RjiPAeV z@b#k5z=fi=TA(;5D0!oK<0DM}k9HDQ)4I-q*6`(L9N&WKE_^|xb0M!Y#y^^SEcUQy z$an@7BqU3?5%Vh=vJ-q3z&A^&5VC5t#&{5QLZkzpO=+!p;p3;o_yeZ1V}t4J5R-W% z-6`#nbeA)b*cnXZ45;RUp1L!TzLT;}t1zgf&`QZT;_oedb75!~5$7Oo<_VT{_?d&* zf+e0Dm8%u6jB)GOHNPDvV8fGjot1xcE|sRjWL}o zHr7ktBOAMO!^ZBwh8TuUzU7A8-;!*45@JflNx;GeP6C!Ua1t;qoP^UnFefL0_}V{= zKM-9=cdNMg*Dt>D4hx9<@=+3y57H;|ARF5qLdgjE5h0fw4QcV>f`s&bApL_7>fBC`c8+YDT|N8mw{_^OzKl9T4 z2jBa<5AFG#BmIWT+Q%>I2(&F-7ow{PElPt6{j PJ@CN6d-u From for WasmModule { } impl WasmModule { - /// Uses the supplied wasm module. - pub fn from_code(code: &[u8]) -> Self { - let module = Module::from_bytes(code).unwrap(); - let limits = *module - .import_section() - .unwrap() - .entries() - .iter() - .find_map(|e| if let External::Memory(mem) = e.external() { Some(mem) } else { None }) - .unwrap() - .limits(); - let code = module.into_bytes().unwrap(); - let hash = T::Hashing::hash(&code); - let memory = - ImportedMemory { min_pages: limits.initial(), max_pages: limits.maximum().unwrap() }; - Self { code: code.into(), hash, memory: Some(memory) } - } - /// Creates a wasm module with an empty `call` and `deploy` function and nothing else. pub fn dummy() -> Self { ModuleDefinition::default().into() diff --git a/substrate/frame/contracts/src/benchmarking/mod.rs b/substrate/frame/contracts/src/benchmarking/mod.rs index e387c28b671..d6c24daa7c4 100644 --- a/substrate/frame/contracts/src/benchmarking/mod.rs +++ b/substrate/frame/contracts/src/benchmarking/mod.rs @@ -29,7 +29,7 @@ use self::{ sandbox::Sandbox, }; use crate::{ - exec::{AccountIdOf, Key}, + exec::Key, migration::{ codegen::LATEST_MIGRATION_VERSION, v09, v10, v11, v12, v13, v14, v15, MigrationStep, }, @@ -184,24 +184,6 @@ fn caller_funding() -> BalanceOf { BalanceOf::::max_value() / 10_000u32.into() } -/// Load the specified contract file from disk by including it into the runtime. -/// -/// We need to load a different version of ink! contracts when the benchmark is run as -/// a test. This is because ink! contracts depend on the sizes of types that are defined -/// differently in the test environment. Solang is more lax in that regard. -macro_rules! load_benchmark { - ($name:expr) => {{ - #[cfg(not(test))] - { - include_bytes!(concat!("../../benchmarks/", $name, ".wasm")) - } - #[cfg(test)] - { - include_bytes!(concat!("../../benchmarks/", $name, "_test.wasm")) - } - }}; -} - benchmarks! { where_clause { where as codec::HasCompact>::Type: Clone + Eq + PartialEq + sp_std::fmt::Debug + scale_info::TypeInfo + codec::Encode, @@ -2643,86 +2625,6 @@ benchmarks! { "); }: {} - // Execute one erc20 transfer using the ink! erc20 example contract. - #[extra] - #[pov_mode = Measured] - ink_erc20_transfer { - let code = load_benchmark!("ink_erc20"); - let data = { - let new: ([u8; 4], BalanceOf) = ([0x9b, 0xae, 0x9d, 0x5e], 1000u32.into()); - new.encode() - }; - let instance = Contract::::new( - WasmModule::from_code(code), data, - )?; - let data = { - let transfer: ([u8; 4], AccountIdOf, BalanceOf) = ( - [0x84, 0xa1, 0x5d, 0xa1], - account::("receiver", 0, 0), - 1u32.into(), - ); - transfer.encode() - }; - }: { - >::bare_call( - instance.caller, - instance.account_id, - 0u32.into(), - Weight::MAX, - None, - data, - DebugInfo::Skip, - CollectEvents::Skip, - Determinism::Enforced, - ) - .result?; - } - - // Execute one erc20 transfer using the open zeppelin erc20 contract compiled with solang. - #[extra] - #[pov_mode = Measured] - solang_erc20_transfer { - let code = include_bytes!("../../benchmarks/solang_erc20.wasm"); - let caller = account::("instantiator", 0, 0); - let mut balance = [0u8; 32]; - balance[0] = 100; - let data = { - let new: ([u8; 4], &str, &str, [u8; 32], AccountIdOf) = ( - [0xa6, 0xf1, 0xf5, 0xe1], - "KSM", - "K", - balance, - caller.clone(), - ); - new.encode() - }; - let instance = Contract::::with_caller( - caller, WasmModule::from_code(code), data, - )?; - balance[0] = 1; - let data = { - let transfer: ([u8; 4], AccountIdOf, [u8; 32]) = ( - [0x6a, 0x46, 0x73, 0x94], - account::("receiver", 0, 0), - balance, - ); - transfer.encode() - }; - }: { - >::bare_call( - instance.caller, - instance.account_id, - 0u32.into(), - Weight::MAX, - None, - data, - DebugInfo::Skip, - CollectEvents::Skip, - Determinism::Enforced, - ) - .result?; - } - impl_benchmark_test_suite!( Contracts, crate::tests::ExtBuilder::default().build(), -- GitLab From d54412ce8bef113c7a515b3a08851e7d77553df5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Thu, 8 Feb 2024 15:39:55 +0100 Subject: [PATCH 028/133] contracts: Remove no longer enforced limits from the `Schedule` (#3184) When switching from the instrumented gas metering to the wasmi gas metering we also removed all imposed limits regarding Wasm module internals. All those things do not interact with the host and have to be handled by wasmi. For example, Wasmi charges additional gas for parameters to each function because as they incur some overhead. Back then we took the opportunity to remove the dependency on the deprecated `parity-wasm` which was used to enforce those limits. This PR merely removes them from the `Schedule` they aren't enforced for a while. --- prdoc/pr_3184.prdoc | 10 +++++ substrate/frame/contracts/src/schedule.rs | 39 ------------------- substrate/frame/contracts/src/wasm/prepare.rs | 5 --- 3 files changed, 10 insertions(+), 44 deletions(-) create mode 100644 prdoc/pr_3184.prdoc diff --git a/prdoc/pr_3184.prdoc b/prdoc/pr_3184.prdoc new file mode 100644 index 00000000000..36ba92bcf5a --- /dev/null +++ b/prdoc/pr_3184.prdoc @@ -0,0 +1,10 @@ +title: "Contracts: Remove no longer enforced limits from the Schedule" + +doc: + - audience: Runtime Dev + description: | + The limits are no longer in use and do nothing. Every builder overwritting them + can just adapt their code to remove them without any consequence. + +crates: + - name: pallet-contracts diff --git a/substrate/frame/contracts/src/schedule.rs b/substrate/frame/contracts/src/schedule.rs index 63641832315..54e037d6be9 100644 --- a/substrate/frame/contracts/src/schedule.rs +++ b/substrate/frame/contracts/src/schedule.rs @@ -39,11 +39,7 @@ use serde::{Deserialize, Serialize}; /// fn create_schedule() -> Schedule { /// Schedule { /// limits: Limits { -/// globals: 3, -/// parameters: 3, /// memory_pages: 16, -/// table_size: 3, -/// br_table_size: 3, /// .. Default::default() /// }, /// instruction_weights: InstructionWeights { @@ -77,38 +73,9 @@ pub struct Limits { /// The maximum number of topics supported by an event. pub event_topics: u32, - /// Maximum number of globals a module is allowed to declare. - /// - /// Globals are not limited through the linear memory limit `memory_pages`. - pub globals: u32, - - /// Maximum number of locals a function can have. - /// - /// As wasm engine initializes each of the local, we need to limit their number to confine - /// execution costs. - pub locals: u32, - - /// Maximum numbers of parameters a function can have. - /// - /// Those need to be limited to prevent a potentially exploitable interaction with - /// the stack height instrumentation: The costs of executing the stack height - /// instrumentation for an indirectly called function scales linearly with the amount - /// of parameters of this function. Because the stack height instrumentation itself is - /// is not weight metered its costs must be static (via this limit) and included in - /// the costs of the instructions that cause them (call, call_indirect). - pub parameters: u32, - /// Maximum number of memory pages allowed for a contract. pub memory_pages: u32, - /// Maximum number of elements allowed in a table. - /// - /// Currently, the only type of element that is allowed in a table is funcref. - pub table_size: u32, - - /// Maximum number of elements that can appear as immediate value to the br_table instruction. - pub br_table_size: u32, - /// The maximum length of a subject in bytes used for PRNG generation. pub subject_len: u32, @@ -370,13 +337,7 @@ impl Default for Limits { fn default() -> Self { Self { event_topics: 4, - globals: 256, - locals: 1024, - parameters: 128, memory_pages: 16, - // 4k function pointers (This is in count not bytes). - table_size: 4096, - br_table_size: 256, subject_len: 32, payload_len: 16 * 1024, runtime_memory: 1024 * 1024 * 128, diff --git a/substrate/frame/contracts/src/wasm/prepare.rs b/substrate/frame/contracts/src/wasm/prepare.rs index dfe8c4f8f9b..5cdf5600fcd 100644 --- a/substrate/frame/contracts/src/wasm/prepare.rs +++ b/substrate/frame/contracts/src/wasm/prepare.rs @@ -384,12 +384,7 @@ mod tests { let wasm = wat::parse_str($wat).unwrap().try_into().unwrap(); let schedule = Schedule { limits: Limits { - globals: 3, - locals: 3, - parameters: 3, memory_pages: 16, - table_size: 3, - br_table_size: 3, .. Default::default() }, .. Default::default() -- GitLab From bc5a758c0c4b6a742fea22b3dbfb4df2864f2f14 Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Thu, 8 Feb 2024 17:04:55 +0100 Subject: [PATCH 029/133] Contracts update doc.rs metadata (#3241) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adding Rust metadata for doc see https://docs.rs/about/metadata --------- Co-authored-by: Alexander Theißen --- substrate/frame/contracts/uapi/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/substrate/frame/contracts/uapi/Cargo.toml b/substrate/frame/contracts/uapi/Cargo.toml index eb8a9c61820..a5081af2a2d 100644 --- a/substrate/frame/contracts/uapi/Cargo.toml +++ b/substrate/frame/contracts/uapi/Cargo.toml @@ -23,6 +23,9 @@ scale = { package = "parity-scale-codec", version = "3.6.1", default-features = [target.'cfg(target_arch = "riscv32")'.dependencies] polkavm-derive = '0.5.0' +[package.metadata.docs.rs] +default-target = ["wasm32-unknown-unknown"] + [features] default = ["scale"] scale = ["dep:scale", "scale-info"] -- GitLab From 0a94124d24ddf4f346c71d9256b84386eac3614d Mon Sep 17 00:00:00 2001 From: drskalman <35698397+drskalman@users.noreply.github.com> Date: Thu, 8 Feb 2024 11:08:51 -0500 Subject: [PATCH 030/133] Make BEEFY client keystore generic over BEEFY `AuthorityId` type (#2258) This is the significant step to make BEEFY client able to handle both ECDSA and (ECDSA, BLS) type signature. The idea is having BEEFY Client generic on crypto types makes migration to new types smoother. This makes the BEEFY Keystore generic over AuthorityId and extends its tests to cover the case when the AuthorityId is of type (ECDSA, BLS12-377) --------- Co-authored-by: Davide Galassi Co-authored-by: Robert Hambrock --- Cargo.lock | 1 + substrate/client/consensus/beefy/Cargo.toml | 9 + .../beefy/src/communication/gossip.rs | 18 +- .../consensus/beefy/src/justification.rs | 9 +- .../client/consensus/beefy/src/keystore.rs | 461 +++++++++++++----- substrate/client/consensus/beefy/src/round.rs | 45 +- substrate/client/consensus/beefy/src/tests.rs | 21 +- .../client/consensus/beefy/src/worker.rs | 19 +- substrate/client/keystore/src/local.rs | 16 +- substrate/frame/beefy/src/tests.rs | 16 +- .../primitives/consensus/beefy/Cargo.toml | 2 + .../consensus/beefy/src/commitment.rs | 2 +- .../primitives/consensus/beefy/src/lib.rs | 45 +- .../consensus/beefy/src/test_utils.rs | 105 ++-- substrate/primitives/core/src/bandersnatch.rs | 13 + substrate/primitives/core/src/bls.rs | 41 +- substrate/primitives/core/src/ecdsa.rs | 17 + substrate/primitives/core/src/ed25519.rs | 17 + .../primitives/core/src/paired_crypto.rs | 31 +- substrate/primitives/core/src/sr25519.rs | 16 + substrate/primitives/keystore/src/lib.rs | 28 ++ substrate/primitives/keystore/src/testing.rs | 47 +- 22 files changed, 745 insertions(+), 234 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index db9a2ca3564..49507ecd4f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18225,6 +18225,7 @@ dependencies = [ "sp-core", "sp-crypto-hashing", "sp-io", + "sp-keystore", "sp-mmr-primitives", "sp-runtime", "sp-std 14.0.0", diff --git a/substrate/client/consensus/beefy/Cargo.toml b/substrate/client/consensus/beefy/Cargo.toml index 35226974278..56c38bf2e47 100644 --- a/substrate/client/consensus/beefy/Cargo.toml +++ b/substrate/client/consensus/beefy/Cargo.toml @@ -52,3 +52,12 @@ sp-consensus-grandpa = { path = "../../../primitives/consensus/grandpa" } sp-keyring = { path = "../../../primitives/keyring" } sp-tracing = { path = "../../../primitives/tracing" } substrate-test-runtime-client = { path = "../../../test-utils/runtime/client" } + +[features] +# This feature adds BLS crypto primitives. It should not be used in production since +# the BLS implementation and interface may still be subject to significant change. +bls-experimental = [ + "sp-application-crypto/bls-experimental", + "sp-consensus-beefy/bls-experimental", + "sp-core/bls-experimental", +] diff --git a/substrate/client/consensus/beefy/src/communication/gossip.rs b/substrate/client/consensus/beefy/src/communication/gossip.rs index 645a10b2a1d..8a0b0a74308 100644 --- a/substrate/client/consensus/beefy/src/communication/gossip.rs +++ b/substrate/client/consensus/beefy/src/communication/gossip.rs @@ -485,8 +485,8 @@ pub(crate) mod tests { use sc_network_test::Block; use sp_application_crypto::key_types::BEEFY as BEEFY_KEY_TYPE; use sp_consensus_beefy::{ - ecdsa_crypto::Signature, known_payloads, Commitment, Keyring, MmrRootHash, Payload, - SignedCommitment, VoteMessage, + ecdsa_crypto::Signature, known_payloads, test_utils::Keyring, Commitment, MmrRootHash, + Payload, SignedCommitment, VoteMessage, }; use sp_keystore::{testing::MemoryKeystore, Keystore}; @@ -507,10 +507,13 @@ pub(crate) mod tests { } } - pub fn sign_commitment(who: &Keyring, commitment: &Commitment) -> Signature { + pub fn sign_commitment( + who: &Keyring, + commitment: &Commitment, + ) -> Signature { let store = MemoryKeystore::new(); store.ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&who.to_seed())).unwrap(); - let beefy_keystore: BeefyKeystore = Some(store.into()).into(); + let beefy_keystore: BeefyKeystore = Some(store.into()).into(); beefy_keystore.sign(&who.public(), &commitment.encode()).unwrap() } @@ -538,7 +541,10 @@ pub(crate) mod tests { .validators() .iter() .map(|validator: &AuthorityId| { - Some(sign_commitment(&Keyring::from_public(validator).unwrap(), &commitment)) + Some(sign_commitment( + &Keyring::::from_public(validator).unwrap(), + &commitment, + )) }) .collect(); @@ -547,7 +553,7 @@ pub(crate) mod tests { #[test] fn should_validate_messages() { - let keys = vec![Keyring::Alice.public()]; + let keys = vec![Keyring::::Alice.public()]; let validator_set = ValidatorSet::::new(keys.clone(), 0).unwrap(); let (gv, mut report_stream) = GossipValidator::::new(Arc::new(Mutex::new(KnownPeers::new()))); diff --git a/substrate/client/consensus/beefy/src/justification.rs b/substrate/client/consensus/beefy/src/justification.rs index 483184e2374..7f1b9e5237c 100644 --- a/substrate/client/consensus/beefy/src/justification.rs +++ b/substrate/client/consensus/beefy/src/justification.rs @@ -76,7 +76,7 @@ pub(crate) fn verify_with_validator_set( .as_ref() .map(|sig| { signatures_checked += 1; - BeefyKeystore::verify(id, sig, &message[..]) + BeefyKeystore::verify(*id, sig, &message[..]) }) .unwrap_or(false) }) @@ -93,7 +93,8 @@ pub(crate) fn verify_with_validator_set( #[cfg(test)] pub(crate) mod tests { use sp_consensus_beefy::{ - known_payloads, Commitment, Keyring, Payload, SignedCommitment, VersionedFinalityProof, + known_payloads, test_utils::Keyring, Commitment, Payload, SignedCommitment, + VersionedFinalityProof, }; use substrate_test_runtime_client::runtime::Block; @@ -103,7 +104,7 @@ pub(crate) mod tests { pub(crate) fn new_finality_proof( block_num: NumberFor, validator_set: &ValidatorSet, - keys: &[Keyring], + keys: &[Keyring], ) -> BeefyVersionedFinalityProof { let commitment = Commitment { payload: Payload::from_single_entry(known_payloads::MMR_ROOT_ID, vec![]), @@ -174,7 +175,7 @@ pub(crate) mod tests { }; // change a signature to a different key *bad_signed_commitment.signatures.first_mut().unwrap() = - Some(Keyring::Dave.sign(&bad_signed_commitment.commitment.encode())); + Some(Keyring::::Dave.sign(&bad_signed_commitment.commitment.encode())); match verify_with_validator_set::(block_num, &validator_set, &bad_proof.into()) { Err((ConsensusError::InvalidJustification, 3)) => (), e => assert!(false, "Got unexpected {:?}", e), diff --git a/substrate/client/consensus/beefy/src/keystore.rs b/substrate/client/consensus/beefy/src/keystore.rs index 75c44de3324..2ddc938fbc6 100644 --- a/substrate/client/consensus/beefy/src/keystore.rs +++ b/substrate/client/consensus/beefy/src/keystore.rs @@ -16,41 +16,45 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use sp_application_crypto::{key_types::BEEFY as BEEFY_KEY_TYPE, RuntimeAppPublic}; +use sp_application_crypto::{key_types::BEEFY as BEEFY_KEY_TYPE, AppCrypto, RuntimeAppPublic}; +use sp_consensus_beefy::{AuthorityIdBound, BeefyAuthorityId, BeefySignatureHasher}; +use sp_core::ecdsa; +#[cfg(feature = "bls-experimental")] +use sp_core::ecdsa_bls377; use sp_crypto_hashing::keccak_256; use sp_keystore::KeystorePtr; +use codec::Decode; use log::warn; - -use sp_consensus_beefy::{ - ecdsa_crypto::{Public, Signature}, - BeefyAuthorityId, -}; +use std::marker::PhantomData; use crate::{error, LOG_TARGET}; -/// Hasher used for BEEFY signatures. -pub(crate) type BeefySignatureHasher = sp_runtime::traits::Keccak256; - /// A BEEFY specific keystore implemented as a `Newtype`. This is basically a /// wrapper around [`sp_keystore::Keystore`] and allows to customize /// common cryptographic functionality. -pub(crate) struct BeefyKeystore(Option); +pub(crate) struct BeefyKeystore( + Option, + PhantomData AuthorityId>, +); -impl BeefyKeystore { +impl BeefyKeystore { /// Check if the keystore contains a private key for one of the public keys /// contained in `keys`. A public key with a matching private key is known /// as a local authority id. /// /// Return the public key for which we also do have a private key. If no /// matching private key is found, `None` will be returned. - pub fn authority_id(&self, keys: &[Public]) -> Option { + pub fn authority_id(&self, keys: &[AuthorityId]) -> Option { let store = self.0.clone()?; // we do check for multiple private keys as a key store sanity check. - let public: Vec = keys + let public: Vec = keys .iter() - .filter(|k| store.has_keys(&[(k.to_raw_vec(), BEEFY_KEY_TYPE)])) + .filter(|k| { + store + .has_keys(&[(::to_raw_vec(k), BEEFY_KEY_TYPE)]) + }) .cloned() .collect(); @@ -71,55 +75,125 @@ impl BeefyKeystore { /// Note that `message` usually will be pre-hashed before being signed. /// /// Return the message signature or an error in case of failure. - pub fn sign(&self, public: &Public, message: &[u8]) -> Result { + pub fn sign( + &self, + public: &AuthorityId, + message: &[u8], + ) -> Result<::Signature, error::Error> { let store = self.0.clone().ok_or_else(|| error::Error::Keystore("no Keystore".into()))?; - let msg = keccak_256(message); - let public = public.as_ref(); - - let sig = store - .ecdsa_sign_prehashed(BEEFY_KEY_TYPE, public, &msg) - .map_err(|e| error::Error::Keystore(e.to_string()))? - .ok_or_else(|| error::Error::Signature("ecdsa_sign_prehashed() failed".to_string()))?; - - // check that `sig` has the expected result type - let sig = sig.clone().try_into().map_err(|_| { - error::Error::Signature(format!("invalid signature {:?} for key {:?}", sig, public)) + // ECDSA should use ecdsa_sign_prehashed since it needs to be hashed by keccak_256 instead + // of blake2. As such we need to deal with producing the signatures case-by-case + let signature_byte_array: Vec = match ::CRYPTO_ID { + ecdsa::CRYPTO_ID => { + let msg_hash = keccak_256(message); + let public: ecdsa::Public = ecdsa::Public::try_from(public.as_slice()).unwrap(); + + let sig = store + .ecdsa_sign_prehashed(BEEFY_KEY_TYPE, &public, &msg_hash) + .map_err(|e| error::Error::Keystore(e.to_string()))? + .ok_or_else(|| { + error::Error::Signature("ecdsa_sign_prehashed() failed".to_string()) + })?; + let sig_ref: &[u8] = sig.as_ref(); + sig_ref.to_vec() + }, + + #[cfg(feature = "bls-experimental")] + ecdsa_bls377::CRYPTO_ID => { + let public: ecdsa_bls377::Public = + ecdsa_bls377::Public::try_from(public.as_slice()).unwrap(); + let sig = store + .ecdsa_bls377_sign_with_keccak256(BEEFY_KEY_TYPE, &public, &message) + .map_err(|e| error::Error::Keystore(e.to_string()))? + .ok_or_else(|| error::Error::Signature("bls377_sign() failed".to_string()))?; + let sig_ref: &[u8] = sig.as_ref(); + sig_ref.to_vec() + }, + + _ => Err(error::Error::Keystore("key type is not supported by BEEFY Keystore".into()))?, + }; + + //check that `sig` has the expected result type + let signature = ::Signature::decode( + &mut signature_byte_array.as_slice(), + ) + .map_err(|_| { + error::Error::Signature(format!( + "invalid signature {:?} for key {:?}", + signature_byte_array, public + )) })?; - Ok(sig) + Ok(signature) } /// Returns a vector of [`sp_consensus_beefy::crypto::Public`] keys which are currently /// supported (i.e. found in the keystore). - pub fn public_keys(&self) -> Result, error::Error> { + pub fn public_keys(&self) -> Result, error::Error> { let store = self.0.clone().ok_or_else(|| error::Error::Keystore("no Keystore".into()))?; - let pk: Vec = - store.ecdsa_public_keys(BEEFY_KEY_TYPE).drain(..).map(Public::from).collect(); - - Ok(pk) + let pk = match ::CRYPTO_ID { + ecdsa::CRYPTO_ID => store + .ecdsa_public_keys(BEEFY_KEY_TYPE) + .drain(..) + .map(|pk| AuthorityId::try_from(pk.as_ref())) + .collect::, _>>() + .or_else(|_| { + Err(error::Error::Keystore( + "unable to convert public key into authority id".into(), + )) + }), + + #[cfg(feature = "bls-experimental")] + ecdsa_bls377::CRYPTO_ID => store + .ecdsa_bls377_public_keys(BEEFY_KEY_TYPE) + .drain(..) + .map(|pk| AuthorityId::try_from(pk.as_ref())) + .collect::, _>>() + .or_else(|_| { + Err(error::Error::Keystore( + "unable to convert public key into authority id".into(), + )) + }), + + _ => Err(error::Error::Keystore("key type is not supported by BEEFY Keystore".into())), + }; + + pk } /// Use the `public` key to verify that `sig` is a valid signature for `message`. /// /// Return `true` if the signature is authentic, `false` otherwise. - pub fn verify(public: &Public, sig: &Signature, message: &[u8]) -> bool { + pub fn verify( + public: &AuthorityId, + sig: &::Signature, + message: &[u8], + ) -> bool { BeefyAuthorityId::::verify(public, sig, message) } } -impl From> for BeefyKeystore { - fn from(store: Option) -> BeefyKeystore { - BeefyKeystore(store) +impl From> for BeefyKeystore +where + ::Signature: Send + Sync, +{ + fn from(store: Option) -> BeefyKeystore { + BeefyKeystore(store, PhantomData) } } #[cfg(test)] pub mod tests { - use sp_consensus_beefy::{ecdsa_crypto, Keyring}; - use sp_core::{ecdsa, Pair}; - use sp_keystore::testing::MemoryKeystore; + #[cfg(feature = "bls-experimental")] + use sp_consensus_beefy::ecdsa_bls_crypto; + use sp_consensus_beefy::{ + ecdsa_crypto, + test_utils::{BeefySignerAuthority, Keyring}, + }; + use sp_core::Pair as PairT; + use sp_keystore::{testing::MemoryKeystore, Keystore}; use super::*; use crate::error::Error; @@ -128,152 +202,265 @@ pub mod tests { MemoryKeystore::new().into() } - #[test] - fn verify_should_work() { - let msg = keccak_256(b"I am Alice!"); - let sig = Keyring::Alice.sign(b"I am Alice!"); - - assert!(ecdsa::Pair::verify_prehashed( - &sig.clone().into(), - &msg, - &Keyring::Alice.public().into(), + fn pair_verify_should_work< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >() + where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { + let msg = b"I am Alice!"; + let sig = Keyring::::Alice.sign(b"I am Alice!"); + + assert!(>::verify( + &Keyring::Alice.public(), + &sig, + &msg.as_slice(), )); // different public key -> fail - assert!(!ecdsa::Pair::verify_prehashed( - &sig.clone().into(), - &msg, - &Keyring::Bob.public().into(), + assert!(!>::verify( + &Keyring::Bob.public(), + &sig, + &msg.as_slice(), )); - let msg = keccak_256(b"I am not Alice!"); + let msg = b"I am not Alice!"; // different msg -> fail - assert!( - !ecdsa::Pair::verify_prehashed(&sig.into(), &msg, &Keyring::Alice.public().into(),) - ); + assert!(!>::verify( + &Keyring::Alice.public(), + &sig, + &msg.as_slice(), + )); + } + + /// Generate key pair in the given store using the provided seed + fn generate_in_store( + store: KeystorePtr, + key_type: sp_application_crypto::KeyTypeId, + owner: Option>, + ) -> AuthorityId + where + AuthorityId: + AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + ::Pair: BeefySignerAuthority, + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + { + let optional_seed: Option = owner.map(|owner| owner.to_seed()); + + match ::CRYPTO_ID { + ecdsa::CRYPTO_ID => { + let pk = store.ecdsa_generate_new(key_type, optional_seed.as_deref()).ok().unwrap(); + AuthorityId::decode(&mut pk.as_ref()).unwrap() + }, + #[cfg(feature = "bls-experimental")] + ecdsa_bls377::CRYPTO_ID => { + let pk = store + .ecdsa_bls377_generate_new(key_type, optional_seed.as_deref()) + .ok() + .unwrap(); + AuthorityId::decode(&mut pk.as_ref()).unwrap() + }, + _ => panic!("Requested CRYPTO_ID is not supported by the BEEFY Keyring"), + } } #[test] - fn pair_works() { - let want = ecdsa_crypto::Pair::from_string("//Alice", None) + fn pair_verify_should_work_ecdsa() { + pair_verify_should_work::(); + } + + #[cfg(feature = "bls-experimental")] + #[test] + fn pair_verify_should_work_ecdsa_n_bls() { + pair_verify_should_work::(); + } + + fn pair_works< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >() + where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { + let want = ::Pair::from_string("//Alice", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Alice.pair().to_raw_vec(); + let got = Keyring::::Alice.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//Bob", None) + let want = ::Pair::from_string("//Bob", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Bob.pair().to_raw_vec(); + let got = Keyring::::Bob.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//Charlie", None) + let want = ::Pair::from_string("//Charlie", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Charlie.pair().to_raw_vec(); + let got = Keyring::::Charlie.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//Dave", None) + let want = ::Pair::from_string("//Dave", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Dave.pair().to_raw_vec(); + let got = Keyring::::Dave.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//Eve", None) + let want = ::Pair::from_string("//Eve", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Eve.pair().to_raw_vec(); + let got = Keyring::::Eve.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//Ferdie", None) + let want = ::Pair::from_string("//Ferdie", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Ferdie.pair().to_raw_vec(); + let got = Keyring::::Ferdie.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//One", None) + let want = ::Pair::from_string("//One", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::One.pair().to_raw_vec(); + let got = Keyring::::One.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//Two", None) + let want = ::Pair::from_string("//Two", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Two.pair().to_raw_vec(); + let got = Keyring::::Two.pair().to_raw_vec(); assert_eq!(want, got); } #[test] - fn authority_id_works() { + fn ecdsa_pair_works() { + pair_works::(); + } + + #[cfg(feature = "bls-experimental")] + #[test] + fn ecdsa_n_bls_pair_works() { + pair_works::(); + } + + fn authority_id_works< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >() + where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { let store = keystore(); - let alice: ecdsa_crypto::Public = store - .ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&Keyring::Alice.to_seed())) - .ok() - .unwrap() - .into(); + generate_in_store::(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Alice)); + + let alice = Keyring::::Alice.public(); let bob = Keyring::Bob.public(); let charlie = Keyring::Charlie.public(); - let store: BeefyKeystore = Some(store).into(); + let beefy_store: BeefyKeystore = Some(store).into(); let mut keys = vec![bob, charlie]; - let id = store.authority_id(keys.as_slice()); + let id = beefy_store.authority_id(keys.as_slice()); assert!(id.is_none()); keys.push(alice.clone()); - let id = store.authority_id(keys.as_slice()).unwrap(); + let id = beefy_store.authority_id(keys.as_slice()).unwrap(); assert_eq!(id, alice); } #[test] - fn sign_works() { + fn authority_id_works_for_ecdsa() { + authority_id_works::(); + } + + #[cfg(feature = "bls-experimental")] + #[test] + fn authority_id_works_for_ecdsa_n_bls() { + authority_id_works::(); + } + + fn sign_works< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >() + where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { let store = keystore(); - let alice: ecdsa_crypto::Public = store - .ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&Keyring::Alice.to_seed())) - .ok() - .unwrap() - .into(); + generate_in_store::(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Alice)); - let store: BeefyKeystore = Some(store).into(); + let alice = Keyring::Alice.public(); + + let store: BeefyKeystore = Some(store).into(); let msg = b"are you involved or commited?"; let sig1 = store.sign(&alice, msg).unwrap(); - let sig2 = Keyring::Alice.sign(msg); + let sig2 = Keyring::::Alice.sign(msg); assert_eq!(sig1, sig2); } #[test] - fn sign_error() { + fn sign_works_for_ecdsa() { + sign_works::(); + } + + #[cfg(feature = "bls-experimental")] + #[test] + fn sign_works_for_ecdsa_n_bls() { + sign_works::(); + } + + fn sign_error< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >( + expected_error_message: &str, + ) where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { let store = keystore(); - store - .ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&Keyring::Bob.to_seed())) - .ok() - .unwrap(); + generate_in_store::(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Bob)); - let store: BeefyKeystore = Some(store).into(); + let store: BeefyKeystore = Some(store).into(); let alice = Keyring::Alice.public(); let msg = b"are you involved or commited?"; let sig = store.sign(&alice, msg).err().unwrap(); - let err = Error::Signature("ecdsa_sign_prehashed() failed".to_string()); + let err = Error::Signature(expected_error_message.to_string()); assert_eq!(sig, err); } + #[test] + fn sign_error_for_ecdsa() { + sign_error::("ecdsa_sign_prehashed() failed"); + } + + #[cfg(feature = "bls-experimental")] + #[test] + fn sign_error_for_ecdsa_n_bls() { + sign_error::("bls377_sign() failed"); + } + #[test] fn sign_no_keystore() { - let store: BeefyKeystore = None.into(); + let store: BeefyKeystore = None.into(); let alice = Keyring::Alice.public(); let msg = b"are you involved or commited"; @@ -283,17 +470,21 @@ pub mod tests { assert_eq!(sig, err); } - #[test] - fn verify_works() { + fn verify_works< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >() + where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { let store = keystore(); - let alice: ecdsa_crypto::Public = store - .ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&Keyring::Alice.to_seed())) - .ok() - .unwrap() - .into(); + generate_in_store::(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Alice)); - let store: BeefyKeystore = Some(store).into(); + let store: BeefyKeystore = Some(store).into(); + + let alice = Keyring::Alice.public(); // `msg` and `sig` match let msg = b"are you involved or commited?"; @@ -305,32 +496,48 @@ pub mod tests { assert!(!BeefyKeystore::verify(&alice, &sig, msg)); } - // Note that we use keys with and without a seed for this test. #[test] - fn public_keys_works() { + fn verify_works_for_ecdsa() { + verify_works::(); + } + + #[cfg(feature = "bls-experimental")] + #[test] + + fn verify_works_for_ecdsa_n_bls() { + verify_works::(); + } + + // Note that we use keys with and without a seed for this test. + fn public_keys_works< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >() + where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { const TEST_TYPE: sp_application_crypto::KeyTypeId = sp_application_crypto::KeyTypeId(*b"test"); let store = keystore(); - let add_key = - |key_type, seed: Option<&str>| store.ecdsa_generate_new(key_type, seed).unwrap(); - // test keys - let _ = add_key(TEST_TYPE, Some(Keyring::Alice.to_seed().as_str())); - let _ = add_key(TEST_TYPE, Some(Keyring::Bob.to_seed().as_str())); - - let _ = add_key(TEST_TYPE, None); - let _ = add_key(TEST_TYPE, None); + let _ = generate_in_store::(store.clone(), TEST_TYPE, Some(Keyring::Alice)); + let _ = generate_in_store::(store.clone(), TEST_TYPE, Some(Keyring::Bob)); // BEEFY keys - let _ = add_key(BEEFY_KEY_TYPE, Some(Keyring::Dave.to_seed().as_str())); - let _ = add_key(BEEFY_KEY_TYPE, Some(Keyring::Eve.to_seed().as_str())); + let _ = + generate_in_store::(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Dave)); + let _ = generate_in_store::(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Eve)); + + let _ = generate_in_store::(store.clone(), TEST_TYPE, None); + let _ = generate_in_store::(store.clone(), TEST_TYPE, None); - let key1: ecdsa_crypto::Public = add_key(BEEFY_KEY_TYPE, None).into(); - let key2: ecdsa_crypto::Public = add_key(BEEFY_KEY_TYPE, None).into(); + let key1 = generate_in_store::(store.clone(), BEEFY_KEY_TYPE, None); + let key2 = generate_in_store::(store.clone(), BEEFY_KEY_TYPE, None); - let store: BeefyKeystore = Some(store).into(); + let store: BeefyKeystore = Some(store).into(); let keys = store.public_keys().ok().unwrap(); @@ -340,4 +547,16 @@ pub mod tests { assert!(keys.contains(&key1)); assert!(keys.contains(&key2)); } + + #[test] + fn public_keys_works_for_ecdsa_keystore() { + public_keys_works::(); + } + + #[cfg(feature = "bls-experimental")] + #[test] + + fn public_keys_works_for_ecdsa_n_bls() { + public_keys_works::(); + } } diff --git a/substrate/client/consensus/beefy/src/round.rs b/substrate/client/consensus/beefy/src/round.rs index 47414c60fdb..0045dc70c26 100644 --- a/substrate/client/consensus/beefy/src/round.rs +++ b/substrate/client/consensus/beefy/src/round.rs @@ -207,7 +207,7 @@ mod tests { use sc_network_test::Block; use sp_consensus_beefy::{ - known_payloads::MMR_ROOT_ID, Commitment, EquivocationProof, Keyring, Payload, + known_payloads::MMR_ROOT_ID, test_utils::Keyring, Commitment, EquivocationProof, Payload, SignedCommitment, ValidatorSet, VoteMessage, }; @@ -226,7 +226,7 @@ mod tests { #[test] fn round_tracker() { let mut rt = RoundTracker::default(); - let bob_vote = (Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed")); + let bob_vote = (Keyring::Bob.public(), Keyring::::Bob.sign(b"I am committed")); let threshold = 2; // adding new vote allowed @@ -237,7 +237,8 @@ mod tests { // vote is not done assert!(!rt.is_done(threshold)); - let alice_vote = (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")); + let alice_vote = + (Keyring::Alice.public(), Keyring::::Alice.sign(b"I am committed")); // adding new vote (self vote this time) allowed assert!(rt.add_vote(alice_vote)); @@ -271,7 +272,11 @@ mod tests { assert_eq!(42, rounds.validator_set_id()); assert_eq!(1, rounds.session_start()); assert_eq!( - &vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()], + &vec![ + Keyring::::Alice.public(), + Keyring::::Bob.public(), + Keyring::::Charlie.public() + ], rounds.validators() ); } @@ -301,7 +306,7 @@ mod tests { let mut vote = VoteMessage { id: Keyring::Alice.public(), commitment: commitment.clone(), - signature: Keyring::Alice.sign(b"I am committed"), + signature: Keyring::::Alice.sign(b"I am committed"), }; // add 1st good vote assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Ok); @@ -310,26 +315,26 @@ mod tests { assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Ok); vote.id = Keyring::Dave.public(); - vote.signature = Keyring::Dave.sign(b"I am committed"); + vote.signature = Keyring::::Dave.sign(b"I am committed"); // invalid vote (Dave is not a validator) assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Invalid); vote.id = Keyring::Bob.public(); - vote.signature = Keyring::Bob.sign(b"I am committed"); + vote.signature = Keyring::::Bob.sign(b"I am committed"); // add 2nd good vote assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Ok); vote.id = Keyring::Charlie.public(); - vote.signature = Keyring::Charlie.sign(b"I am committed"); + vote.signature = Keyring::::Charlie.sign(b"I am committed"); // add 3rd good vote -> round concluded -> signatures present assert_eq!( rounds.add_vote(vote.clone()), VoteImportResult::RoundConcluded(SignedCommitment { commitment, signatures: vec![ - Some(Keyring::Alice.sign(b"I am committed")), - Some(Keyring::Bob.sign(b"I am committed")), - Some(Keyring::Charlie.sign(b"I am committed")), + Some(Keyring::::Alice.sign(b"I am committed")), + Some(Keyring::::Bob.sign(b"I am committed")), + Some(Keyring::::Charlie.sign(b"I am committed")), None, ] }) @@ -337,7 +342,7 @@ mod tests { rounds.conclude(block_number); vote.id = Keyring::Eve.public(); - vote.signature = Keyring::Eve.sign(b"I am committed"); + vote.signature = Keyring::::Eve.sign(b"I am committed"); // Eve is a validator, but round was concluded, adding vote disallowed assert_eq!(rounds.add_vote(vote), VoteImportResult::Stale); } @@ -364,7 +369,7 @@ mod tests { let mut vote = VoteMessage { id: Keyring::Alice.public(), commitment, - signature: Keyring::Alice.sign(b"I am committed"), + signature: Keyring::::Alice.sign(b"I am committed"), }; // add vote for previous session, should fail assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Stale); @@ -407,22 +412,22 @@ mod tests { let mut alice_vote = VoteMessage { id: Keyring::Alice.public(), commitment: commitment.clone(), - signature: Keyring::Alice.sign(b"I am committed"), + signature: Keyring::::Alice.sign(b"I am committed"), }; let mut bob_vote = VoteMessage { id: Keyring::Bob.public(), commitment: commitment.clone(), - signature: Keyring::Bob.sign(b"I am committed"), + signature: Keyring::::Bob.sign(b"I am committed"), }; let mut charlie_vote = VoteMessage { id: Keyring::Charlie.public(), commitment, - signature: Keyring::Charlie.sign(b"I am committed"), + signature: Keyring::::Charlie.sign(b"I am committed"), }; let expected_signatures = vec![ - Some(Keyring::Alice.sign(b"I am committed")), - Some(Keyring::Bob.sign(b"I am committed")), - Some(Keyring::Charlie.sign(b"I am committed")), + Some(Keyring::::Alice.sign(b"I am committed")), + Some(Keyring::::Bob.sign(b"I am committed")), + Some(Keyring::::Charlie.sign(b"I am committed")), ]; // round 1 - only 2 out of 3 vote @@ -484,7 +489,7 @@ mod tests { let alice_vote1 = VoteMessage { id: Keyring::Alice.public(), commitment: commitment1, - signature: Keyring::Alice.sign(b"I am committed"), + signature: Keyring::::Alice.sign(b"I am committed"), }; let mut alice_vote2 = alice_vote1.clone(); alice_vote2.commitment = commitment2; diff --git a/substrate/client/consensus/beefy/src/tests.rs b/substrate/client/consensus/beefy/src/tests.rs index 7e61e877c1d..0e6ff210b15 100644 --- a/substrate/client/consensus/beefy/src/tests.rs +++ b/substrate/client/consensus/beefy/src/tests.rs @@ -57,9 +57,10 @@ use sp_consensus_beefy::{ ecdsa_crypto::{AuthorityId, Signature}, known_payloads, mmr::{find_mmr_root_digest, MmrRootProvider}, - BeefyApi, Commitment, ConsensusLog, EquivocationProof, Keyring as BeefyKeyring, MmrRootHash, - OpaqueKeyOwnershipProof, Payload, SignedCommitment, ValidatorSet, ValidatorSetId, - VersionedFinalityProof, VoteMessage, BEEFY_ENGINE_ID, + test_utils::Keyring as BeefyKeyring, + BeefyApi, Commitment, ConsensusLog, EquivocationProof, MmrRootHash, OpaqueKeyOwnershipProof, + Payload, SignedCommitment, ValidatorSet, ValidatorSetId, VersionedFinalityProof, VoteMessage, + BEEFY_ENGINE_ID, }; use sp_core::H256; use sp_keystore::{testing::MemoryKeystore, Keystore, KeystorePtr}; @@ -349,11 +350,11 @@ fn add_auth_change_digest(builder: &mut impl BlockBuilderExt, new_auth_set: Beef .unwrap(); } -pub(crate) fn make_beefy_ids(keys: &[BeefyKeyring]) -> Vec { - keys.iter().map(|&key| key.public().into()).collect() +pub(crate) fn make_beefy_ids(keys: &[BeefyKeyring]) -> Vec { + keys.iter().map(|key| key.public().into()).collect() } -pub(crate) fn create_beefy_keystore(authority: BeefyKeyring) -> KeystorePtr { +pub(crate) fn create_beefy_keystore(authority: &BeefyKeyring) -> KeystorePtr { let keystore = MemoryKeystore::new(); keystore .ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&authority.to_seed())) @@ -393,7 +394,7 @@ async fn voter_init_setup( // Spawns beefy voters. Returns a future to spawn on the runtime. fn initialize_beefy( net: &mut BeefyTestNet, - peers: Vec<(usize, &BeefyKeyring, Arc)>, + peers: Vec<(usize, &BeefyKeyring, Arc)>, min_block_delta: u32, ) -> impl Future where @@ -413,7 +414,7 @@ where for (peer_id, key, api) in peers.into_iter() { let peer = &net.peers[peer_id]; - let keystore = create_beefy_keystore(*key); + let keystore = create_beefy_keystore(key); let (_, _, peer_data) = net.make_block_import(peer.client().clone()); let PeerData { beefy_rpc_links, beefy_voter_links, .. } = peer_data; @@ -471,7 +472,7 @@ async fn run_for(duration: Duration, net: &Arc>) { pub(crate) fn get_beefy_streams( net: &mut BeefyTestNet, // peer index and key - peers: impl Iterator, + peers: impl Iterator)>, ) -> (Vec>, Vec>>) { let mut best_block_streams = Vec::new(); @@ -569,7 +570,7 @@ async fn streams_empty_after_timeout( async fn finalize_block_and_wait_for_beefy( net: &Arc>, // peer index and key - peers: impl Iterator + Clone, + peers: impl Iterator)> + Clone, finalize_target: &H256, expected_beefy: &[u64], ) { diff --git a/substrate/client/consensus/beefy/src/worker.rs b/substrate/client/consensus/beefy/src/worker.rs index e67e3e0f76a..19a24b9bc1a 100644 --- a/substrate/client/consensus/beefy/src/worker.rs +++ b/substrate/client/consensus/beefy/src/worker.rs @@ -26,7 +26,7 @@ use crate::{ error::Error, expect_validator_set, justification::BeefyVersionedFinalityProof, - keystore::{BeefyKeystore, BeefySignatureHasher}, + keystore::BeefyKeystore, metric_inc, metric_set, metrics::VoterMetrics, round::{Rounds, VoteImportResult}, @@ -45,8 +45,8 @@ use sp_consensus::SyncOracle; use sp_consensus_beefy::{ check_equivocation_proof, ecdsa_crypto::{AuthorityId, Signature}, - BeefyApi, Commitment, ConsensusLog, EquivocationProof, PayloadProvider, ValidatorSet, - VersionedFinalityProof, VoteMessage, BEEFY_ENGINE_ID, + BeefyApi, BeefySignatureHasher, Commitment, ConsensusLog, EquivocationProof, PayloadProvider, + ValidatorSet, VersionedFinalityProof, VoteMessage, BEEFY_ENGINE_ID, }; use sp_runtime::{ generic::{BlockId, OpaqueDigestItemId}, @@ -340,7 +340,7 @@ pub(crate) struct BeefyWorkerBase { // utilities pub backend: Arc, pub runtime: Arc, - pub key_store: BeefyKeystore, + pub key_store: BeefyKeystore, /// BEEFY client metrics. pub metrics: Option, @@ -1278,8 +1278,11 @@ pub(crate) mod tests { use sc_network_test::TestNetFactory; use sp_blockchain::Backend as BlockchainBackendT; use sp_consensus_beefy::{ - generate_equivocation_proof, known_payloads, known_payloads::MMR_ROOT_ID, - mmr::MmrRootProvider, Keyring, Payload, SignedCommitment, + known_payloads, + known_payloads::MMR_ROOT_ID, + mmr::MmrRootProvider, + test_utils::{generate_equivocation_proof, Keyring}, + Payload, SignedCommitment, }; use sp_runtime::traits::{Header as HeaderT, One}; use substrate_test_runtime_client::{ @@ -1309,7 +1312,7 @@ pub(crate) mod tests { fn create_beefy_worker( peer: &mut BeefyPeer, - key: &Keyring, + key: &Keyring, min_block_delta: u32, genesis_validator_set: ValidatorSet, ) -> BeefyWorker< @@ -1319,7 +1322,7 @@ pub(crate) mod tests { TestApi, Arc>, > { - let keystore = create_beefy_keystore(*key); + let keystore = create_beefy_keystore(key); let (to_rpc_justif_sender, from_voter_justif_stream) = BeefyVersionedFinalityProofStream::::channel(); diff --git a/substrate/client/keystore/src/local.rs b/substrate/client/keystore/src/local.rs index 3b29f435e2a..ca4a87ef383 100644 --- a/substrate/client/keystore/src/local.rs +++ b/substrate/client/keystore/src/local.rs @@ -37,7 +37,7 @@ use sp_core::bandersnatch; } sp_keystore::bls_experimental_enabled! { -use sp_core::{bls377, bls381, ecdsa_bls377}; +use sp_core::{bls377, bls381, ecdsa_bls377, KeccakHasher}; } use crate::{Error, Result}; @@ -391,6 +391,20 @@ impl Keystore for LocalKeystore { self.sign::(key_type, public, msg) } + fn ecdsa_bls377_sign_with_keccak256( + &self, + key_type: KeyTypeId, + public: &ecdsa_bls377::Public, + msg: &[u8], + ) -> std::result::Result, TraitError> { + let sig = self.0 + .read() + .key_pair_by_type::(public, key_type)? + .map(|pair| pair.sign_with_hasher::(msg)); + Ok(sig) + } + + } } diff --git a/substrate/frame/beefy/src/tests.rs b/substrate/frame/beefy/src/tests.rs index bf5ae19510c..453cf19a4fe 100644 --- a/substrate/frame/beefy/src/tests.rs +++ b/substrate/frame/beefy/src/tests.rs @@ -15,21 +15,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::vec; - use codec::Encode; -use sp_consensus_beefy::{ - check_equivocation_proof, generate_equivocation_proof, known_payloads::MMR_ROOT_ID, - Keyring as BeefyKeyring, Payload, ValidatorSet, KEY_TYPE as BEEFY_KEY_TYPE, -}; - -use sp_runtime::DigestItem; +use std::vec; use frame_support::{ assert_err, assert_ok, dispatch::{GetDispatchInfo, Pays}, traits::{Currency, KeyOwnerProofSystem, OnInitialize}, }; +use sp_consensus_beefy::{ + check_equivocation_proof, + known_payloads::MMR_ROOT_ID, + test_utils::{generate_equivocation_proof, Keyring as BeefyKeyring}, + Payload, ValidatorSet, KEY_TYPE as BEEFY_KEY_TYPE, +}; +use sp_runtime::DigestItem; use crate::{mock::*, Call, Config, Error, Weight, WeightInfo}; diff --git a/substrate/primitives/consensus/beefy/Cargo.toml b/substrate/primitives/consensus/beefy/Cargo.toml index 6232eca5ff8..9fd9ce5d123 100644 --- a/substrate/primitives/consensus/beefy/Cargo.toml +++ b/substrate/primitives/consensus/beefy/Cargo.toml @@ -25,6 +25,7 @@ sp-crypto-hashing = { path = "../../crypto/hashing", default-features = false } sp-io = { path = "../../io", default-features = false } sp-mmr-primitives = { path = "../../merkle-mountain-range", default-features = false } sp-runtime = { path = "../../runtime", default-features = false } +sp-keystore = { path = "../../keystore", default-features = false } sp-std = { path = "../../std", default-features = false } strum = { version = "0.24.1", features = ["derive"], default-features = false } lazy_static = { version = "1.4.0", optional = true } @@ -45,6 +46,7 @@ std = [ "sp-core/std", "sp-crypto-hashing/std", "sp-io/std", + "sp-keystore/std", "sp-mmr-primitives/std", "sp-runtime/std", "sp-std/std", diff --git a/substrate/primitives/consensus/beefy/src/commitment.rs b/substrate/primitives/consensus/beefy/src/commitment.rs index 37be1a4f6fc..1f0fb34ebf1 100644 --- a/substrate/primitives/consensus/beefy/src/commitment.rs +++ b/substrate/primitives/consensus/beefy/src/commitment.rs @@ -398,7 +398,7 @@ mod tests { assert_eq!( encoded, array_bytes::hex2bytes_unchecked( - "046d68343048656c6c6f20576f726c642105000000000000000000000000000000000000000000000004300400000008558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c746cc321f2319a5e99a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba01667603fc041cf9d7147d22bf54b15e5778893d6986b71a929747befd3b4d233fbe668bc480e8865116b94db46ca25a01e03c71955f2582604e415da68f2c3c406b9d5f4ad416230ec5453f05ac16a50d8d0923dfb0413cc956ae3fa6334465bd1f2cacec8e9cd606438390fe2a29dc052d6e1f8105c337a86cdd9aaacdc496577f3db8c55ef9e6fd48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487bca2324b6a0046395a71681be3d0c2a00df61d3b2be0963eb6caa243cc505d327aec73e1bb7ffe9a14b1354b0c406792ac6d6f47c06987c15dec9993f43eefa001d866fe0850d986702c414840f0d9ec0fdc04832ef91ae37c8d49e2f573ca50cb37f152801d489a19395cb04e5fc8f2ab6954b58a3bcc40ef9b6409d2ff7ef07" + "046d68343048656c6c6f20576f726c642105000000000000000000000000000000000000000000000004300400000008558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c746cc321f2319a5e99a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba015dd1c9b2237e54baa93d232cdf83a430b58a5efbc2f86ca1bab173a315ff6f15bef161425750c028055e9a23947b73002889a8b22168628438875a8ef25d76db998a80187b50719471286f054f3b3809b77a0cd87d7fe9c1a9d5d562683e25a70610f0804e92340549a43a7159b77b0c2d6e1f8105c337a86cdd9aaacdc496577f3db8c55ef9e6fd48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487bca2324b6a0046395a71681be3d0c2a001074884b6998c82331bd57ffa0a02cbfd02483c765b9216eab6a1fc119206236bf7971be68acaebff7400edee943240006a6096c9cfa65e9eb4e67f025c27112d14b4574fb208c439500f45cf3a8060f6cf009044f3141cce0364a7c2710a19b1bdf4abf27f86e5e3db08bddd35a7d12" ) ); } diff --git a/substrate/primitives/consensus/beefy/src/lib.rs b/substrate/primitives/consensus/beefy/src/lib.rs index 68eeeb3c680..1c3801e3a50 100644 --- a/substrate/primitives/consensus/beefy/src/lib.rs +++ b/substrate/primitives/consensus/beefy/src/lib.rs @@ -32,20 +32,22 @@ //! while GRANDPA uses `ed25519`. mod commitment; -pub mod mmr; mod payload; -#[cfg(feature = "std")] -mod test_utils; + +pub mod mmr; pub mod witness; +/// Test utilities +#[cfg(feature = "std")] +pub mod test_utils; + pub use commitment::{Commitment, SignedCommitment, VersionedFinalityProof}; pub use payload::{known_payloads, BeefyPayloadId, Payload, PayloadProvider}; -#[cfg(feature = "std")] -pub use test_utils::*; use codec::{Codec, Decode, Encode}; +use core::fmt::{Debug, Display}; use scale_info::TypeInfo; -use sp_application_crypto::RuntimeAppPublic; +use sp_application_crypto::{AppCrypto, AppPublic, ByteArray, RuntimeAppPublic}; use sp_core::H256; use sp_runtime::traits::{Hash, Keccak256, NumberFor}; use sp_std::prelude::*; @@ -63,6 +65,25 @@ pub trait BeefyAuthorityId: RuntimeAppPublic { fn verify(&self, signature: &::Signature, msg: &[u8]) -> bool; } +/// Hasher used for BEEFY signatures. +pub type BeefySignatureHasher = sp_runtime::traits::Keccak256; + +/// A trait bound which lists all traits which are required to be implemented by +/// a BEEFY AuthorityId type in order to be able to be used in BEEFY Keystore +pub trait AuthorityIdBound: + Codec + + Debug + + Clone + + AsRef<[u8]> + + ByteArray + + AppPublic + + AppCrypto + + RuntimeAppPublic + + Display + + BeefyAuthorityId +{ +} + /// BEEFY cryptographic types for ECDSA crypto /// /// This module basically introduces four crypto types: @@ -74,7 +95,7 @@ pub trait BeefyAuthorityId: RuntimeAppPublic { /// Your code should use the above types as concrete types for all crypto related /// functionality. pub mod ecdsa_crypto { - use super::{BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE}; + use super::{AuthorityIdBound, BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE}; use sp_application_crypto::{app_crypto, ecdsa}; use sp_core::crypto::Wraps; @@ -101,6 +122,7 @@ pub mod ecdsa_crypto { } } } + impl AuthorityIdBound for AuthorityId {} } /// BEEFY cryptographic types for BLS crypto @@ -116,7 +138,7 @@ pub mod ecdsa_crypto { #[cfg(feature = "bls-experimental")] pub mod bls_crypto { - use super::{BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE}; + use super::{AuthorityIdBound, BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE}; use sp_application_crypto::{app_crypto, bls377}; use sp_core::{bls377::Pair as BlsPair, crypto::Wraps, Pair as _}; @@ -134,13 +156,14 @@ pub mod bls_crypto { { fn verify(&self, signature: &::Signature, msg: &[u8]) -> bool { // `w3f-bls` library uses IETF hashing standard and as such does not expose - // a choice of hash to field function. + // a choice of hash-to-field function. // We are directly calling into the library to avoid introducing new host call. // and because BeefyAuthorityId::verify is being called in the runtime so we don't have BlsPair::verify(signature.as_inner_ref(), msg, self.as_inner_ref()) } } + impl AuthorityIdBound for AuthorityId {} } /// BEEFY cryptographic types for (ECDSA,BLS) crypto pair @@ -155,7 +178,7 @@ pub mod bls_crypto { /// functionality. #[cfg(feature = "bls-experimental")] pub mod ecdsa_bls_crypto { - use super::{BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE}; + use super::{AuthorityIdBound, BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE}; use sp_application_crypto::{app_crypto, ecdsa_bls377}; use sp_core::{crypto::Wraps, ecdsa_bls377::Pair as EcdsaBlsPair}; @@ -187,6 +210,8 @@ pub mod ecdsa_bls_crypto { ) } } + + impl AuthorityIdBound for AuthorityId {} } /// The `ConsensusEngineId` of BEEFY. diff --git a/substrate/primitives/consensus/beefy/src/test_utils.rs b/substrate/primitives/consensus/beefy/src/test_utils.rs index a6e65e5bff0..ec13c9c6900 100644 --- a/substrate/primitives/consensus/beefy/src/test_utils.rs +++ b/substrate/primitives/consensus/beefy/src/test_utils.rs @@ -15,18 +15,24 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![cfg(feature = "std")] +#[cfg(feature = "bls-experimental")] +use crate::ecdsa_bls_crypto; +use crate::{ + ecdsa_crypto, AuthorityIdBound, BeefySignatureHasher, Commitment, EquivocationProof, Payload, + ValidatorSetId, VoteMessage, +}; +use sp_application_crypto::{AppCrypto, AppPair, RuntimeAppPublic, Wraps}; +use sp_core::{ecdsa, Pair}; +use sp_runtime::traits::Hash; -use crate::{ecdsa_crypto, Commitment, EquivocationProof, Payload, ValidatorSetId, VoteMessage}; use codec::Encode; -use sp_core::{ecdsa, Pair}; -use std::collections::HashMap; +use std::{collections::HashMap, marker::PhantomData}; use strum::IntoEnumIterator; /// Set of test accounts using [`crate::ecdsa_crypto`] types. #[allow(missing_docs)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::Display, strum::EnumIter)] -pub enum Keyring { +pub enum Keyring { Alice, Bob, Charlie, @@ -35,71 +41,110 @@ pub enum Keyring { Ferdie, One, Two, + _Marker(PhantomData), +} + +/// Trait representing BEEFY specific generation and signing behavior of authority id +/// +/// Accepts custom hashing fn for the message and custom convertor fn for the signer. +pub trait BeefySignerAuthority: AppPair { + /// Generate and return signature for `message` using custom hashing `MsgHash` + fn sign_with_hasher(&self, message: &[u8]) -> ::Signature; +} + +impl BeefySignerAuthority for ::Pair +where + MsgHash: Hash, + ::Output: Into<[u8; 32]>, +{ + fn sign_with_hasher(&self, message: &[u8]) -> ::Signature { + let hashed_message = ::hash(message).into(); + self.as_inner_ref().sign_prehashed(&hashed_message).into() + } +} + +#[cfg(feature = "bls-experimental")] +impl BeefySignerAuthority for ::Pair +where + MsgHash: Hash, + ::Output: Into<[u8; 32]>, +{ + fn sign_with_hasher(&self, message: &[u8]) -> ::Signature { + self.as_inner_ref().sign_with_hasher::(&message).into() + } } -impl Keyring { +/// Implement Keyring functionalities generically over AuthorityId +impl Keyring +where + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + ::Pair: BeefySignerAuthority, + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, +{ /// Sign `msg`. - pub fn sign(self, msg: &[u8]) -> ecdsa_crypto::Signature { - // todo: use custom signature hashing type - let msg = sp_crypto_hashing::keccak_256(msg); - ecdsa::Pair::from(self).sign_prehashed(&msg).into() + pub fn sign(&self, msg: &[u8]) -> ::Signature { + let key_pair: ::Pair = self.pair(); + key_pair.sign_with_hasher(&msg).into() } /// Return key pair. - pub fn pair(self) -> ecdsa_crypto::Pair { - ecdsa::Pair::from_string(self.to_seed().as_str(), None).unwrap().into() + pub fn pair(&self) -> ::Pair { + ::Pair::from_string(self.to_seed().as_str(), None) + .unwrap() + .into() } /// Return public key. - pub fn public(self) -> ecdsa_crypto::Public { - self.pair().public() + pub fn public(&self) -> AuthorityId { + self.pair().public().into() } /// Return seed string. - pub fn to_seed(self) -> String { + pub fn to_seed(&self) -> String { format!("//{}", self) } /// Get Keyring from public key. - pub fn from_public(who: &ecdsa_crypto::Public) -> Option { - Self::iter().find(|&k| &ecdsa_crypto::Public::from(k) == who) + pub fn from_public(who: &AuthorityId) -> Option> { + Self::iter().find(|k| k.public() == *who) } } lazy_static::lazy_static! { - static ref PRIVATE_KEYS: HashMap = - Keyring::iter().map(|i| (i, i.pair())).collect(); - static ref PUBLIC_KEYS: HashMap = - PRIVATE_KEYS.iter().map(|(&name, pair)| (name, pair.public())).collect(); + static ref PRIVATE_KEYS: HashMap, ecdsa_crypto::Pair> = + Keyring::iter().map(|i| (i.clone(), i.pair())).collect(); + static ref PUBLIC_KEYS: HashMap, ecdsa_crypto::Public> = + PRIVATE_KEYS.iter().map(|(name, pair)| (name.clone(), sp_application_crypto::Pair::public(pair))).collect(); } -impl From for ecdsa_crypto::Pair { - fn from(k: Keyring) -> Self { +impl From> for ecdsa_crypto::Pair { + fn from(k: Keyring) -> Self { k.pair() } } -impl From for ecdsa::Pair { - fn from(k: Keyring) -> Self { +impl From> for ecdsa::Pair { + fn from(k: Keyring) -> Self { k.pair().into() } } -impl From for ecdsa_crypto::Public { - fn from(k: Keyring) -> Self { +impl From> for ecdsa_crypto::Public { + fn from(k: Keyring) -> Self { (*PUBLIC_KEYS).get(&k).cloned().unwrap() } } /// Create a new `EquivocationProof` based on given arguments. pub fn generate_equivocation_proof( - vote1: (u64, Payload, ValidatorSetId, &Keyring), - vote2: (u64, Payload, ValidatorSetId, &Keyring), + vote1: (u64, Payload, ValidatorSetId, &Keyring), + vote2: (u64, Payload, ValidatorSetId, &Keyring), ) -> EquivocationProof { let signed_vote = |block_number: u64, payload: Payload, validator_set_id: ValidatorSetId, - keyring: &Keyring| { + keyring: &Keyring| { let commitment = Commitment { validator_set_id, block_number, payload }; let signature = keyring.sign(&commitment.encode()); VoteMessage { commitment, id: keyring.public(), signature } diff --git a/substrate/primitives/core/src/bandersnatch.rs b/substrate/primitives/core/src/bandersnatch.rs index 96b0ff19e56..61e7162544a 100644 --- a/substrate/primitives/core/src/bandersnatch.rs +++ b/substrate/primitives/core/src/bandersnatch.rs @@ -985,6 +985,19 @@ mod tests { assert!(res.is_err()); } + #[test] + fn generate_with_phrase_should_be_recoverable_with_from_string() { + let (pair, phrase, seed) = Pair::generate_with_phrase(None); + let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_seed.public()); + let (repair_phrase, reseed) = + Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid"); + assert_eq!(seed, reseed); + assert_eq!(pair.public(), repair_phrase.public()); + let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_string.public()); + } + #[test] fn sign_verify() { let pair = Pair::from_seed(DEV_SEED); diff --git a/substrate/primitives/core/src/bls.rs b/substrate/primitives/core/src/bls.rs index 452c6372d16..0c84d0ba8e6 100644 --- a/substrate/primitives/core/src/bls.rs +++ b/substrate/primitives/core/src/bls.rs @@ -15,7 +15,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Simple BLS (Boneh–Lynn–Shacham) Signature API. +//! BLS (Boneh–Lynn–Shacham) Signature along with efficiently verifiable Chaum-Pedersen proof API. +//! Signatures are implemented according to +//! [Efficient Aggregatable BLS Signatures with Chaum-Pedersen Proofs](https://eprint.iacr.org/2022/1611) +//! Hash-to-BLS-curve is using Simplified SWU for AB == 0 +//! [RFC 9380](https://datatracker.ietf.org/doc/rfc9380/) Sect 6.6.3. +//! Chaum-Pedersen proof uses the same hash-to-field specified in RFC 9380 for the field of the BLS +//! curve. #[cfg(feature = "serde")] use crate::crypto::Ss58Codec; @@ -452,11 +458,12 @@ impl TraitPair for Pair { fn derive>( &self, path: Iter, - _seed: Option, + seed: Option, ) -> Result<(Self, Option), DeriveError> { - let mut acc: [u8; SECRET_KEY_SERIALIZED_SIZE] = self.0.secret.to_bytes().try_into().expect( - "Secret key serializer returns a vector of SECRET_KEY_SERIALIZED_SIZE size; qed", - ); + let mut acc: [u8; SECRET_KEY_SERIALIZED_SIZE] = + seed.unwrap_or(self.0.secret.to_bytes().try_into().expect( + "Secret key serializer returns a vector of SECRET_KEY_SERIALIZED_SIZE size; qed", + )); for j in path { match j { DeriveJunction::Soft(_cc) => return Err(DeriveError::SoftKeyInPath), @@ -544,7 +551,7 @@ mod test { "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", ); let pair = Pair::from_seed(&seed); - // we are using hash to field so this is not going to work + // we are using hash-to-field so this is not going to work // assert_eq!(pair.seed(), seed); let path = vec![DeriveJunction::Hard([0u8; 32])]; let derived = pair.derive(path.into_iter(), None).ok().unwrap().0; @@ -588,12 +595,12 @@ mod test { assert_eq!( public, Public::unchecked_from(array_bytes::hex2array_unchecked( - "6dc6be608fab3c6bd894a606be86db346cc170db85c733853a371f3db54ae1b12052c0888d472760c81b537572a26f00db865e5963aef8634f9917571c51b538b564b2a9ceda938c8b930969ee3b832448e08e33a79e9ddd28af419a3ce45300f5dbc768b067781f44f3fe05a19e6b07b1c4196151ec3f8ea37e4f89a8963030d2101e931276bb9ebe1f20102239d780" + "7a84ca8ce4c37c93c95ecee6a3c0c9a7b9c225093cf2f12dc4f69cbfb847ef9424a18f5755d5a742247d386ff2aabb806bcf160eff31293ea9616976628f77266c8a8cc1d8753be04197bd6cdd8c5c87a148f782c4c1568d599b48833fd539001e580cff64bbc71850605433fcd051f3afc3b74819786f815ffb5272030a8d03e5df61e6183f8fd8ea85f26defa83400" )) ); let message = b""; let signature = - array_bytes::hex2array_unchecked("bbb395bbdee1a35930912034f5fde3b36df2835a0536c865501b0675776a1d5931a3bea2e66eff73b2546c6af2061a8019223e4ebbbed661b2538e0f5823f2c708eb89c406beca8fcb53a5c13dbc7c0c42e4cf2be2942bba96ea29297915a06bd2b1b979c0e2ac8fd4ec684a6b5d110c" + array_bytes::hex2array_unchecked("d1e3013161991e142d8751017d4996209c2ff8a9ee160f373733eda3b4b785ba6edce9f45f87104bbe07aa6aa6eb2780aa705efb2c13d3b317d6409d159d23bdc7cdd5c2a832d1551cf49d811d49c901495e527dbd532e3a462335ce2686009104aba7bc11c5b22be78f3198d2727a0b" ); let expected_signature = Signature::unchecked_from(signature); println!("signature is {:?}", pair.sign(&message[..])); @@ -647,12 +654,30 @@ mod test { assert_eq!(pair1.public(), pair2.public()); } + #[test] + fn generate_with_phrase_should_be_recoverable_with_from_string() { + let (pair, phrase, seed) = Pair::generate_with_phrase(None); + let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_seed.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + let (repair_phrase, reseed) = + Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid"); + assert_eq!(seed, reseed); + assert_eq!(pair.public(), repair_phrase.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + + let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_string.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + } + #[test] fn password_does_something() { let (pair1, phrase, _) = Pair::generate_with_phrase(Some("password")); let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap(); assert_ne!(pair1.public(), pair2.public()); + assert_ne!(pair1.to_raw_vec(), pair2.to_raw_vec()); } #[test] diff --git a/substrate/primitives/core/src/ecdsa.rs b/substrate/primitives/core/src/ecdsa.rs index 1b63db7af7f..f172b3a7d02 100644 --- a/substrate/primitives/core/src/ecdsa.rs +++ b/substrate/primitives/core/src/ecdsa.rs @@ -644,12 +644,29 @@ mod test { assert_eq!(pair1.public(), pair2.public()); } + #[test] + fn generate_with_phrase_should_be_recoverable_with_from_string() { + let (pair, phrase, seed) = Pair::generate_with_phrase(None); + let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_seed.public()); + assert_eq!(pair.secret, repair_seed.secret); + let (repair_phrase, reseed) = + Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid"); + assert_eq!(seed, reseed); + assert_eq!(pair.public(), repair_phrase.public()); + assert_eq!(pair.secret, repair_phrase.secret); + let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_string.public()); + assert_eq!(pair.secret, repair_string.secret); + } + #[test] fn password_does_something() { let (pair1, phrase, _) = Pair::generate_with_phrase(Some("password")); let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap(); assert_ne!(pair1.public(), pair2.public()); + assert_ne!(pair1.secret, pair2.secret); } #[test] diff --git a/substrate/primitives/core/src/ed25519.rs b/substrate/primitives/core/src/ed25519.rs index aa0d77510bd..60ebd93e12d 100644 --- a/substrate/primitives/core/src/ed25519.rs +++ b/substrate/primitives/core/src/ed25519.rs @@ -501,6 +501,22 @@ mod test { ); } + #[test] + fn generate_with_phrase_should_be_recoverable_with_from_string() { + let (pair, phrase, seed) = Pair::generate_with_phrase(None); + let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_seed.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + let (repair_phrase, reseed) = + Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid"); + assert_eq!(seed, reseed); + assert_eq!(pair.public(), repair_phrase.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_string.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + } + #[test] fn test_vector_should_work() { let pair = Pair::from_seed(&array_bytes::hex2array_unchecked( @@ -590,6 +606,7 @@ mod test { let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap(); assert_ne!(pair1.public(), pair2.public()); + assert_ne!(pair1.to_raw_vec(), pair2.to_raw_vec()); } #[test] diff --git a/substrate/primitives/core/src/paired_crypto.rs b/substrate/primitives/core/src/paired_crypto.rs index 960b8469249..20b32c339bd 100644 --- a/substrate/primitives/core/src/paired_crypto.rs +++ b/substrate/primitives/core/src/paired_crypto.rs @@ -460,10 +460,11 @@ where path: Iter, seed: Option, ) -> Result<(Self, Option), DeriveError> { - let path: Vec<_> = path.collect(); + let left_path: Vec<_> = path.collect(); + let right_path: Vec<_> = left_path.clone(); - let left = self.left.derive(path.iter().cloned(), seed.map(|s| s.into()))?; - let right = self.right.derive(path.into_iter(), seed.map(|s| s.into()))?; + let left = self.left.derive(left_path.into_iter(), seed.map(|s| s.into()))?; + let right = self.right.derive(right_path.into_iter(), seed.map(|s| s.into()))?; let seed = match (left.1, right.1) { (Some(l), Some(r)) if l.as_ref() == r.as_ref() => Some(l.into()), @@ -542,13 +543,30 @@ mod test { ); } + #[test] + fn generate_with_phrase_should_be_recoverable_with_from_string() { + let (pair, phrase, seed) = Pair::generate_with_phrase(None); + let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_seed.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + + let (repair_phrase, reseed) = + Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid"); + assert_eq!(seed, reseed); + assert_eq!(pair.public(), repair_phrase.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_string.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + } + #[test] fn seed_and_derive_should_work() { let seed_for_right_and_left: [u8; SECURE_SEED_LEN] = array_bytes::hex2array_unchecked( "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", ); let pair = Pair::from_seed(&seed_for_right_and_left); - // we are using hash to field so this is not going to work + // we are using hash-to-field so this is not going to work // assert_eq!(pair.seed(), seed); let path = vec![DeriveJunction::Hard([0u8; 32])]; let derived = pair.derive(path.into_iter(), None).ok().unwrap().0; @@ -599,13 +617,13 @@ mod test { assert_eq!( public, Public::unchecked_from( - array_bytes::hex2array_unchecked("028db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd916dc6be608fab3c6bd894a606be86db346cc170db85c733853a371f3db54ae1b12052c0888d472760c81b537572a26f00db865e5963aef8634f9917571c51b538b564b2a9ceda938c8b930969ee3b832448e08e33a79e9ddd28af419a3ce45300f5dbc768b067781f44f3fe05a19e6b07b1c4196151ec3f8ea37e4f89a8963030d2101e931276bb9ebe1f20102239d780" + array_bytes::hex2array_unchecked("028db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd917a84ca8ce4c37c93c95ecee6a3c0c9a7b9c225093cf2f12dc4f69cbfb847ef9424a18f5755d5a742247d386ff2aabb806bcf160eff31293ea9616976628f77266c8a8cc1d8753be04197bd6cdd8c5c87a148f782c4c1568d599b48833fd539001e580cff64bbc71850605433fcd051f3afc3b74819786f815ffb5272030a8d03e5df61e6183f8fd8ea85f26defa83400" ), ), ); let message = b""; let signature = - array_bytes::hex2array_unchecked("3dde91174bd9359027be59a428b8146513df80a2a3c7eda2194f64de04a69ab97b753169e94db6ffd50921a2668a48b94ca11e3d32c1ff19cfe88890aa7e8f3c00bbb395bbdee1a35930912034f5fde3b36df2835a0536c865501b0675776a1d5931a3bea2e66eff73b2546c6af2061a8019223e4ebbbed661b2538e0f5823f2c708eb89c406beca8fcb53a5c13dbc7c0c42e4cf2be2942bba96ea29297915a06bd2b1b979c0e2ac8fd4ec684a6b5d110c" + array_bytes::hex2array_unchecked("3dde91174bd9359027be59a428b8146513df80a2a3c7eda2194f64de04a69ab97b753169e94db6ffd50921a2668a48b94ca11e3d32c1ff19cfe88890aa7e8f3c00d1e3013161991e142d8751017d4996209c2ff8a9ee160f373733eda3b4b785ba6edce9f45f87104bbe07aa6aa6eb2780aa705efb2c13d3b317d6409d159d23bdc7cdd5c2a832d1551cf49d811d49c901495e527dbd532e3a462335ce2686009104aba7bc11c5b22be78f3198d2727a0b" ); let signature = Signature::unchecked_from(signature); assert!(pair.sign(&message[..]) == signature); @@ -664,6 +682,7 @@ mod test { let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap(); assert_ne!(pair1.public(), pair2.public()); + assert_ne!(pair1.to_raw_vec(), pair2.to_raw_vec()); } #[test] diff --git a/substrate/primitives/core/src/sr25519.rs b/substrate/primitives/core/src/sr25519.rs index b821055e2c5..7c02afc3cd5 100644 --- a/substrate/primitives/core/src/sr25519.rs +++ b/substrate/primitives/core/src/sr25519.rs @@ -970,6 +970,22 @@ mod tests { assert!(Pair::verify(&signature, &message[..], &public)); } + #[test] + fn generate_with_phrase_should_be_recoverable_with_from_string() { + let (pair, phrase, seed) = Pair::generate_with_phrase(None); + let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_seed.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + let (repair_phrase, reseed) = + Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid"); + assert_eq!(seed, reseed); + assert_eq!(pair.public(), repair_phrase.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_string.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + } + #[test] fn generated_pair_should_work() { let (pair, _) = Pair::generate(); diff --git a/substrate/primitives/keystore/src/lib.rs b/substrate/primitives/keystore/src/lib.rs index 07c4e2d5fd1..5fae27bc328 100644 --- a/substrate/primitives/keystore/src/lib.rs +++ b/substrate/primitives/keystore/src/lib.rs @@ -355,6 +355,24 @@ pub trait Keystore: Send + Sync { msg: &[u8], ) -> Result, Error>; + /// Hashes the `message` using keccak256 and then signs it using ECDSA + /// algorithm. It does not affect the behavior of BLS12-377 component. It generates + /// BLS12-377 Signature according to IETF standard. + /// + /// Receives [`KeyTypeId`] and a [`ecdsa_bls377::Public`] key to be able to map + /// them to a private key that exists in the keystore. + /// + /// Returns an [`ecdsa_bls377::Signature`] or `None` in case the given `key_type` + /// and `public` combination doesn't exist in the keystore. + /// An `Err` will be returned if generating the signature itself failed. + #[cfg(feature = "bls-experimental")] + fn ecdsa_bls377_sign_with_keccak256( + &self, + key_type: KeyTypeId, + public: &ecdsa_bls377::Public, + msg: &[u8], + ) -> Result, Error>; + /// Insert a new secret key. fn insert(&self, key_type: KeyTypeId, suri: &str, public: &[u8]) -> Result<(), ()>; @@ -661,6 +679,16 @@ impl Keystore for Arc { (**self).ecdsa_bls377_sign(key_type, public, msg) } + #[cfg(feature = "bls-experimental")] + fn ecdsa_bls377_sign_with_keccak256( + &self, + key_type: KeyTypeId, + public: &ecdsa_bls377::Public, + msg: &[u8], + ) -> Result, Error> { + (**self).ecdsa_bls377_sign_with_keccak256(key_type, public, msg) + } + fn insert(&self, key_type: KeyTypeId, suri: &str, public: &[u8]) -> Result<(), ()> { (**self).insert(key_type, suri, public) } diff --git a/substrate/primitives/keystore/src/testing.rs b/substrate/primitives/keystore/src/testing.rs index 2879c458b4f..e10660b126a 100644 --- a/substrate/primitives/keystore/src/testing.rs +++ b/substrate/primitives/keystore/src/testing.rs @@ -22,7 +22,7 @@ use crate::{Error, Keystore, KeystorePtr}; #[cfg(feature = "bandersnatch-experimental")] use sp_core::bandersnatch; #[cfg(feature = "bls-experimental")] -use sp_core::{bls377, bls381, ecdsa_bls377}; +use sp_core::{bls377, bls381, ecdsa_bls377, KeccakHasher}; use sp_core::{ crypto::{ByteArray, KeyTypeId, Pair, VrfSecret}, ecdsa, ed25519, sr25519, @@ -346,6 +346,19 @@ impl Keystore for MemoryKeystore { self.sign::(key_type, public, msg) } + #[cfg(feature = "bls-experimental")] + fn ecdsa_bls377_sign_with_keccak256( + &self, + key_type: KeyTypeId, + public: &ecdsa_bls377::Public, + msg: &[u8], + ) -> Result, Error> { + let sig = self + .pair::(key_type, public) + .map(|pair| pair.sign_with_hasher::(msg)); + Ok(sig) + } + fn insert(&self, key_type: KeyTypeId, suri: &str, public: &[u8]) -> Result<(), ()> { self.keys .write() @@ -493,6 +506,38 @@ mod tests { assert!(res.is_some()); } + #[test] + #[cfg(feature = "bls-experimental")] + fn ecdsa_bls377_sign_with_keccak_works() { + use sp_core::testing::ECDSA_BLS377; + + let store = MemoryKeystore::new(); + + let suri = "//Alice"; + let pair = ecdsa_bls377::Pair::from_string(suri, None).unwrap(); + + let msg = b"this should be a normal unhashed message not "; + + // insert key, sign again + store.insert(ECDSA_BLS377, suri, pair.public().as_ref()).unwrap(); + + let res = store + .ecdsa_bls377_sign_with_keccak256(ECDSA_BLS377, &pair.public(), &msg[..]) + .unwrap(); + + assert!(res.is_some()); + + // does not verify with default out-of-the-box verification + assert!(!ecdsa_bls377::Pair::verify(&res.clone().unwrap(), &msg[..], &pair.public())); + + // should verify using keccak256 as hasher + assert!(ecdsa_bls377::Pair::verify_with_hasher::( + &res.unwrap(), + msg, + &pair.public() + )); + } + #[test] #[cfg(feature = "bandersnatch-experimental")] fn bandersnatch_vrf_sign() { -- GitLab From a2e6256c0d264fa75c5367ccdc5939136e3e47a8 Mon Sep 17 00:00:00 2001 From: Radha <86818441+DrW3RK@users.noreply.github.com> Date: Thu, 8 Feb 2024 17:20:30 +0100 Subject: [PATCH 031/133] Update README.md - Runtimes on Fellowship Repo (#3263) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Liam Aharon Co-authored-by: Bastian Köcher --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 93f9539a94a..63743a456f4 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,9 @@ way. The Polkadot SDK comprises three main pieces of software: [![Polkadot-license](https://img.shields.io/badge/License-GPL3-blue)](./polkadot/LICENSE) Implementation of a node for the https://polkadot.network in Rust, using the Substrate framework. This directory -currently contains runtimes for the Polkadot, Kusama, Westend, and Rococo networks. In the future, these will be -relocated to the [`runtimes`](https://github.com/polkadot-fellows/runtimes/) repository. +currently contains runtimes for the Westend and Rococo test networks. Polkadot, Kusama and their system chain runtimes +are located in the [`runtimes`](https://github.com/polkadot-fellows/runtimes/) repository maintained by +[the Polkadot Technical Fellowship](https://polkadot-fellows.github.io/dashboard/#/overview). ## [Substrate](./substrate/) [![SubstrateRustDocs](https://img.shields.io/badge/Rust_Docs-Substrate-24CC85?logo=rust)](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/polkadot_sdk/substrate/index.html) -- GitLab From c36c51cac3444d65f512aafaa79647712d6f3b8a Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 8 Feb 2024 18:27:52 +0100 Subject: [PATCH 032/133] `bench pallet`: only require `Hash` instead of `Block` (#3244) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preparation for https://github.com/paritytech/polkadot-sdk/issues/2664 Changes: - Only require `Hash` instead of `Block` for the benchmarking - Refactor DB types to do the same ## Integration This breaking change can easily be integrated into your node via: ```patch - cmd.run::(config) + cmd.run::, ()>(config) ``` Status: waiting for CI checks --------- Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Bastian Köcher Co-authored-by: cheme --- Cargo.lock | 1 + .../parachain-template/node/src/command.rs | 2 +- cumulus/polkadot-parachain/src/command.rs | 2 +- polkadot/cli/Cargo.toml | 8 +- polkadot/cli/src/command.rs | 2 +- prdoc/pr_3244.prdoc | 18 ++++ .../bin/node-template/node/src/command.rs | 2 +- substrate/bin/node/cli/src/command.rs | 3 +- substrate/client/db/src/bench.rs | 98 ++++++++++--------- substrate/client/db/src/lib.rs | 51 +++++----- .../benchmarking-cli/src/pallet/command.rs | 13 ++- .../benchmarking-cli/src/storage/write.rs | 6 +- 12 files changed, 120 insertions(+), 86 deletions(-) create mode 100644 prdoc/pr_3244.prdoc diff --git a/Cargo.lock b/Cargo.lock index 49507ecd4f9..b6fa96cd0a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12130,6 +12130,7 @@ dependencies = [ "sp-io", "sp-keyring", "sp-maybe-compressed-blob", + "sp-runtime", "substrate-build-script-utils", "thiserror", "try-runtime-cli", diff --git a/cumulus/parachain-template/node/src/command.rs b/cumulus/parachain-template/node/src/command.rs index 6ddb68a359a..72b3ab7bb4b 100644 --- a/cumulus/parachain-template/node/src/command.rs +++ b/cumulus/parachain-template/node/src/command.rs @@ -183,7 +183,7 @@ pub fn run() -> Result<()> { match cmd { BenchmarkCmd::Pallet(cmd) => if cfg!(feature = "runtime-benchmarks") { - runner.sync_run(|config| cmd.run::(config)) + runner.sync_run(|config| cmd.run::, ()>(config)) } else { Err("Benchmarking wasn't enabled when building the node. \ You can enable it with `--features runtime-benchmarks`." diff --git a/cumulus/polkadot-parachain/src/command.rs b/cumulus/polkadot-parachain/src/command.rs index 19d067068f4..3be232ce698 100644 --- a/cumulus/polkadot-parachain/src/command.rs +++ b/cumulus/polkadot-parachain/src/command.rs @@ -584,7 +584,7 @@ pub fn run() -> Result<()> { match cmd { BenchmarkCmd::Pallet(cmd) => if cfg!(feature = "runtime-benchmarks") { - runner.sync_run(|config| cmd.run::(config)) + runner.sync_run(|config| cmd.run::, ()>(config)) } else { Err("Benchmarking wasn't enabled when building the node. \ You can enable it with `--features runtime-benchmarks`." diff --git a/polkadot/cli/Cargo.toml b/polkadot/cli/Cargo.toml index 3186e31f39e..75afa2d199f 100644 --- a/polkadot/cli/Cargo.toml +++ b/polkadot/cli/Cargo.toml @@ -42,6 +42,7 @@ sc-tracing = { path = "../../substrate/client/tracing", optional = true } sc-sysinfo = { path = "../../substrate/client/sysinfo" } sc-executor = { path = "../../substrate/client/executor" } sc-storage-monitor = { path = "../../substrate/client/storage-monitor" } +sp-runtime = { path = "../../substrate/primitives/runtime" } [build-dependencies] substrate-build-script-utils = { path = "../../substrate/utils/build-script-utils" } @@ -63,9 +64,14 @@ runtime-benchmarks = [ "polkadot-node-metrics/runtime-benchmarks", "sc-service?/runtime-benchmarks", "service/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", ] full-node = ["service/full-node"] -try-runtime = ["service/try-runtime", "try-runtime-cli/try-runtime"] +try-runtime = [ + "service/try-runtime", + "sp-runtime/try-runtime", + "try-runtime-cli/try-runtime", +] fast-runtime = ["service/fast-runtime"] pyroscope = ["pyro", "pyroscope_pprofrs"] diff --git a/polkadot/cli/src/command.rs b/polkadot/cli/src/command.rs index 7319f28a2e2..f71891ecde3 100644 --- a/polkadot/cli/src/command.rs +++ b/polkadot/cli/src/command.rs @@ -451,7 +451,7 @@ pub fn run() -> Result<()> { if cfg!(feature = "runtime-benchmarks") { runner.sync_run(|config| { - cmd.run::(config) + cmd.run::, ()>(config) .map_err(|e| Error::SubstrateCli(e)) }) } else { diff --git a/prdoc/pr_3244.prdoc b/prdoc/pr_3244.prdoc new file mode 100644 index 00000000000..3d851c8afe0 --- /dev/null +++ b/prdoc/pr_3244.prdoc @@ -0,0 +1,18 @@ +title: "Make the `benchmark pallet` command only require a Hasher" + +doc: + - audience: Node Dev + description: | + Currently the `benchmark pallet` command requires a `Block` type, while only using its hasher. + Now this is changed to only require the hasher. This means to use `HashingFor` in the + place where `Block` was required. + Example patch for your node with `cmd` being `BenchmarkCmd::Pallet(cmd)`: + + ```patch + - cmd.run::(config) + + cmd.run::, ()>(config) + ``` + +crates: + - name: sc-client-db + - name: frame-benchmarking-cli diff --git a/substrate/bin/node-template/node/src/command.rs b/substrate/bin/node-template/node/src/command.rs index a25157693cd..3778df66422 100644 --- a/substrate/bin/node-template/node/src/command.rs +++ b/substrate/bin/node-template/node/src/command.rs @@ -117,7 +117,7 @@ pub fn run() -> sc_cli::Result<()> { ) } - cmd.run::(config) + cmd.run::, ()>(config) }, BenchmarkCmd::Block(cmd) => { let PartialComponents { client, .. } = service::new_partial(&config)?; diff --git a/substrate/bin/node/cli/src/command.rs b/substrate/bin/node/cli/src/command.rs index dc28705c2ae..96451732028 100644 --- a/substrate/bin/node/cli/src/command.rs +++ b/substrate/bin/node/cli/src/command.rs @@ -28,6 +28,7 @@ use node_primitives::Block; use sc_cli::{Result, SubstrateCli}; use sc_service::PartialComponents; use sp_keyring::Sr25519Keyring; +use sp_runtime::traits::HashingFor; use std::sync::Arc; @@ -106,7 +107,7 @@ pub fn run() -> Result<()> { ) } - cmd.run::(config) + cmd.run::, sp_statement_store::runtime_api::HostFunctions>(config) }, BenchmarkCmd::Block(cmd) => { // ensure that we keep the task manager alive diff --git a/substrate/client/db/src/bench.rs b/substrate/client/db/src/bench.rs index 03ad4817b53..32503cf63c0 100644 --- a/substrate/client/db/src/bench.rs +++ b/substrate/client/db/src/bench.rs @@ -19,7 +19,7 @@ //! State backend that's useful for benchmarking use crate::{DbState, DbStateBuilder}; -use hash_db::{Hasher, Prefix}; +use hash_db::{Hasher as DbHasher, Prefix}; use kvdb::{DBTransaction, KeyValueDB}; use linked_hash_map::LinkedHashMap; use parking_lot::Mutex; @@ -27,10 +27,7 @@ use sp_core::{ hexdisplay::HexDisplay, storage::{ChildInfo, TrackedStorageKey}, }; -use sp_runtime::{ - traits::{Block as BlockT, HashingFor}, - StateVersion, Storage, -}; +use sp_runtime::{traits::Hash, StateVersion, Storage}; use sp_state_machine::{ backend::Backend as StateBackend, BackendTransaction, ChildStorageCollection, DBValue, IterArgs, StorageCollection, StorageIterator, StorageKey, StorageValue, @@ -45,16 +42,16 @@ use std::{ sync::Arc, }; -type State = DbState; +type State = DbState; -struct StorageDb { +struct StorageDb { db: Arc, - _block: std::marker::PhantomData, + _phantom: std::marker::PhantomData, } -impl sp_state_machine::Storage> for StorageDb { - fn get(&self, key: &Block::Hash, prefix: Prefix) -> Result, String> { - let prefixed_key = prefixed_key::>(key, prefix); +impl sp_state_machine::Storage for StorageDb { + fn get(&self, key: &Hasher::Output, prefix: Prefix) -> Result, String> { + let prefixed_key = prefixed_key::(key, prefix); self.db .get(0, &prefixed_key) .map_err(|e| format!("Database backend error: {:?}", e)) @@ -75,29 +72,29 @@ struct KeyTracker { } /// State that manages the backend database reference. Allows runtime to control the database. -pub struct BenchmarkingState { - root: Cell, - genesis_root: B::Hash, - state: RefCell>>, +pub struct BenchmarkingState { + root: Cell, + genesis_root: Hasher::Output, + state: RefCell>>, db: Cell>>, genesis: HashMap, (Vec, i32)>, record: Cell>>, key_tracker: Arc>, whitelist: RefCell>, - proof_recorder: Option>>, - proof_recorder_root: Cell, - shared_trie_cache: SharedTrieCache>, + proof_recorder: Option>, + proof_recorder_root: Cell, + shared_trie_cache: SharedTrieCache, } /// A raw iterator over the `BenchmarkingState`. -pub struct RawIter { - inner: as StateBackend>>::RawIter, +pub struct RawIter { + inner: as StateBackend>::RawIter, child_trie: Option>, key_tracker: Arc>, } -impl StorageIterator> for RawIter { - type Backend = BenchmarkingState; +impl StorageIterator for RawIter { + type Backend = BenchmarkingState; type Error = String; fn next_key(&mut self, backend: &Self::Backend) -> Option> { @@ -128,7 +125,7 @@ impl StorageIterator> for RawIter { } } -impl BenchmarkingState { +impl BenchmarkingState { /// Create a new instance that creates a database in a temporary dir. pub fn new( genesis: Storage, @@ -137,9 +134,9 @@ impl BenchmarkingState { enable_tracking: bool, ) -> Result { let state_version = sp_runtime::StateVersion::default(); - let mut root = B::Hash::default(); - let mut mdb = MemoryDB::>::default(); - sp_trie::trie_types::TrieDBMutBuilderV1::>::new(&mut mdb, &mut root).build(); + let mut root = Default::default(); + let mut mdb = MemoryDB::::default(); + sp_trie::trie_types::TrieDBMutBuilderV1::::new(&mut mdb, &mut root).build(); let mut state = BenchmarkingState { state: RefCell::new(None), @@ -169,7 +166,7 @@ impl BenchmarkingState { child_content.data.iter().map(|(k, v)| (k.as_ref(), Some(v.as_ref()))), ) }); - let (root, transaction): (B::Hash, _) = + let (root, transaction): (Hasher::Output, _) = state.state.borrow().as_ref().unwrap().full_storage_root( genesis.top.iter().map(|(k, v)| (k.as_ref(), Some(v.as_ref()))), child_delta, @@ -193,9 +190,9 @@ impl BenchmarkingState { recorder.reset(); self.proof_recorder_root.set(self.root.get()); } - let storage_db = Arc::new(StorageDb:: { db, _block: Default::default() }); + let storage_db = Arc::new(StorageDb:: { db, _phantom: Default::default() }); *self.state.borrow_mut() = Some( - DbStateBuilder::::new(storage_db, self.root.get()) + DbStateBuilder::::new(storage_db, self.root.get()) .with_optional_recorder(self.proof_recorder.clone()) .with_cache(self.shared_trie_cache.local_cache()) .build(), @@ -341,17 +338,17 @@ fn state_err() -> String { "State is not open".into() } -impl StateBackend> for BenchmarkingState { - type Error = as StateBackend>>::Error; - type TrieBackendStorage = as StateBackend>>::TrieBackendStorage; - type RawIter = RawIter; +impl StateBackend for BenchmarkingState { + type Error = as StateBackend>::Error; + type TrieBackendStorage = as StateBackend>::TrieBackendStorage; + type RawIter = RawIter; fn storage(&self, key: &[u8]) -> Result>, Self::Error> { self.add_read_key(None, key); self.state.borrow().as_ref().ok_or_else(state_err)?.storage(key) } - fn storage_hash(&self, key: &[u8]) -> Result, Self::Error> { + fn storage_hash(&self, key: &[u8]) -> Result, Self::Error> { self.add_read_key(None, key); self.state.borrow().as_ref().ok_or_else(state_err)?.storage_hash(key) } @@ -373,7 +370,7 @@ impl StateBackend> for BenchmarkingState { &self, child_info: &ChildInfo, key: &[u8], - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { self.add_read_key(Some(child_info.storage_key()), key); self.state .borrow() @@ -385,7 +382,7 @@ impl StateBackend> for BenchmarkingState { fn closest_merkle_value( &self, key: &[u8], - ) -> Result>, Self::Error> { + ) -> Result>, Self::Error> { self.add_read_key(None, key); self.state.borrow().as_ref().ok_or_else(state_err)?.closest_merkle_value(key) } @@ -394,7 +391,7 @@ impl StateBackend> for BenchmarkingState { &self, child_info: &ChildInfo, key: &[u8], - ) -> Result>, Self::Error> { + ) -> Result>, Self::Error> { self.add_read_key(None, key); self.state .borrow() @@ -443,7 +440,7 @@ impl StateBackend> for BenchmarkingState { &self, delta: impl Iterator)>, state_version: StateVersion, - ) -> (B::Hash, BackendTransaction>) { + ) -> (Hasher::Output, BackendTransaction) { self.state .borrow() .as_ref() @@ -455,7 +452,7 @@ impl StateBackend> for BenchmarkingState { child_info: &ChildInfo, delta: impl Iterator)>, state_version: StateVersion, - ) -> (B::Hash, bool, BackendTransaction>) { + ) -> (Hasher::Output, bool, BackendTransaction) { self.state .borrow() .as_ref() @@ -479,8 +476,8 @@ impl StateBackend> for BenchmarkingState { fn commit( &self, - storage_root: as Hasher>::Out, - mut transaction: BackendTransaction>, + storage_root: ::Out, + mut transaction: BackendTransaction, main_storage_changes: StorageCollection, child_storage_changes: ChildStorageCollection, ) -> Result<(), Self::Error> { @@ -634,8 +631,7 @@ impl StateBackend> for BenchmarkingState { log::debug!(target: "benchmark", "Some proof size: {}", &proof_size); proof_size } else { - if let Some(size) = proof.encoded_compact_size::>(proof_recorder_root) - { + if let Some(size) = proof.encoded_compact_size::(proof_recorder_root) { size as u32 } else if proof_recorder_root == self.root.get() { log::debug!(target: "benchmark", "No changes - no proof"); @@ -654,7 +650,7 @@ impl StateBackend> for BenchmarkingState { } } -impl std::fmt::Debug for BenchmarkingState { +impl std::fmt::Debug for BenchmarkingState { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Bench DB") } @@ -663,6 +659,7 @@ impl std::fmt::Debug for BenchmarkingState { #[cfg(test)] mod test { use crate::bench::BenchmarkingState; + use sp_runtime::traits::HashingFor; use sp_state_machine::backend::Backend as _; fn hex(hex: &str) -> Vec { @@ -681,7 +678,8 @@ mod test { ..sp_runtime::Storage::default() }; let bench_state = - BenchmarkingState::::new(storage, None, false, true).unwrap(); + BenchmarkingState::>::new(storage, None, false, true) + .unwrap(); assert_eq!(bench_state.read_write_count(), (0, 0, 0, 0)); assert_eq!(bench_state.keys(Default::default()).unwrap().count(), 1); @@ -690,9 +688,13 @@ mod test { #[test] fn read_to_main_and_child_tries() { - let bench_state = - BenchmarkingState::::new(Default::default(), None, false, true) - .unwrap(); + let bench_state = BenchmarkingState::>::new( + Default::default(), + None, + false, + true, + ) + .unwrap(); for _ in 0..2 { let child1 = sp_core::storage::ChildInfo::new_default(b"child1"); diff --git a/substrate/client/db/src/lib.rs b/substrate/client/db/src/lib.rs index 2d8622d5f12..870b4150b9d 100644 --- a/substrate/client/db/src/lib.rs +++ b/substrate/client/db/src/lib.rs @@ -101,14 +101,11 @@ pub use bench::BenchmarkingState; const CACHE_HEADERS: usize = 8; /// DB-backed patricia trie state, transaction type is an overlay of changes to commit. -pub type DbState = - sp_state_machine::TrieBackend>>, HashingFor>; +pub type DbState = sp_state_machine::TrieBackend>, H>; /// Builder for [`DbState`]. -pub type DbStateBuilder = sp_state_machine::TrieBackendBuilder< - Arc>>, - HashingFor, ->; +pub type DbStateBuilder = + sp_state_machine::TrieBackendBuilder>, Hasher>; /// Length of a [`DbHash`]. const DB_HASH_LEN: usize = 32; @@ -135,13 +132,17 @@ enum DbExtrinsic { /// It makes sure that the hash we are using stays pinned in storage /// until this structure is dropped. pub struct RefTrackingState { - state: DbState, + state: DbState>, storage: Arc>, parent_hash: Option, } impl RefTrackingState { - fn new(state: DbState, storage: Arc>, parent_hash: Option) -> Self { + fn new( + state: DbState>, + storage: Arc>, + parent_hash: Option, + ) -> Self { RefTrackingState { state, parent_hash, storage } } } @@ -162,12 +163,12 @@ impl std::fmt::Debug for RefTrackingState { /// A raw iterator over the `RefTrackingState`. pub struct RawIter { - inner: as StateBackend>>::RawIter, + inner: > as StateBackend>>::RawIter, } impl StorageIterator> for RawIter { type Backend = RefTrackingState; - type Error = as StateBackend>>::Error; + type Error = > as StateBackend>>::Error; fn next_key(&mut self, backend: &Self::Backend) -> Option> { self.inner.next_key(&backend.state) @@ -186,8 +187,9 @@ impl StorageIterator> for RawIter { } impl StateBackend> for RefTrackingState { - type Error = as StateBackend>>::Error; - type TrieBackendStorage = as StateBackend>>::TrieBackendStorage; + type Error = > as StateBackend>>::Error; + type TrieBackendStorage = + > as StateBackend>>::TrieBackendStorage; type RawIter = RawIter; fn storage(&self, key: &[u8]) -> Result>, Self::Error> { @@ -284,7 +286,8 @@ impl StateBackend> for RefTrackingState { } impl AsTrieBackend> for RefTrackingState { - type TrieBackendStorage = as StateBackend>>::TrieBackendStorage; + type TrieBackendStorage = + > as StateBackend>>::TrieBackendStorage; fn as_trie_backend( &self, @@ -1936,7 +1939,7 @@ impl Backend { fn empty_state(&self) -> RecordStatsState, Block> { let root = EmptyStorage::::new().0; // Empty trie - let db_state = DbStateBuilder::::new(self.storage.clone(), root) + let db_state = DbStateBuilder::>::new(self.storage.clone(), root) .with_optional_cache(self.shared_trie_cache.as_ref().map(|c| c.local_cache())) .build(); let state = RefTrackingState::new(db_state, self.storage.clone(), None); @@ -2428,9 +2431,12 @@ impl sc_client_api::backend::Backend for Backend { if hash == self.blockchain.meta.read().genesis_hash { if let Some(genesis_state) = &*self.genesis_state.read() { let root = genesis_state.root; - let db_state = DbStateBuilder::::new(genesis_state.clone(), root) - .with_optional_cache(self.shared_trie_cache.as_ref().map(|c| c.local_cache())) - .build(); + let db_state = + DbStateBuilder::>::new(genesis_state.clone(), root) + .with_optional_cache( + self.shared_trie_cache.as_ref().map(|c| c.local_cache()), + ) + .build(); let state = RefTrackingState::new(db_state, self.storage.clone(), None); return Ok(RecordStatsState::new(state, None, self.state_usage.clone())) @@ -2449,11 +2455,12 @@ impl sc_client_api::backend::Backend for Backend { self.storage.state_db.pin(&hash, hdr.number.saturated_into::(), hint) { let root = hdr.state_root; - let db_state = DbStateBuilder::::new(self.storage.clone(), root) - .with_optional_cache( - self.shared_trie_cache.as_ref().map(|c| c.local_cache()), - ) - .build(); + let db_state = + DbStateBuilder::>::new(self.storage.clone(), root) + .with_optional_cache( + self.shared_trie_cache.as_ref().map(|c| c.local_cache()), + ) + .build(); let state = RefTrackingState::new(db_state, self.storage.clone(), Some(hash)); Ok(RecordStatsState::new(state, Some(hash), self.state_usage.clone())) } else { diff --git a/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs b/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs index 5c76ca68e85..dce49db15f7 100644 --- a/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs +++ b/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs @@ -37,7 +37,7 @@ use sp_core::{ }; use sp_externalities::Extensions; use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; -use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; +use sp_runtime::traits::Hash; use sp_state_machine::StateMachine; use std::{collections::HashMap, fmt::Debug, fs, str::FromStr, time}; @@ -140,11 +140,10 @@ This could mean that you either did not build the node correctly with the \ not created by a node that was compiled with the flag"; impl PalletCmd { - /// Runs the command and benchmarks the chain. - pub fn run(&self, config: Configuration) -> Result<()> + /// Runs the command and benchmarks a pallet. + pub fn run(&self, config: Configuration) -> Result<()> where - BB: BlockT + Debug, - <<::Header as HeaderT>::Number as std::str::FromStr>::Err: std::fmt::Debug, + Hasher: Hash, ExtraHostFunctions: sp_wasm_interface::HostFunctions, { let _d = self.execution.as_ref().map(|exec| { @@ -199,7 +198,7 @@ impl PalletCmd { let genesis_storage = spec.build_storage()?; let mut changes = Default::default(); let cache_size = Some(self.database_cache_size as usize); - let state_with_tracking = BenchmarkingState::::new( + let state_with_tracking = BenchmarkingState::::new( genesis_storage.clone(), cache_size, // Record proof size @@ -207,7 +206,7 @@ impl PalletCmd { // Enable storage tracking true, )?; - let state_without_tracking = BenchmarkingState::::new( + let state_without_tracking = BenchmarkingState::::new( genesis_storage, cache_size, // Do not record proof size diff --git a/substrate/utils/frame/benchmarking-cli/src/storage/write.rs b/substrate/utils/frame/benchmarking-cli/src/storage/write.rs index 65941497bda..4fa56b6e818 100644 --- a/substrate/utils/frame/benchmarking-cli/src/storage/write.rs +++ b/substrate/utils/frame/benchmarking-cli/src/storage/write.rs @@ -57,7 +57,7 @@ impl StorageCmd { let best_hash = client.usage_info().chain.best_hash; let header = client.header(best_hash)?.ok_or("Header not found")?; let original_root = *header.state_root(); - let trie = DbStateBuilder::::new(storage.clone(), original_root).build(); + let trie = DbStateBuilder::>::new(storage.clone(), original_root).build(); info!("Preparing keys from block {}", best_hash); // Load all KV pairs and randomly shuffle them. @@ -189,7 +189,7 @@ fn convert_tx( /// if `child_info` exist then it means this is a child tree key fn measure_write( db: Arc>, - trie: &DbState, + trie: &DbState>, key: Vec, new_v: Vec, version: StateVersion, @@ -220,7 +220,7 @@ fn measure_write( /// if `child_info` exist then it means this is a child tree key fn check_new_value( db: Arc>, - trie: &DbState, + trie: &DbState>, key: &Vec, new_v: &Vec, version: StateVersion, -- GitLab From aac07af03c3453e2f36990f0cccc3d68841482d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Thu, 8 Feb 2024 19:03:22 +0100 Subject: [PATCH 033/133] Fixes `TotalValueLocked` out of sync in nomination pools (#3052) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `TotalLockedValue` storage value in nomination pools pallet may get out of sync if the staking pallet does implicit withdrawal of unlocking chunks belonging to a bonded pool stash. This fix is based on a new method in the `OnStakingUpdate` traits, `on_withdraw`, which allows the nomination pools pallet to adjust the `TotalLockedValue` every time there is an implicit or explicit withdrawal from a bonded pool's stash. This PR also adds a migration that checks and updates the on-chain TVL if it got out of sync due to the bug this PR fixes. **Changes to `trait OnStakingUpdate`** In order for staking to notify the nomination pools pallet that chunks where withdrew, we add a new method, `on_withdraw` to the `OnStakingUpdate` trait. The nomination pools pallet filters the withdraws that are related to bonded pool accounts and updates the `TotalValueLocked` accordingly. **Others** - Adds try-state checks to the EPM/staking e2e tests - Adds tests for auto withdrawing in the context of nomination pools **To-do** - [x] check if we need a migration to fix the current `TotalValueLocked` (run try-runtime) - [x] migrations to fix the current on-chain TVL value ✅ **Kusama**: ``` TotalValueLocked: 99.4559 kKSM TotalValueLocked (calculated) 99.4559 kKSM ``` ⚠️ **Westend**: ``` TotalValueLocked: 18.4060 kWND TotalValueLocked (calculated) 18.4050 kWND ``` **Polkadot**: TVL not released yet. Closes https://github.com/paritytech/polkadot-sdk/issues/3055 --------- Co-authored-by: command-bot <> Co-authored-by: Ross Bulat Co-authored-by: Dónal Murray --- Cargo.lock | 1 + polkadot/runtime/westend/src/lib.rs | 2 +- prdoc/pr_3052.prdoc | 15 ++ .../test-staking-e2e/Cargo.toml | 16 ++ .../test-staking-e2e/src/lib.rs | 166 ++++++++++++++++-- .../test-staking-e2e/src/mock.rs | 106 +++++++++-- substrate/frame/nomination-pools/src/lib.rs | 42 ++--- .../frame/nomination-pools/src/migration.rs | 86 +++++++-- substrate/frame/nomination-pools/src/mock.rs | 13 ++ substrate/frame/staking/src/pallet/impls.rs | 5 +- substrate/frame/staking/src/pallet/mod.rs | 2 +- substrate/primitives/staking/src/lib.rs | 3 + 12 files changed, 383 insertions(+), 74 deletions(-) create mode 100644 prdoc/pr_3052.prdoc diff --git a/Cargo.lock b/Cargo.lock index b6fa96cd0a3..845f2217855 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9735,6 +9735,7 @@ dependencies = [ "pallet-bags-list", "pallet-balances", "pallet-election-provider-multi-phase", + "pallet-nomination-pools", "pallet-session", "pallet-staking", "pallet-timestamp", diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 7305684a0d9..8d903ebf62c 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -1647,7 +1647,7 @@ pub mod migrations { pallet_referenda::migration::v1::MigrateV0ToV1, pallet_grandpa::migrations::MigrateV4ToV5, parachains_configuration::migration::v10::MigrateToV10, - pallet_nomination_pools::migration::versioned::V7ToV8, + pallet_nomination_pools::migration::unversioned::TotalValueLockedSync, UpgradeSessionKeys, frame_support::migrations::RemovePallet< ImOnlinePalletName, diff --git a/prdoc/pr_3052.prdoc b/prdoc/pr_3052.prdoc new file mode 100644 index 00000000000..09f3cf59e4d --- /dev/null +++ b/prdoc/pr_3052.prdoc @@ -0,0 +1,15 @@ +title: "Fixes a scenario where a nomination pool's `TotalValueLocked` is out of sync due to staking's implicit withdraw" + +doc: + - audience: Runtime Dev + description: | + The nomination pools pallet `TotalValueLocked` may get out of sync if the staking pallet + does implicit withdrawal of unlocking chunks belonging to a bonded pool stash. This fix + is based on a new method on the `OnStakingUpdate` traits, `on_withdraw`, which allows the + nomination pools pallet to adjust the `TotalValueLocked` every time there is an implicit or + explicit withdrawal from a bonded pool's stash. + +crates: + - name: "pallet-nomination-pools" + - name: "pallet-staking" + - name: "sp-staking" diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml b/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml index 05c6a6d4046..e9bcc96455b 100644 --- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml +++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml @@ -35,7 +35,23 @@ frame-election-provider-support = { path = "../../election-provider-support" } pallet-election-provider-multi-phase = { path = ".." } pallet-staking = { path = "../../staking" } +pallet-nomination-pools = { path = "../../nomination-pools" } pallet-bags-list = { path = "../../bags-list" } pallet-balances = { path = "../../balances" } pallet-timestamp = { path = "../../timestamp" } pallet-session = { path = "../../session" } + +[features] +try-runtime = [ + "frame-election-provider-support/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-bags-list/try-runtime", + "pallet-balances/try-runtime", + "pallet-election-provider-multi-phase/try-runtime", + "pallet-nomination-pools/try-runtime", + "pallet-session/try-runtime", + "pallet-staking/try-runtime", + "pallet-timestamp/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs index 1d3f4712b1d..53bff50f748 100644 --- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs @@ -53,9 +53,9 @@ fn log_current_time() { #[test] fn block_progression_works() { - let (mut ext, pool_state, _) = ExtBuilder::default().build_offchainify(); + let (ext, pool_state, _) = ExtBuilder::default().build_offchainify(); - ext.execute_with(|| { + execute_with(ext, || { assert_eq!(active_era(), 0); assert_eq!(Session::current_index(), 0); assert!(ElectionProviderMultiPhase::current_phase().is_off()); @@ -70,9 +70,9 @@ fn block_progression_works() { assert!(ElectionProviderMultiPhase::current_phase().is_signed()); }); - let (mut ext, pool_state, _) = ExtBuilder::default().build_offchainify(); + let (ext, pool_state, _) = ExtBuilder::default().build_offchainify(); - ext.execute_with(|| { + execute_with(ext, || { assert_eq!(active_era(), 0); assert_eq!(Session::current_index(), 0); assert!(ElectionProviderMultiPhase::current_phase().is_off()); @@ -93,12 +93,12 @@ fn offchainify_works() { let staking_builder = StakingExtBuilder::default(); let epm_builder = EpmExtBuilder::default(); - let (mut ext, pool_state, _) = ExtBuilder::default() + let (ext, pool_state, _) = ExtBuilder::default() .epm(epm_builder) .staking(staking_builder) .build_offchainify(); - ext.execute_with(|| { + execute_with(ext, || { // test ocw progression and solution queue if submission when unsigned phase submission is // not delayed. for _ in 0..100 { @@ -142,9 +142,9 @@ fn offchainify_works() { /// restarts. Note that in this test case, the emergency throttling is disabled. fn enters_emergency_phase_after_forcing_before_elect() { let epm_builder = EpmExtBuilder::default().disable_emergency_throttling(); - let (mut ext, pool_state, _) = ExtBuilder::default().epm(epm_builder).build_offchainify(); + let (ext, pool_state, _) = ExtBuilder::default().epm(epm_builder).build_offchainify(); - ext.execute_with(|| { + execute_with(ext, || { log!( trace, "current validators (staking): {:?}", @@ -213,12 +213,12 @@ fn continous_slashes_below_offending_threshold() { let staking_builder = StakingExtBuilder::default().validator_count(10); let epm_builder = EpmExtBuilder::default().disable_emergency_throttling(); - let (mut ext, pool_state, _) = ExtBuilder::default() + let (ext, pool_state, _) = ExtBuilder::default() .epm(epm_builder) .staking(staking_builder) .build_offchainify(); - ext.execute_with(|| { + execute_with(ext, || { assert_eq!(Session::validators().len(), 10); let mut active_validator_set = Session::validators(); @@ -271,12 +271,12 @@ fn set_validation_intention_after_chilled() { use frame_election_provider_support::SortedListProvider; use pallet_staking::{Event, Forcing, Nominators}; - let (mut ext, pool_state, _) = ExtBuilder::default() + let (ext, pool_state, _) = ExtBuilder::default() .epm(EpmExtBuilder::default()) .staking(StakingExtBuilder::default()) .build_offchainify(); - ext.execute_with(|| { + execute_with(ext, || { assert_eq!(active_era(), 0); // validator is part of the validator set. assert!(Session::validators().contains(&41)); @@ -334,10 +334,10 @@ fn set_validation_intention_after_chilled() { fn ledger_consistency_active_balance_below_ed() { use pallet_staking::{Error, Event}; - let (mut ext, pool_state, _) = + let (ext, pool_state, _) = ExtBuilder::default().staking(StakingExtBuilder::default()).build_offchainify(); - ext.execute_with(|| { + execute_with(ext, || { assert_eq!(Staking::ledger(11.into()).unwrap().active, 1000); // unbonding total of active stake fails because the active ledger balance would fall @@ -387,3 +387,141 @@ fn ledger_consistency_active_balance_below_ed() { assert!(Staking::ledger(11.into()).is_err()); }); } + +#[test] +/// Automatic withdrawal of unlocking funds in staking propagates to the nomination pools and its +/// state correctly. +/// +/// The staking pallet may withdraw unlocking funds from a pool's bonded account without a pool +/// member or operator calling explicitly `Call::withdraw*`. This test verifies that the member's +/// are eventually paid and the `TotalValueLocked` is kept in sync in those cases. +fn automatic_unbonding_pools() { + use pallet_nomination_pools::TotalValueLocked; + + // closure to fetch the staking unlocking chunks of an account. + let unlocking_chunks_of = |account: AccountId| -> usize { + Staking::ledger(sp_staking::StakingAccount::Controller(account)) + .unwrap() + .unlocking + .len() + }; + + let (ext, pool_state, _) = ExtBuilder::default() + .pools(PoolsExtBuilder::default().max_unbonding(1)) + .staking(StakingExtBuilder::default().max_unlocking(1).bonding_duration(2)) + .build_offchainify(); + + execute_with(ext, || { + assert_eq!(::MaxUnlockingChunks::get(), 1); + assert_eq!(::BondingDuration::get(), 2); + assert_eq!(::MaxUnbonding::get(), 1); + + // init state of pool members. + let init_free_balance_2 = Balances::free_balance(2); + let init_free_balance_3 = Balances::free_balance(3); + + let pool_bonded_account = Pools::create_bonded_account(1); + + // creates a pool with 5 bonded, owned by 1. + assert_ok!(Pools::create(RuntimeOrigin::signed(1), 5, 1, 1, 1)); + assert_eq!(locked_amount_for(pool_bonded_account), 5); + + let init_tvl = TotalValueLocked::::get(); + + // 2 joins the pool. + assert_ok!(Pools::join(RuntimeOrigin::signed(2), 10, 1)); + assert_eq!(locked_amount_for(pool_bonded_account), 15); + + // 3 joins the pool. + assert_ok!(Pools::join(RuntimeOrigin::signed(3), 10, 1)); + assert_eq!(locked_amount_for(pool_bonded_account), 25); + + assert_eq!(TotalValueLocked::::get(), 25); + + // currently unlocking 0 chunks in the bonded pools ledger. + assert_eq!(unlocking_chunks_of(pool_bonded_account), 0); + + // unbond 2 from pool. + assert_ok!(Pools::unbond(RuntimeOrigin::signed(2), 2, 10)); + + // amount is still locked in the pool, needs to wait for unbonding period. + assert_eq!(locked_amount_for(pool_bonded_account), 25); + + // max chunks in the ledger are now filled up (`MaxUnlockingChunks == 1`). + assert_eq!(unlocking_chunks_of(pool_bonded_account), 1); + + // tries to unbond 3 from pool. it will fail since there are no unlocking chunks left + // available and the current in the queue haven't been there for more than bonding + // duration. + assert_err!( + Pools::unbond(RuntimeOrigin::signed(3), 3, 10), + pallet_staking::Error::::NoMoreChunks + ); + + assert_eq!(current_era(), 0); + + // progress over bonding duration. + for _ in 0..=::BondingDuration::get() { + start_next_active_era(pool_state.clone()).unwrap(); + } + assert_eq!(current_era(), 3); + System::reset_events(); + + let locked_before_withdraw_pool = locked_amount_for(pool_bonded_account); + assert_eq!(Balances::free_balance(pool_bonded_account), 26); + + // now unbonding 3 will work, although the pool's ledger still has the unlocking chunks + // filled up. + assert_ok!(Pools::unbond(RuntimeOrigin::signed(3), 3, 10)); + assert_eq!(unlocking_chunks_of(pool_bonded_account), 1); + + assert_eq!( + staking_events(), + [ + // auto-withdraw happened as expected to release 2's unbonding funds, but the funds + // were not transfered to 2 and stay in the pool's tranferrable balance instead. + pallet_staking::Event::Withdrawn { stash: 7939698191839293293, amount: 10 }, + pallet_staking::Event::Unbonded { stash: 7939698191839293293, amount: 10 } + ] + ); + + // balance of the pool remains the same, it hasn't withdraw explicitly from the pool yet. + assert_eq!(Balances::free_balance(pool_bonded_account), 26); + // but the locked amount in the pool's account decreases due to the auto-withdraw: + assert_eq!(locked_before_withdraw_pool - 10, locked_amount_for(pool_bonded_account)); + + // TVL correctly updated. + assert_eq!(TotalValueLocked::::get(), 25 - 10); + + // however, note that the withdrawing from the pool still works for 2, the funds are taken + // from the pool's free balance. + assert_eq!(Balances::free_balance(pool_bonded_account), 26); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(2), 2, 10)); + assert_eq!(Balances::free_balance(pool_bonded_account), 16); + + assert_eq!(Balances::free_balance(2), 20); + assert_eq!(TotalValueLocked::::get(), 15); + + // 3 cannot withdraw yet. + assert_err!( + Pools::withdraw_unbonded(RuntimeOrigin::signed(3), 3, 10), + pallet_nomination_pools::Error::::CannotWithdrawAny + ); + + // progress over bonding duration. + for _ in 0..=::BondingDuration::get() { + start_next_active_era(pool_state.clone()).unwrap(); + } + assert_eq!(current_era(), 6); + System::reset_events(); + + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(3), 3, 10)); + + // final conditions are the expected. + assert_eq!(Balances::free_balance(pool_bonded_account), 6); // 5 init bonded + ED + assert_eq!(Balances::free_balance(2), init_free_balance_2); + assert_eq!(Balances::free_balance(3), init_free_balance_3); + + assert_eq!(TotalValueLocked::::get(), init_tvl); + }); +} diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs index 32633a0ed7d..882b894bb22 100644 --- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs +++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs @@ -69,6 +69,7 @@ frame_support::construct_runtime!( System: frame_system, ElectionProviderMultiPhase: pallet_election_provider_multi_phase, Staking: pallet_staking, + Pools: pallet_nomination_pools, Balances: pallet_balances, BagsList: pallet_bags_list, Session: pallet_session, @@ -114,7 +115,7 @@ impl pallet_balances::Config for Runtime { type MaxFreezes = traits::ConstU32<1>; type RuntimeHoldReason = RuntimeHoldReason; type RuntimeFreezeReason = RuntimeFreezeReason; - type FreezeIdentifier = (); + type FreezeIdentifier = RuntimeFreezeReason; type WeightInfo = (); } @@ -233,7 +234,7 @@ const THRESHOLDS: [VoteWeight; 9] = [10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_00 parameter_types! { pub static BagThresholds: &'static [sp_npos_elections::VoteWeight] = &THRESHOLDS; pub const SessionsPerEra: sp_staking::SessionIndex = 2; - pub const BondingDuration: sp_staking::EraIndex = 28; + pub static BondingDuration: sp_staking::EraIndex = 28; pub const SlashDeferDuration: sp_staking::EraIndex = 7; // 1/4 the bonding duration. pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(40); pub HistoryDepth: u32 = 84; @@ -247,6 +248,45 @@ impl pallet_bags_list::Config for Runtime { type Score = VoteWeight; } +pub struct BalanceToU256; +impl sp_runtime::traits::Convert for BalanceToU256 { + fn convert(n: Balance) -> sp_core::U256 { + n.into() + } +} + +pub struct U256ToBalance; +impl sp_runtime::traits::Convert for U256ToBalance { + fn convert(n: sp_core::U256) -> Balance { + n.try_into().unwrap() + } +} + +parameter_types! { + pub const PoolsPalletId: frame_support::PalletId = frame_support::PalletId(*b"py/nopls"); + pub static MaxUnbonding: u32 = 8; +} + +impl pallet_nomination_pools::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type Currency = Balances; + type RuntimeFreezeReason = RuntimeFreezeReason; + type RewardCounter = sp_runtime::FixedU128; + type BalanceToU256 = BalanceToU256; + type U256ToBalance = U256ToBalance; + type Staking = Staking; + type PostUnbondingPoolsWindow = ConstU32<2>; + type PalletId = PoolsPalletId; + type MaxMetadataLen = ConstU32<256>; + type MaxUnbonding = MaxUnbonding; + type MaxPointsToBalance = frame_support::traits::ConstU8<10>; +} + +parameter_types! { + pub static MaxUnlockingChunks: u32 = 32; +} + /// Upper limit on the number of NPOS nominations. const MAX_QUOTA_NOMINATIONS: u32 = 16; @@ -273,10 +313,10 @@ impl pallet_staking::Config for Runtime { type VoterList = BagsList; type NominationsQuota = pallet_staking::FixedNominationsQuota; type TargetList = pallet_staking::UseValidatorsMap; - type MaxUnlockingChunks = ConstU32<32>; + type MaxUnlockingChunks = MaxUnlockingChunks; type MaxControllersInDeprecationBatch = ConstU32<100>; type HistoryDepth = HistoryDepth; - type EventListeners = (); + type EventListeners = Pools; type WeightInfo = pallet_staking::weights::SubstrateWeight; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; } @@ -394,6 +434,14 @@ impl StakingExtBuilder { self.validator_count = n; self } + pub fn max_unlocking(self, max: u32) -> Self { + ::set(max); + self + } + pub fn bonding_duration(self, eras: EraIndex) -> Self { + ::set(eras); + self + } } pub struct EpmExtBuilder {} @@ -417,6 +465,21 @@ impl EpmExtBuilder { } } +pub struct PoolsExtBuilder {} + +impl Default for PoolsExtBuilder { + fn default() -> Self { + PoolsExtBuilder {} + } +} + +impl PoolsExtBuilder { + pub fn max_unbonding(self, max: u32) -> Self { + ::set(max); + self + } +} + pub struct BalancesExtBuilder { balances: Vec<(AccountId, Balance)>, } @@ -464,6 +527,7 @@ pub struct ExtBuilder { staking_builder: StakingExtBuilder, epm_builder: EpmExtBuilder, balances_builder: BalancesExtBuilder, + pools_builder: PoolsExtBuilder, } impl Default for ExtBuilder { @@ -472,6 +536,7 @@ impl Default for ExtBuilder { staking_builder: StakingExtBuilder::default(), epm_builder: EpmExtBuilder::default(), balances_builder: BalancesExtBuilder::default(), + pools_builder: PoolsExtBuilder::default(), } } } @@ -548,6 +613,11 @@ impl ExtBuilder { self } + pub fn pools(mut self, builder: PoolsExtBuilder) -> Self { + self.pools_builder = builder; + self + } + pub fn balances(mut self, builder: BalancesExtBuilder) -> Self { self.balances_builder = builder; self @@ -567,20 +637,20 @@ impl ExtBuilder { (ext, pool_state, offchain_state) } +} - pub fn build_and_execute(self, test: impl FnOnce() -> ()) { - let mut ext = self.build(); - ext.execute_with(test); +pub(crate) fn execute_with(mut ext: sp_io::TestExternalities, test: impl FnOnce() -> ()) { + ext.execute_with(test); - #[cfg(feature = "try-runtime")] - ext.execute_with(|| { - let bn = System::block_number(); + #[cfg(feature = "try-runtime")] + ext.execute_with(|| { + let bn = System::block_number(); - assert_ok!(>::try_state(bn)); - assert_ok!(>::try_state(bn)); - assert_ok!(>::try_state(bn)); - }); - } + assert_ok!(>::try_state(bn)); + assert_ok!(>::try_state(bn)); + assert_ok!(>::try_state(bn)); + assert_ok!(>::try_state(bn)); + }); } // Progress to given block, triggering session and era changes as we progress and ensuring that @@ -606,6 +676,7 @@ pub fn roll_to(n: BlockNumber, delay_solution: bool) { if b != n { Staking::on_finalize(System::block_number()); } + Pools::on_initialize(b); log_current_time(); } @@ -860,6 +931,11 @@ pub(crate) fn set_minimum_election_score( .map_err(|_| ()) } +pub(crate) fn locked_amount_for(account_id: AccountId) -> Balance { + let lock = pallet_balances::Locks::::get(account_id); + lock[0].amount +} + pub(crate) fn staking_events() -> Vec> { System::events() .into_iter() diff --git a/substrate/frame/nomination-pools/src/lib.rs b/substrate/frame/nomination-pools/src/lib.rs index c2653d8bdab..074d59931ad 100644 --- a/substrate/frame/nomination-pools/src/lib.rs +++ b/substrate/frame/nomination-pools/src/lib.rs @@ -1293,27 +1293,6 @@ impl BondedPool { }); }; } - - /// Withdraw all the funds that are already unlocked from staking for the - /// [`BondedPool::bonded_account`]. - /// - /// Also reduces the [`TotalValueLocked`] by the difference of the - /// [`T::Staking::total_stake`] of the [`BondedPool::bonded_account`] that might occur by - /// [`T::Staking::withdraw_unbonded`]. - /// - /// Returns the result of [`T::Staking::withdraw_unbonded`] - fn withdraw_from_staking(&self, num_slashing_spans: u32) -> Result { - let bonded_account = self.bonded_account(); - - let prev_total = T::Staking::total_stake(&bonded_account.clone()).unwrap_or_default(); - let outcome = T::Staking::withdraw_unbonded(bonded_account.clone(), num_slashing_spans); - let diff = prev_total - .defensive_saturating_sub(T::Staking::total_stake(&bonded_account).unwrap_or_default()); - TotalValueLocked::::mutate(|tvl| { - tvl.saturating_reduce(diff); - }); - outcome - } } /// A reward pool. @@ -1733,7 +1712,7 @@ pub mod pallet { CountedStorageMap<_, Twox64Concat, PoolId, BondedPoolInner>; /// Reward pools. This is where there rewards for each pool accumulate. When a members payout is - /// claimed, the balance comes out fo the reward pool. Keyed by the bonded pools account. + /// claimed, the balance comes out of the reward pool. Keyed by the bonded pools account. #[pallet::storage] pub type RewardPools = CountedStorageMap<_, Twox64Concat, PoolId, RewardPool>; @@ -1753,8 +1732,8 @@ pub mod pallet { /// A reverse lookup from the pool's account id to its id. /// - /// This is only used for slashing. In all other instances, the pool id is used, and the - /// accounts are deterministically derived from it. + /// This is only used for slashing and on automatic withdraw update. In all other instances, the + /// pool id is used, and the accounts are deterministically derived from it. #[pallet::storage] pub type ReversePoolIdLookup = CountedStorageMap<_, Twox64Concat, T::AccountId, PoolId, OptionQuery>; @@ -2223,7 +2202,7 @@ pub mod pallet { // For now we only allow a pool to withdraw unbonded if its not destroying. If the pool // is destroying then `withdraw_unbonded` can be used. ensure!(pool.state != PoolState::Destroying, Error::::NotDestroying); - pool.withdraw_from_staking(num_slashing_spans)?; + T::Staking::withdraw_unbonded(pool.bonded_account(), num_slashing_spans)?; Ok(()) } @@ -2275,7 +2254,8 @@ pub mod pallet { // Before calculating the `balance_to_unbond`, we call withdraw unbonded to ensure the // `transferrable_balance` is correct. - let stash_killed = bonded_pool.withdraw_from_staking(num_slashing_spans)?; + let stash_killed = + T::Staking::withdraw_unbonded(bonded_pool.bonded_account(), num_slashing_spans)?; // defensive-only: the depositor puts enough funds into the stash so that it will only // be destroyed when they are leaving. @@ -3628,4 +3608,14 @@ impl sp_staking::OnStakingUpdate> for Pall } Self::deposit_event(Event::::PoolSlashed { pool_id, balance: slashed_bonded }); } + + /// Reduces the overall `TotalValueLocked` if a withdrawal happened for a pool involved in the + /// staking withdraw. + fn on_withdraw(pool_account: &T::AccountId, amount: BalanceOf) { + if ReversePoolIdLookup::::get(pool_account).is_some() { + TotalValueLocked::::mutate(|tvl| { + tvl.saturating_reduce(amount); + }); + } + } } diff --git a/substrate/frame/nomination-pools/src/migration.rs b/substrate/frame/nomination-pools/src/migration.rs index 559baf76e4c..6887fcfa7ec 100644 --- a/substrate/frame/nomination-pools/src/migration.rs +++ b/substrate/frame/nomination-pools/src/migration.rs @@ -56,6 +56,59 @@ pub mod versioned { >; } +pub mod unversioned { + use super::*; + + /// Checks and updates `TotalValueLocked` if out of sync. + pub struct TotalValueLockedSync(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for TotalValueLockedSync { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + Ok(Vec::new()) + } + + fn on_runtime_upgrade() -> Weight { + let migrated = BondedPools::::count(); + + // recalcuate the `TotalValueLocked` to compare with the current on-chain TVL which may + // be out of sync. + let tvl: BalanceOf = helpers::calculate_tvl_by_total_stake::(); + let onchain_tvl = TotalValueLocked::::get(); + + let writes = if tvl != onchain_tvl { + TotalValueLocked::::set(tvl); + + log!( + info, + "on-chain TVL was out of sync, update. Old: {:?}, new: {:?}", + onchain_tvl, + tvl + ); + + // writes: onchain version + set total value locked. + 2 + } else { + log!(info, "on-chain TVL was OK: {:?}", tvl); + + // writes: onchain version write. + 1 + }; + + // reads: migrated * (BondedPools + Staking::total_stake) + count + onchain + // version + // + // writes: current version + (maybe) TVL + T::DbWeight::get() + .reads_writes(migrated.saturating_mul(2).saturating_add(2).into(), writes) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_: Vec) -> Result<(), TryRuntimeError> { + Ok(()) + } + } +} + pub mod v8 { use super::{v7::V7BondedPoolInner, *}; @@ -146,6 +199,7 @@ pub(crate) mod v7 { } impl V7BondedPool { + #[allow(dead_code)] fn bonded_account(&self) -> T::AccountId { Pallet::::create_bonded_account(self.id) } @@ -157,26 +211,12 @@ pub(crate) mod v7 { CountedStorageMap, Twox64Concat, PoolId, V7BondedPoolInner>; pub struct VersionUncheckedMigrateV6ToV7(sp_std::marker::PhantomData); - impl VersionUncheckedMigrateV6ToV7 { - fn calculate_tvl_by_total_stake() -> BalanceOf { - BondedPools::::iter() - .map(|(id, inner)| { - T::Staking::total_stake( - &V7BondedPool { id, inner: inner.clone() }.bonded_account(), - ) - .unwrap_or_default() - }) - .reduce(|acc, total_balance| acc + total_balance) - .unwrap_or_default() - } - } - impl OnRuntimeUpgrade for VersionUncheckedMigrateV6ToV7 { fn on_runtime_upgrade() -> Weight { let migrated = BondedPools::::count(); // The TVL should be the sum of all the funds that are actively staked and in the // unbonding process of the account of each pool. - let tvl: BalanceOf = Self::calculate_tvl_by_total_stake(); + let tvl: BalanceOf = helpers::calculate_tvl_by_total_stake::(); TotalValueLocked::::set(tvl); @@ -198,7 +238,7 @@ pub(crate) mod v7 { fn post_upgrade(_data: Vec) -> Result<(), TryRuntimeError> { // check that the `TotalValueLocked` written is actually the sum of `total_stake` of the // `BondedPools`` - let tvl: BalanceOf = Self::calculate_tvl_by_total_stake(); + let tvl: BalanceOf = helpers::calculate_tvl_by_total_stake::(); ensure!( TotalValueLocked::::get() == tvl, "TVL written is not equal to `Staking::total_stake` of all `BondedPools`." @@ -977,3 +1017,17 @@ pub mod v1 { } } } + +mod helpers { + use super::*; + + pub(crate) fn calculate_tvl_by_total_stake() -> BalanceOf { + BondedPools::::iter() + .map(|(id, inner)| { + T::Staking::total_stake(&BondedPool { id, inner: inner.clone() }.bonded_account()) + .unwrap_or_default() + }) + .reduce(|acc, total_balance| acc + total_balance) + .unwrap_or_default() + } +} diff --git a/substrate/frame/nomination-pools/src/mock.rs b/substrate/frame/nomination-pools/src/mock.rs index 84c41a15b20..f982b72c635 100644 --- a/substrate/frame/nomination-pools/src/mock.rs +++ b/substrate/frame/nomination-pools/src/mock.rs @@ -134,11 +134,24 @@ impl sp_staking::StakingInterface for StakingMock { fn withdraw_unbonded(who: Self::AccountId, _: u32) -> Result { let mut unbonding_map = UnbondingBalanceMap::get(); + + // closure to calculate the current unlocking funds across all eras/accounts. + let unlocking = |pair: &Vec<(EraIndex, Balance)>| -> Balance { + pair.iter() + .try_fold(Zero::zero(), |acc: Balance, (_at, amount)| acc.checked_add(*amount)) + .unwrap() + }; + let staker_map = unbonding_map.get_mut(&who).ok_or("Nothing to unbond")?; + let unlocking_before = unlocking(&staker_map); let current_era = Self::current_era(); + staker_map.retain(|(unlocking_at, _amount)| *unlocking_at > current_era); + // if there was a withdrawal, notify the pallet. + Pools::on_withdraw(&who, unlocking_before.saturating_sub(unlocking(&staker_map))); + UnbondingBalanceMap::set(&unbonding_map); Ok(UnbondingBalanceMap::get().is_empty() && BondedBalanceMap::get().is_empty()) } diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 8b45430c688..165972a0560 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -41,7 +41,7 @@ use sp_runtime::{ use sp_staking::{ currency_to_vote::CurrencyToVote, offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, - EraIndex, Page, SessionIndex, Stake, + EraIndex, OnStakingUpdate, Page, SessionIndex, Stake, StakingAccount::{self, Controller, Stash}, StakingInterface, }; @@ -150,6 +150,9 @@ impl Pallet { // Already checked that this won't overflow by entry condition. let value = old_total.defensive_saturating_sub(new_total); Self::deposit_event(Event::::Withdrawn { stash, amount: value }); + + // notify listeners. + T::EventListeners::on_withdraw(controller, value); } Ok(used_weight) diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index 0689418d02b..65e981058e6 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -275,7 +275,7 @@ pub mod pallet { /// Something that listens to staking updates and performs actions based on the data it /// receives. /// - /// WARNING: this only reports slashing events for the time being. + /// WARNING: this only reports slashing and withdraw events for the time being. type EventListeners: sp_staking::OnStakingUpdate>; /// Some parameters of the benchmarking. diff --git a/substrate/primitives/staking/src/lib.rs b/substrate/primitives/staking/src/lib.rs index c2ac5ae004b..f5b4a1ed63f 100644 --- a/substrate/primitives/staking/src/lib.rs +++ b/substrate/primitives/staking/src/lib.rs @@ -150,6 +150,9 @@ pub trait OnStakingUpdate { _slashed_total: Balance, ) { } + + /// Fired when a portion of a staker's balance has been withdrawn. + fn on_withdraw(_stash: &AccountId, _amount: Balance) {} } /// A generic representation of a staking implementation. -- GitLab From e53ebd8cd48a42f053bb8c30bbe84c90065ce3c8 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 8 Feb 2024 19:47:04 +0100 Subject: [PATCH 034/133] [FRAME] Parameters pallet (#2061) Closes #169 Fork of the `orml-parameters-pallet` as introduced by https://github.com/open-web3-stack/open-runtime-module-library/pull/927 (cc @xlc) It greatly changes how the macros work, but keeps the pallet the same. The downside of my code is now that it does only support constant keys in the form of types, not value-bearing keys. I think this is an acceptable trade off, give that it can be used by *any* pallet without any changes. The pallet allows to dynamically set parameters that can be used in pallet configs while also restricting the updating on a per-key basis. The rust-docs contains a complete example. Changes: - Add `parameters-pallet` - Use in the kitchensink as demonstration - Add experimental attribute to define dynamic params in the runtime. - Adding a bunch of traits to `frame_support::traits::dynamic_params` that can be re-used by the ORML macros ## Example First to define the parameters in the runtime file. The syntax is very explicit about the codec index and errors if there is no. ```rust #[dynamic_params(RuntimeParameters, pallet_parameters::Parameters::))] pub mod dynamic_params { use super::*; #[dynamic_pallet_params] #[codec(index = 0)] pub mod storage { /// Configures the base deposit of storing some data. #[codec(index = 0)] pub static BaseDeposit: Balance = 1 * DOLLARS; /// Configures the per-byte deposit of storing some data. #[codec(index = 1)] pub static ByteDeposit: Balance = 1 * CENTS; } #[dynamic_pallet_params] #[codec(index = 1)] pub mod contracts { #[codec(index = 0)] pub static DepositPerItem: Balance = deposit(1, 0); #[codec(index = 1)] pub static DepositPerByte: Balance = deposit(0, 1); } } ``` Then the pallet is configured with the aggregate: ```rust impl pallet_parameters::Config for Runtime { type AggregratedKeyValue = RuntimeParameters; type AdminOrigin = EnsureRootWithSuccess>; ... } ``` And then the parameters can be used in a pallet config: ```rust impl pallet_preimage::Config for Runtime { type DepositBase = dynamic_params::storage::DepositBase; } ``` A custom origin an be defined like this: ```rust pub struct DynamicParametersManagerOrigin; impl EnsureOriginWithArg for DynamicParametersManagerOrigin { type Success = (); fn try_origin( origin: RuntimeOrigin, key: &RuntimeParametersKey, ) -> Result { match key { RuntimeParametersKey::Storage(_) => { frame_system::ensure_root(origin.clone()).map_err(|_| origin)?; return Ok(()) }, RuntimeParametersKey::Contract(_) => { frame_system::ensure_root(origin.clone()).map_err(|_| origin)?; return Ok(()) }, } } #[cfg(feature = "runtime-benchmarks")] fn try_successful_origin(_key: &RuntimeParametersKey) -> Result { Ok(RuntimeOrigin::Root) } } ``` --------- Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Nikhil Gupta <17176722+gupnik@users.noreply.github.com> Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: command-bot <> --- Cargo.lock | 21 + Cargo.toml | 1 + prdoc/pr_2061.prdoc | 14 + substrate/bin/node/runtime/Cargo.toml | 4 + substrate/bin/node/runtime/src/lib.rs | 102 +++- .../frame/examples/kitchensink/Cargo.toml | 2 +- substrate/frame/parameters/Cargo.toml | 57 ++ .../frame/parameters/src/benchmarking.rs | 51 ++ substrate/frame/parameters/src/lib.rs | 270 +++++++++ substrate/frame/parameters/src/tests/mock.rs | 152 +++++ substrate/frame/parameters/src/tests/mod.rs | 20 + .../parameters/src/tests/test_renamed.rs | 168 ++++++ substrate/frame/parameters/src/tests/unit.rs | 311 ++++++++++ substrate/frame/parameters/src/weights.rs | 76 +++ substrate/frame/support/procedural/Cargo.toml | 2 +- .../support/procedural/src/dynamic_params.rs | 563 ++++++++++++++++++ substrate/frame/support/procedural/src/lib.rs | 54 +- substrate/frame/support/src/lib.rs | 10 +- substrate/frame/support/src/traits.rs | 2 + .../support/src/traits/dynamic_params.rs | 153 +++++ .../inject_runtime_type_invalid.stderr | 2 +- 21 files changed, 2017 insertions(+), 18 deletions(-) create mode 100644 prdoc/pr_2061.prdoc create mode 100644 substrate/frame/parameters/Cargo.toml create mode 100644 substrate/frame/parameters/src/benchmarking.rs create mode 100644 substrate/frame/parameters/src/lib.rs create mode 100644 substrate/frame/parameters/src/tests/mock.rs create mode 100644 substrate/frame/parameters/src/tests/mod.rs create mode 100644 substrate/frame/parameters/src/tests/test_renamed.rs create mode 100644 substrate/frame/parameters/src/tests/unit.rs create mode 100644 substrate/frame/parameters/src/weights.rs create mode 100644 substrate/frame/support/procedural/src/dynamic_params.rs create mode 100644 substrate/frame/support/src/traits/dynamic_params.rs diff --git a/Cargo.lock b/Cargo.lock index 845f2217855..f5b272d1175 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7020,6 +7020,7 @@ dependencies = [ "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", + "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", @@ -10430,6 +10431,26 @@ dependencies = [ "sp-runtime", ] +[[package]] +name = "pallet-parameters" +version = "0.0.1" +dependencies = [ + "docify", + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-balances", + "pallet-example-basic", + "parity-scale-codec", + "paste", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std 14.0.0", +] + [[package]] name = "pallet-preimage" version = "28.0.0" diff --git a/Cargo.toml b/Cargo.toml index 8cbfc7a23fe..0a70bb03756 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -366,6 +366,7 @@ members = [ "substrate/frame/offences/benchmarking", "substrate/frame/paged-list", "substrate/frame/paged-list/fuzzer", + "substrate/frame/parameters", "substrate/frame/preimage", "substrate/frame/proxy", "substrate/frame/ranked-collective", diff --git a/prdoc/pr_2061.prdoc b/prdoc/pr_2061.prdoc new file mode 100644 index 00000000000..07df11ca054 --- /dev/null +++ b/prdoc/pr_2061.prdoc @@ -0,0 +1,14 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Add Parameters Pallet + +doc: + - audience: Runtime Dev + description: | + Adds `pallet-parameters` that allows to have parameters for pallet configs that dynamically change at runtime. Allows to be permissioned on a per-key basis and is compatible with ORML macros. + +crates: + - name: "pallet-parameters" + - name: "frame-support" + - name: "frame-support-procedural" diff --git a/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml index 4bb5fed2b09..76e32f02f0a 100644 --- a/substrate/bin/node/runtime/Cargo.toml +++ b/substrate/bin/node/runtime/Cargo.toml @@ -142,6 +142,7 @@ pallet-vesting = { path = "../../../frame/vesting", default-features = false } pallet-whitelist = { path = "../../../frame/whitelist", default-features = false } pallet-tx-pause = { path = "../../../frame/tx-pause", default-features = false } pallet-safe-mode = { path = "../../../frame/safe-mode", default-features = false } +pallet-parameters = { path = "../../../frame/parameters", default-features = false } [build-dependencies] substrate-wasm-builder = { path = "../../../utils/wasm-builder", optional = true } @@ -209,6 +210,7 @@ std = [ "pallet-nomination-pools/std", "pallet-offences-benchmarking?/std", "pallet-offences/std", + "pallet-parameters/std", "pallet-preimage/std", "pallet-proxy/std", "pallet-ranked-collective/std", @@ -310,6 +312,7 @@ runtime-benchmarks = [ "pallet-nomination-pools/runtime-benchmarks", "pallet-offences-benchmarking/runtime-benchmarks", "pallet-offences/runtime-benchmarks", + "pallet-parameters/runtime-benchmarks", "pallet-preimage/runtime-benchmarks", "pallet-proxy/runtime-benchmarks", "pallet-ranked-collective/runtime-benchmarks", @@ -386,6 +389,7 @@ try-runtime = [ "pallet-nis/try-runtime", "pallet-nomination-pools/try-runtime", "pallet-offences/try-runtime", + "pallet-parameters/try-runtime", "pallet-preimage/try-runtime", "pallet-proxy/try-runtime", "pallet-ranked-collective/try-runtime", diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 515272cd99a..02d5bb55429 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -30,6 +30,7 @@ use frame_election_provider_support::{ use frame_support::{ construct_runtime, derive_impl, dispatch::DispatchClass, + dynamic_params::{dynamic_pallet_params, dynamic_params}, genesis_builder_helper::{build_config, create_default_config}, instances::{Instance1, Instance2}, ord_parameter_types, @@ -44,9 +45,9 @@ use frame_support::{ GetSalary, PayFromAccount, }, AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, Contains, Currency, - EitherOfDiverse, EqualPrivilegeOnly, Imbalance, InsideBoth, InstanceFilter, - KeyOwnerProofSystem, LinearStoragePrice, LockIdentifier, Nothing, OnUnbalanced, - WithdrawReasons, + EitherOfDiverse, EnsureOriginWithArg, EqualPrivilegeOnly, Imbalance, InsideBoth, + InstanceFilter, KeyOwnerProofSystem, LinearStoragePrice, LockIdentifier, Nothing, + OnUnbalanced, WithdrawReasons, }, weights::{ constants::{ @@ -457,9 +458,6 @@ impl pallet_glutton::Config for Runtime { } parameter_types! { - pub const PreimageBaseDeposit: Balance = 1 * DOLLARS; - // One cent: $10,000 / MB - pub const PreimageByteDeposit: Balance = 1 * CENTS; pub const PreimageHoldReason: RuntimeHoldReason = RuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage); } @@ -472,7 +470,11 @@ impl pallet_preimage::Config for Runtime { AccountId, Balances, PreimageHoldReason, - LinearStoragePrice, + LinearStoragePrice< + dynamic_params::storage::BaseDeposit, + dynamic_params::storage::ByteDeposit, + Balance, + >, >; } @@ -1326,9 +1328,6 @@ impl pallet_tips::Config for Runtime { } parameter_types! { - pub const DepositPerItem: Balance = deposit(1, 0); - pub const DepositPerByte: Balance = deposit(0, 1); - pub const DefaultDepositLimit: Balance = deposit(1024, 1024 * 1024); pub Schedule: pallet_contracts::Schedule = Default::default(); pub CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(30); } @@ -1346,9 +1345,9 @@ impl pallet_contracts::Config for Runtime { /// change because that would break already deployed contracts. The `Call` structure itself /// is not allowed to change the indices of existing pallets, too. type CallFilter = Nothing; - type DepositPerItem = DepositPerItem; - type DepositPerByte = DepositPerByte; - type DefaultDepositLimit = DefaultDepositLimit; + type DepositPerItem = dynamic_params::contracts::DepositPerItem; + type DepositPerByte = dynamic_params::contracts::DepositPerByte; + type DefaultDepositLimit = dynamic_params::contracts::DefaultDepositLimit; type CallStack = [pallet_contracts::Frame; 5]; type WeightPrice = pallet_transaction_payment::Pallet; type WeightInfo = pallet_contracts::weights::SubstrateWeight; @@ -2088,6 +2087,81 @@ impl pallet_mixnet::Config for Runtime { type MinMixnodes = ConstU32<7>; // Low to allow small testing networks } +/// Dynamic parameters that can be changed at runtime through the +/// `pallet_parameters::set_parameter`. +#[dynamic_params(RuntimeParameters, pallet_parameters::Parameters::)] +pub mod dynamic_params { + use super::*; + + #[dynamic_pallet_params] + #[codec(index = 0)] + pub mod storage { + /// Configures the base deposit of storing some data. + #[codec(index = 0)] + pub static BaseDeposit: Balance = 1 * DOLLARS; + + /// Configures the per-byte deposit of storing some data. + #[codec(index = 1)] + pub static ByteDeposit: Balance = 1 * CENTS; + } + + #[dynamic_pallet_params] + #[codec(index = 1)] + pub mod contracts { + #[codec(index = 0)] + pub static DepositPerItem: Balance = deposit(1, 0); + + #[codec(index = 1)] + pub static DepositPerByte: Balance = deposit(0, 1); + + #[codec(index = 2)] + pub static DefaultDepositLimit: Balance = deposit(1024, 1024 * 1024); + } +} + +#[cfg(feature = "runtime-benchmarks")] +impl Default for RuntimeParameters { + fn default() -> Self { + RuntimeParameters::Storage(dynamic_params::storage::Parameters::BaseDeposit( + dynamic_params::storage::BaseDeposit, + Some(1 * DOLLARS), + )) + } +} + +pub struct DynamicParametersManagerOrigin; +impl EnsureOriginWithArg for DynamicParametersManagerOrigin { + type Success = (); + + fn try_origin( + origin: RuntimeOrigin, + key: &RuntimeParametersKey, + ) -> Result { + match key { + RuntimeParametersKey::Storage(_) => { + frame_system::ensure_root(origin.clone()).map_err(|_| origin)?; + return Ok(()) + }, + RuntimeParametersKey::Contract(_) => { + frame_system::ensure_root(origin.clone()).map_err(|_| origin)?; + return Ok(()) + }, + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(_key: &RuntimeParametersKey) -> Result { + Ok(RuntimeOrigin::root()) + } +} + +impl pallet_parameters::Config for Runtime { + type RuntimeParameters = RuntimeParameters; + type RuntimeEvent = RuntimeEvent; + type AdminOrigin = DynamicParametersManagerOrigin; + type WeightInfo = (); +} + construct_runtime!( pub enum Runtime { System: frame_system, @@ -2169,6 +2243,7 @@ construct_runtime!( Broker: pallet_broker, TasksExample: pallet_example_tasks, Mixnet: pallet_mixnet, + Parameters: pallet_parameters, SkipFeelessPayment: pallet_skip_feeless_payment, } ); @@ -2288,6 +2363,7 @@ mod benches { [pallet_elections_phragmen, Elections] [pallet_fast_unstake, FastUnstake] [pallet_nis, Nis] + [pallet_parameters, Parameters] [pallet_grandpa, Grandpa] [pallet_identity, Identity] [pallet_im_online, ImOnline] diff --git a/substrate/frame/examples/kitchensink/Cargo.toml b/substrate/frame/examples/kitchensink/Cargo.toml index 4255ebb66b6..f866024b8e9 100644 --- a/substrate/frame/examples/kitchensink/Cargo.toml +++ b/substrate/frame/examples/kitchensink/Cargo.toml @@ -20,7 +20,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = log = { version = "0.4.17", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -frame-support = { path = "../../support", default-features = false } +frame-support = { path = "../../support", default-features = false, features = ["experimental"] } frame-system = { path = "../../system", default-features = false } sp-io = { path = "../../../primitives/io", default-features = false } diff --git a/substrate/frame/parameters/Cargo.toml b/substrate/frame/parameters/Cargo.toml new file mode 100644 index 00000000000..c95e23b1a02 --- /dev/null +++ b/substrate/frame/parameters/Cargo.toml @@ -0,0 +1,57 @@ +[package] +name = "pallet-parameters" +description = "Pallet to store and configure parameters." +repository.workspace = true +license = "Apache-2.0" +version = "0.0.1" +authors = ["Acala Developers", "Parity Technologies "] +edition.workspace = true + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["max-encoded-len"] } +scale-info = { version = "2.1.2", default-features = false, features = ["derive"] } +paste = { version = "1.0.14", default-features = false } +serde = { version = "1.0.188", features = ["derive"], optional = true } +docify = "0.2.5" + +frame-support = { path = "../support", default-features = false, features = ["experimental"] } +frame-system = { path = "../system", default-features = false } +sp-core = { path = "../../primitives/core", default-features = false } +sp-runtime = { path = "../../primitives/runtime", default-features = false } +sp-std = { path = "../../primitives/std", default-features = false } +frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } + +[dev-dependencies] +sp-core = { path = "../../primitives/core", features = ["std"] } +sp-io = { path = "../../primitives/io", features = ["std"] } +pallet-example-basic = { path = "../examples/basic", features = ["std"] } +pallet-balances = { path = "../balances", features = ["std"] } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "serde", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-example-basic/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-balances/try-runtime", + "pallet-example-basic/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/substrate/frame/parameters/src/benchmarking.rs b/substrate/frame/parameters/src/benchmarking.rs new file mode 100644 index 00000000000..1f22e026cbc --- /dev/null +++ b/substrate/frame/parameters/src/benchmarking.rs @@ -0,0 +1,51 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Parameters pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +#[cfg(test)] +use crate::Pallet as Parameters; + +use frame_benchmarking::v2::*; + +#[benchmarks(where T::RuntimeParameters: Default)] +mod benchmarks { + use super::*; + + #[benchmark] + fn set_parameter() -> Result<(), BenchmarkError> { + let kv = T::RuntimeParameters::default(); + let k = kv.clone().into_parts().0; + + let origin = + T::AdminOrigin::try_successful_origin(&k).map_err(|_| BenchmarkError::Weightless)?; + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, kv); + + Ok(()) + } + + impl_benchmark_test_suite! { + Parameters, + crate::tests::mock::new_test_ext(), + crate::tests::mock::Runtime, + } +} diff --git a/substrate/frame/parameters/src/lib.rs b/substrate/frame/parameters/src/lib.rs new file mode 100644 index 00000000000..91cf10ba93f --- /dev/null +++ b/substrate/frame/parameters/src/lib.rs @@ -0,0 +1,270 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg_attr(not(feature = "std"), no_std)] +#![deny(missing_docs)] +// Need to enable this one since we document feature-gated stuff. +#![allow(rustdoc::broken_intra_doc_links)] + +//! # **⚠️ WARNING ⚠️** +//! +//!
+//! THIS CRATE IS NOT AUDITED AND SHOULD NOT BE USED IN PRODUCTION. +//!
+//! +//! # Parameters +//! +//! Allows to update configuration parameters at runtime. +//! +//! ## Pallet API +//! +//! This pallet exposes two APIs; one *inbound* side to update parameters, and one *outbound* side +//! to access said parameters. Parameters themselves are defined in the runtime config and will be +//! aggregated into an enum. Each parameter is addressed by a `key` and can have a default value. +//! This is not done by the pallet but through the [`frame_support::dynamic_params::dynamic_params`] +//! macro or alternatives. +//! +//! Note that this is incurring one storage read per access. This should not be a problem in most +//! cases but must be considered in weight-restrained code. +//! +//! ### Inbound +//! +//! The inbound side solely consists of the [`Pallet::set_parameter`] extrinsic to update the value +//! of a parameter. Each parameter can have their own admin origin as given by the +//! [`Config::AdminOrigin`]. +//! +//! ### Outbound +//! +//! The outbound side is runtime facing for the most part. More general, it provides a `Get` +//! implementation and can be used in every spot where that is accepted. Two macros are in place: +//! [`frame_support::dynamic_params::define_parameters` and +//! [`frame_support::dynamic_params:dynamic_pallet_params`] to define and expose parameters in a +//! typed manner. +//! +//! See the [`pallet`] module for more information about the interfaces this pallet exposes, +//! including its configuration trait, dispatchables, storage items, events and errors. +//! +//! ## Overview +//! +//! This pallet is a good fit for updating parameters without a runtime upgrade. It is very handy to +//! not require a runtime upgrade for a simple parameter change since runtime upgrades require a lot +//! of diligence and always bear risks. It seems overkill to update the whole runtime for a simple +//! parameter change. This pallet allows for fine-grained control over who can update what. +//! The only down-side is that it trades off performance with convenience and should therefore only +//! be used in places where that is proven to be uncritical. Values that are rarely accessed but +//! change often would be a perfect fit. +//! +//! ### Example Configuration +//! +//! Here is an example of how to define some parameters, including their default values: +#![doc = docify::embed!("src/tests/mock.rs", dynamic_params)] +//! +//! A permissioned origin can be define on a per-key basis like this: +#![doc = docify::embed!("src/tests/mock.rs", custom_origin)] +//! +//! The pallet will also require a default value for benchmarking. Ideally this is the variant with +//! the longest encoded length. Although in either case the PoV benchmarking will take the worst +//! case over the whole enum. +#![doc = docify::embed!("src/tests/mock.rs", benchmarking_default)] +//! +//! Now the aggregated parameter needs to be injected into the pallet config: +#![doc = docify::embed!("src/tests/mock.rs", impl_config)] +//! +//! As last step, the parameters can now be used in other pallets 🙌 +#![doc = docify::embed!("src/tests/mock.rs", usage)] +//! +//! ### Examples Usage +//! +//! Now to demonstrate how the values can be updated: +#![doc = docify::embed!("src/tests/unit.rs", set_parameters_example)] +//! +//! ## Low Level / Implementation Details +//! +//! The pallet stores the parameters in a storage map and implements the matching `Get` for +//! each `Key` type. The `Get` then accesses the `Parameters` map to retrieve the value. An event is +//! emitted every time that a value was updated. It is even emitted when the value is changed to the +//! same. +//! +//! The key and value types themselves are defined by macros and aggregated into a runtime wide +//! enum. This enum is then injected into the pallet. This allows it to be used without any changes +//! to the pallet that the parameter will be utilized by. +//! +//! ### Design Goals +//! +//! 1. Easy to update without runtime upgrade. +//! 2. Exposes metadata and docs for user convenience. +//! 3. Can be permissioned on a per-key base. +//! +//! ### Design +//! +//! 1. Everything is done at runtime without the need for `const` values. `Get` allows for this - +//! which is coincidentally an upside and a downside. 2. The types are defined through macros, which +//! allows to expose metadata and docs. 3. Access control is done through the `EnsureOriginWithArg` +//! trait, that allows to pass data along to the origin check. It gets passed in the key. The +//! implementor can then match on the key and the origin to decide whether the origin is +//! permissioned to set the value. + +use frame_support::pallet_prelude::*; +use frame_system::pallet_prelude::*; + +use frame_support::traits::{ + dynamic_params::{AggregratedKeyValue, IntoKey, Key, RuntimeParameterStore, TryIntoKey}, + EnsureOriginWithArg, +}; + +mod benchmarking; +#[cfg(test)] +mod tests; +mod weights; + +pub use pallet::*; +pub use weights::WeightInfo; + +/// The key type of a parameter. +type KeyOf = <::RuntimeParameters as AggregratedKeyValue>::Key; + +/// The value type of a parameter. +type ValueOf = <::RuntimeParameters as AggregratedKeyValue>::Value; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::config(with_default)] + pub trait Config: frame_system::Config { + /// The overarching event type. + #[pallet::no_default_bounds] + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The overarching KV type of the parameters. + /// + /// Usually created by [`frame_support::dynamic_params`] or equivalent. + #[pallet::no_default_bounds] + type RuntimeParameters: AggregratedKeyValue; + + /// The origin which may update a parameter. + /// + /// The key of the parameter is passed in as second argument to allow for fine grained + /// control. + #[pallet::no_default_bounds] + type AdminOrigin: EnsureOriginWithArg>; + + /// Weight information for extrinsics in this module. + type WeightInfo: WeightInfo; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// A Parameter was set. + /// + /// Is also emitted when the value was not changed. + Updated { + /// The key that was updated. + key: ::Key, + /// The old value before this call. + old_value: Option<::Value>, + /// The new value after this call. + new_value: Option<::Value>, + }, + } + + /// Stored parameters. + #[pallet::storage] + pub type Parameters = + StorageMap<_, Blake2_128Concat, KeyOf, ValueOf, OptionQuery>; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::call] + impl Pallet { + /// Set the value of a parameter. + /// + /// The dispatch origin of this call must be `AdminOrigin` for the given `key`. Values be + /// deleted by setting them to `None`. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::set_parameter())] + pub fn set_parameter( + origin: OriginFor, + key_value: T::RuntimeParameters, + ) -> DispatchResult { + let (key, new) = key_value.into_parts(); + T::AdminOrigin::ensure_origin(origin, &key)?; + + let mut old = None; + Parameters::::mutate(&key, |v| { + old = v.clone(); + *v = new.clone(); + }); + + Self::deposit_event(Event::Updated { key, old_value: old, new_value: new }); + + Ok(()) + } + } + /// Default implementations of [`DefaultConfig`], which can be used to implement [`Config`]. + pub mod config_preludes { + use super::*; + use frame_support::derive_impl; + + /// A configuration for testing. + pub struct TestDefaultConfig; + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig, no_aggregated_types)] + impl frame_system::DefaultConfig for TestDefaultConfig {} + + #[frame_support::register_default_impl(TestDefaultConfig)] + impl DefaultConfig for TestDefaultConfig { + #[inject_runtime_type] + type RuntimeEvent = (); + #[inject_runtime_type] + type RuntimeParameters = (); + + type AdminOrigin = frame_support::traits::AsEnsureOriginWithArg< + frame_system::EnsureRoot, + >; + + type WeightInfo = (); + } + } +} + +impl RuntimeParameterStore for Pallet { + type AggregratedKeyValue = T::RuntimeParameters; + + fn get(key: K) -> Option + where + KV: AggregratedKeyValue, + K: Key + Into<::Key>, + ::Key: IntoKey< + <::AggregratedKeyValue as AggregratedKeyValue>::Key, + >, + <::AggregratedKeyValue as AggregratedKeyValue>::Value: + TryIntoKey<::Value>, + ::Value: TryInto, + { + let key: ::Key = key.into(); + let val = Parameters::::get(key.into_key()); + val.and_then(|v| { + let val: ::Value = v.try_into_key().ok()?; + let val: K::WrappedValue = val.try_into().ok()?; + let val = val.into(); + Some(val) + }) + } +} diff --git a/substrate/frame/parameters/src/tests/mock.rs b/substrate/frame/parameters/src/tests/mock.rs new file mode 100644 index 00000000000..98612dc6a6d --- /dev/null +++ b/substrate/frame/parameters/src/tests/mock.rs @@ -0,0 +1,152 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg(any(test, feature = "runtime-benchmarks"))] + +//! Mock runtime that configures the `pallet_example_basic` to use dynamic params for testing. + +use frame_support::{ + construct_runtime, derive_impl, + dynamic_params::{dynamic_pallet_params, dynamic_params}, + traits::EnsureOriginWithArg, +}; + +use crate as pallet_parameters; +use crate::*; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type Block = frame_system::mocking::MockBlock; + type AccountData = pallet_balances::AccountData<::Balance>; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)] +impl pallet_balances::Config for Runtime { + type ReserveIdentifier = [u8; 8]; + type AccountStore = System; +} + +#[docify::export] +#[dynamic_params(RuntimeParameters, pallet_parameters::Parameters::)] +pub mod dynamic_params { + use super::*; + + #[dynamic_pallet_params] + #[codec(index = 3)] + pub mod pallet1 { + #[codec(index = 0)] + pub static Key1: u64 = 0; + #[codec(index = 1)] + pub static Key2: u32 = 1; + #[codec(index = 2)] + pub static Key3: u128 = 2; + } + + #[dynamic_pallet_params] + #[codec(index = 1)] + pub mod pallet2 { + #[codec(index = 2)] + pub static Key1: u64 = 0; + #[codec(index = 1)] + pub static Key2: u32 = 2; + #[codec(index = 0)] + pub static Key3: u128 = 4; + } +} + +#[docify::export(benchmarking_default)] +#[cfg(feature = "runtime-benchmarks")] +impl Default for RuntimeParameters { + fn default() -> Self { + RuntimeParameters::Pallet1(dynamic_params::pallet1::Parameters::Key1( + dynamic_params::pallet1::Key1, + Some(123), + )) + } +} + +#[docify::export] +mod custom_origin { + use super::*; + pub struct ParamsManager; + + impl EnsureOriginWithArg for ParamsManager { + type Success = (); + + fn try_origin( + origin: RuntimeOrigin, + key: &RuntimeParametersKey, + ) -> Result { + // Account 123 is allowed to set parameters in benchmarking only: + #[cfg(feature = "runtime-benchmarks")] + if ensure_signed(origin.clone()).map_or(false, |acc| acc == 123) { + return Ok(()); + } + + match key { + RuntimeParametersKey::Pallet1(_) => ensure_root(origin.clone()), + RuntimeParametersKey::Pallet2(_) => ensure_signed(origin.clone()).map(|_| ()), + } + .map_err(|_| origin) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(_key: &RuntimeParametersKey) -> Result { + Ok(RuntimeOrigin::signed(123)) + } + } +} + +#[docify::export(impl_config)] +#[derive_impl(pallet_parameters::config_preludes::TestDefaultConfig as pallet_parameters::DefaultConfig)] +impl Config for Runtime { + type AdminOrigin = custom_origin::ParamsManager; + // RuntimeParameters is injected by the `derive_impl` macro. + // RuntimeEvent is injected by the `derive_impl` macro. + // WeightInfo is injected by the `derive_impl` macro. +} + +#[docify::export(usage)] +impl pallet_example_basic::Config for Runtime { + // Use the dynamic key in the pallet config: + type MagicNumber = dynamic_params::pallet1::Key1; + + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); +} + +construct_runtime!( + pub enum Runtime { + System: frame_system, + PalletParameters: crate, + Example: pallet_example_basic, + Balances: pallet_balances, + } +); + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut ext = sp_io::TestExternalities::new(Default::default()); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +pub(crate) fn assert_last_event(generic_event: RuntimeEvent) { + let events = frame_system::Pallet::::events(); + // compare to the last event record + let frame_system::EventRecord { event, .. } = &events.last().expect("Event expected"); + assert_eq!(event, &generic_event); +} diff --git a/substrate/frame/parameters/src/tests/mod.rs b/substrate/frame/parameters/src/tests/mod.rs new file mode 100644 index 00000000000..0a2d16906aa --- /dev/null +++ b/substrate/frame/parameters/src/tests/mod.rs @@ -0,0 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub(crate) mod mock; +mod test_renamed; +mod unit; diff --git a/substrate/frame/parameters/src/tests/test_renamed.rs b/substrate/frame/parameters/src/tests/test_renamed.rs new file mode 100644 index 00000000000..b2e0c1fd966 --- /dev/null +++ b/substrate/frame/parameters/src/tests/test_renamed.rs @@ -0,0 +1,168 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg(any(test, feature = "runtime-benchmarks"))] + +//! Tests that the runtime params can be renamed. + +use frame_support::{ + assert_noop, assert_ok, construct_runtime, derive_impl, + dynamic_params::{dynamic_pallet_params, dynamic_params}, + traits::AsEnsureOriginWithArg, +}; +use frame_system::EnsureRoot; + +use crate as pallet_parameters; +use crate::*; +use dynamic_params::*; +use RuntimeParametersRenamed::*; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type Block = frame_system::mocking::MockBlock; + type AccountData = pallet_balances::AccountData<::Balance>; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)] +impl pallet_balances::Config for Runtime { + type ReserveIdentifier = [u8; 8]; + type AccountStore = System; +} + +#[dynamic_params(RuntimeParametersRenamed, pallet_parameters::Parameters::)] +pub mod dynamic_params { + use super::*; + + #[dynamic_pallet_params] + #[codec(index = 3)] + pub mod pallet1 { + #[codec(index = 0)] + pub static Key1: u64 = 0; + #[codec(index = 1)] + pub static Key2: u32 = 1; + #[codec(index = 2)] + pub static Key3: u128 = 2; + } + + #[dynamic_pallet_params] + #[codec(index = 1)] + pub mod pallet2 { + #[codec(index = 2)] + pub static Key1: u64 = 0; + #[codec(index = 1)] + pub static Key2: u32 = 2; + #[codec(index = 0)] + pub static Key3: u128 = 4; + } +} + +#[cfg(feature = "runtime-benchmarks")] +impl Default for RuntimeParametersRenamed { + fn default() -> Self { + RuntimeParametersRenamed::Pallet1(dynamic_params::pallet1::Parameters::Key1( + dynamic_params::pallet1::Key1, + Some(123), + )) + } +} + +#[derive_impl(pallet_parameters::config_preludes::TestDefaultConfig as pallet_parameters::DefaultConfig)] +impl Config for Runtime { + type AdminOrigin = AsEnsureOriginWithArg>; + type RuntimeParameters = RuntimeParametersRenamed; + // RuntimeEvent is injected by the `derive_impl` macro. + // WeightInfo is injected by the `derive_impl` macro. +} + +impl pallet_example_basic::Config for Runtime { + // Use the dynamic key in the pallet config: + type MagicNumber = dynamic_params::pallet1::Key1; + + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); +} + +construct_runtime!( + pub enum Runtime { + System: frame_system, + PalletParameters: crate, + Example: pallet_example_basic, + Balances: pallet_balances, + } +); + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut ext = sp_io::TestExternalities::new(Default::default()); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +pub(crate) fn assert_last_event(generic_event: RuntimeEvent) { + let events = frame_system::Pallet::::events(); + // compare to the last event record + let frame_system::EventRecord { event, .. } = &events.last().expect("Event expected"); + assert_eq!(event, &generic_event); +} + +#[test] +fn set_parameters_example() { + new_test_ext().execute_with(|| { + assert_eq!(pallet1::Key3::get(), 2, "Default works"); + + // This gets rejected since the origin is not root. + assert_noop!( + PalletParameters::set_parameter( + RuntimeOrigin::signed(1), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + ), + DispatchError::BadOrigin + ); + + assert_ok!(PalletParameters::set_parameter( + RuntimeOrigin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + )); + + assert_eq!(pallet1::Key3::get(), 123, "Update works"); + assert_last_event( + crate::Event::Updated { + key: RuntimeParametersRenamedKey::Pallet1(pallet1::ParametersKey::Key3( + pallet1::Key3, + )), + old_value: None, + new_value: Some(RuntimeParametersRenamedValue::Pallet1( + pallet1::ParametersValue::Key3(123), + )), + } + .into(), + ); + }); +} + +#[test] +fn get_through_external_pallet_works() { + new_test_ext().execute_with(|| { + assert_eq!(::MagicNumber::get(), 0); + + assert_ok!(PalletParameters::set_parameter( + RuntimeOrigin::root(), + Pallet1(pallet1::Parameters::Key1(pallet1::Key1, Some(123))), + )); + + assert_eq!(::MagicNumber::get(), 123); + }); +} diff --git a/substrate/frame/parameters/src/tests/unit.rs b/substrate/frame/parameters/src/tests/unit.rs new file mode 100644 index 00000000000..d3f11ba9640 --- /dev/null +++ b/substrate/frame/parameters/src/tests/unit.rs @@ -0,0 +1,311 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Unit tests for the parameters pallet. + +#![cfg(test)] + +use crate::tests::mock::{ + assert_last_event, dynamic_params::*, new_test_ext, PalletParameters, Runtime, + RuntimeOrigin as Origin, RuntimeParameters, RuntimeParameters::*, RuntimeParametersKey, + RuntimeParametersValue, +}; +use codec::Encode; +use frame_support::{assert_noop, assert_ok, traits::dynamic_params::AggregratedKeyValue}; +use sp_core::Get; +use sp_runtime::DispatchError; + +#[docify::export] +#[test] +fn set_parameters_example() { + new_test_ext().execute_with(|| { + assert_eq!(pallet1::Key3::get(), 2, "Default works"); + + // This gets rejected since the origin is not root. + assert_noop!( + PalletParameters::set_parameter( + Origin::signed(1), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + ), + DispatchError::BadOrigin + ); + + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + )); + + assert_eq!(pallet1::Key3::get(), 123, "Update works"); + assert_last_event( + crate::Event::Updated { + key: RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key3(pallet1::Key3)), + old_value: None, + new_value: Some(RuntimeParametersValue::Pallet1(pallet1::ParametersValue::Key3( + 123, + ))), + } + .into(), + ); + }); +} + +#[test] +fn set_parameters_same_is_noop() { + new_test_ext().execute_with(|| { + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + )); + + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + )); + + assert_eq!(pallet1::Key3::get(), 123, "Update works"); + }); +} + +#[test] +fn set_parameters_twice_works() { + new_test_ext().execute_with(|| { + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + )); + + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(432))), + )); + + assert_eq!(pallet1::Key3::get(), 432, "Update works"); + }); +} + +#[test] +fn set_parameters_removing_restores_default_works() { + new_test_ext().execute_with(|| { + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + )); + + assert_eq!(pallet1::Key3::get(), 123, "Update works"); + assert!( + crate::Parameters::::contains_key(RuntimeParametersKey::Pallet1( + pallet1::ParametersKey::Key3(pallet1::Key3) + )), + "Key inserted" + ); + + // Removing the value restores the default. + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, None)), + )); + + assert_eq!(pallet1::Key3::get(), 2, "Default restored"); + assert!( + !crate::Parameters::::contains_key(RuntimeParametersKey::Pallet1( + pallet1::ParametersKey::Key3(pallet1::Key3) + )), + "Key removed" + ); + }); +} + +#[test] +fn set_parameters_to_default_emits_events_works() { + new_test_ext().execute_with(|| { + assert_eq!(pallet1::Key3::get(), 2); + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(2))), + )); + assert_eq!(pallet1::Key3::get(), 2); + + assert!( + crate::Parameters::::contains_key(RuntimeParametersKey::Pallet1( + pallet1::ParametersKey::Key3(pallet1::Key3) + )), + "Key inserted" + ); + assert_last_event( + crate::Event::Updated { + key: RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key3(pallet1::Key3)), + old_value: None, + new_value: Some(RuntimeParametersValue::Pallet1(pallet1::ParametersValue::Key3(2))), + } + .into(), + ); + + // It will also emit a second event: + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(2))), + )); + assert_eq!(frame_system::Pallet::::events().len(), 2); + }); +} + +#[test] +fn set_parameters_wrong_origin_errors() { + new_test_ext().execute_with(|| { + // Pallet1 is root origin only: + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + )); + + assert_noop!( + PalletParameters::set_parameter( + Origin::signed(1), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(432))), + ), + DispatchError::BadOrigin + ); + + // Pallet2 is signed origin only: + assert_ok!(PalletParameters::set_parameter( + Origin::signed(1), + Pallet2(pallet2::Parameters::Key3(pallet2::Key3, Some(123))), + )); + + assert_noop!( + PalletParameters::set_parameter( + Origin::root(), + Pallet2(pallet2::Parameters::Key3(pallet2::Key3, Some(432))), + ), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn get_through_external_pallet_works() { + new_test_ext().execute_with(|| { + assert_eq!(::MagicNumber::get(), 0); + + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key1(pallet1::Key1, Some(123))), + )); + + assert_eq!(::MagicNumber::get(), 123); + }); +} + +#[test] +fn test_define_parameters_key_convert() { + let key1 = pallet1::Key1; + let parameter_key: pallet1::ParametersKey = key1.clone().into(); + let key1_2: pallet1::Key1 = parameter_key.clone().try_into().unwrap(); + + assert_eq!(key1, key1_2); + assert_eq!(parameter_key, pallet1::ParametersKey::Key1(key1)); + + let key2 = pallet1::Key2; + let parameter_key: pallet1::ParametersKey = key2.clone().into(); + let key2_2: pallet1::Key2 = parameter_key.clone().try_into().unwrap(); + + assert_eq!(key2, key2_2); + assert_eq!(parameter_key, pallet1::ParametersKey::Key2(key2)); +} + +#[test] +fn test_define_parameters_value_convert() { + let value1 = pallet1::Key1Value(1); + let parameter_value: pallet1::ParametersValue = value1.clone().into(); + let value1_2: pallet1::Key1Value = parameter_value.clone().try_into().unwrap(); + + assert_eq!(value1, value1_2); + assert_eq!(parameter_value, pallet1::ParametersValue::Key1(1)); + + let value2 = pallet1::Key2Value(2); + let parameter_value: pallet1::ParametersValue = value2.clone().into(); + let value2_2: pallet1::Key2Value = parameter_value.clone().try_into().unwrap(); + + assert_eq!(value2, value2_2); + assert_eq!(parameter_value, pallet1::ParametersValue::Key2(2)); +} + +#[test] +fn test_define_parameters_aggregrated_key_value() { + let kv1 = pallet1::Parameters::Key1(pallet1::Key1, None); + let (key1, value1) = kv1.clone().into_parts(); + + assert_eq!(key1, pallet1::ParametersKey::Key1(pallet1::Key1)); + assert_eq!(value1, None); + + let kv2 = pallet1::Parameters::Key2(pallet1::Key2, Some(2)); + let (key2, value2) = kv2.clone().into_parts(); + + assert_eq!(key2, pallet1::ParametersKey::Key2(pallet1::Key2)); + assert_eq!(value2, Some(pallet1::ParametersValue::Key2(2))); +} + +#[test] +fn test_define_aggregrated_parameters_key_convert() { + use codec::Encode; + + let key1 = pallet1::Key1; + let parameter_key: pallet1::ParametersKey = key1.clone().into(); + let runtime_key: RuntimeParametersKey = parameter_key.clone().into(); + + assert_eq!(runtime_key, RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key1(key1))); + assert_eq!(runtime_key.encode(), vec![3, 0]); + + let key2 = pallet2::Key2; + let parameter_key: pallet2::ParametersKey = key2.clone().into(); + let runtime_key: RuntimeParametersKey = parameter_key.clone().into(); + + assert_eq!(runtime_key, RuntimeParametersKey::Pallet2(pallet2::ParametersKey::Key2(key2))); + assert_eq!(runtime_key.encode(), vec![1, 1]); +} + +#[test] +fn test_define_aggregrated_parameters_aggregrated_key_value() { + let kv1 = RuntimeParameters::Pallet1(pallet1::Parameters::Key1(pallet1::Key1, None)); + let (key1, value1) = kv1.clone().into_parts(); + + assert_eq!(key1, RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key1(pallet1::Key1))); + assert_eq!(value1, None); + + let kv2 = RuntimeParameters::Pallet2(pallet2::Parameters::Key2(pallet2::Key2, Some(2))); + let (key2, value2) = kv2.clone().into_parts(); + + assert_eq!(key2, RuntimeParametersKey::Pallet2(pallet2::ParametersKey::Key2(pallet2::Key2))); + assert_eq!(value2, Some(RuntimeParametersValue::Pallet2(pallet2::ParametersValue::Key2(2)))); +} + +#[test] +fn codec_index_works() { + let enc = RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key1(pallet1::Key1)).encode(); + assert_eq!(enc, vec![3, 0]); + let enc = RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key2(pallet1::Key2)).encode(); + assert_eq!(enc, vec![3, 1]); + let enc = RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key3(pallet1::Key3)).encode(); + assert_eq!(enc, vec![3, 2]); + + let enc = RuntimeParametersKey::Pallet2(pallet2::ParametersKey::Key1(pallet2::Key1)).encode(); + assert_eq!(enc, vec![1, 2]); + let enc = RuntimeParametersKey::Pallet2(pallet2::ParametersKey::Key2(pallet2::Key2)).encode(); + assert_eq!(enc, vec![1, 1]); + let enc = RuntimeParametersKey::Pallet2(pallet2::ParametersKey::Key3(pallet2::Key3)).encode(); + assert_eq!(enc, vec![1, 0]); +} diff --git a/substrate/frame/parameters/src/weights.rs b/substrate/frame/parameters/src/weights.rs new file mode 100644 index 00000000000..6746960b1b7 --- /dev/null +++ b/substrate/frame/parameters/src/weights.rs @@ -0,0 +1,76 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for `pallet_parameters` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-05, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` + +// Executed Command: +// target/production/substrate-node +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_parameters +// --chain=dev +// --header=./substrate/HEADER-APACHE2 +// --output=./substrate/frame/parameters/src/weights.rs +// --template=./substrate/.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `pallet_parameters`. +pub trait WeightInfo { + fn set_parameter() -> Weight; +} + +/// Weights for `pallet_parameters` using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + fn set_parameter() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 0_000 picoseconds. + Weight::from_parts(0, 0) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + fn set_parameter() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 0_000 picoseconds. + Weight::from_parts(0, 0) + } +} diff --git a/substrate/frame/support/procedural/Cargo.toml b/substrate/frame/support/procedural/Cargo.toml index d77f6595db3..5b7fbb3fcaa 100644 --- a/substrate/frame/support/procedural/Cargo.toml +++ b/substrate/frame/support/procedural/Cargo.toml @@ -24,7 +24,7 @@ cfg-expr = "0.15.5" itertools = "0.10.3" proc-macro2 = "1.0.56" quote = "1.0.28" -syn = { version = "2.0.48", features = ["full"] } +syn = { version = "2.0.48", features = ["full", "visit-mut"] } frame-support-procedural-tools = { path = "tools" } macro_magic = { version = "0.5.0", features = ["proc_support"] } proc-macro-warning = { version = "1.0.0", default-features = false } diff --git a/substrate/frame/support/procedural/src/dynamic_params.rs b/substrate/frame/support/procedural/src/dynamic_params.rs new file mode 100644 index 00000000000..b718ccbc955 --- /dev/null +++ b/substrate/frame/support/procedural/src/dynamic_params.rs @@ -0,0 +1,563 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Code for the `#[dynamic_params]`, `#[dynamic_pallet_params]` and +//! `#[dynamic_aggregated_params_internal]` macros. + +use frame_support_procedural_tools::generate_access_from_frame_or_crate; +use inflector::Inflector; +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote, ToTokens}; +use syn::{parse2, spanned::Spanned, visit_mut, visit_mut::VisitMut, Result, Token}; + +/// Parse and expand a `#[dynamic_params(..)]` module. +pub fn dynamic_params(attr: TokenStream, item: TokenStream) -> Result { + DynamicParamModAttr::parse(attr, item).map(ToTokens::into_token_stream) +} + +/// Parse and expand `#[dynamic_pallet_params(..)]` attribute. +pub fn dynamic_pallet_params(attr: TokenStream, item: TokenStream) -> Result { + DynamicPalletParamAttr::parse(attr, item).map(ToTokens::into_token_stream) +} + +/// Parse and expand `#[dynamic_aggregated_params_internal]` attribute. +pub fn dynamic_aggregated_params_internal( + _attr: TokenStream, + item: TokenStream, +) -> Result { + parse2::(item).map(ToTokens::into_token_stream) +} + +/// A top `#[dynamic_params(..)]` attribute together with a mod. +#[derive(derive_syn_parse::Parse)] +pub struct DynamicParamModAttr { + params_mod: syn::ItemMod, + meta: DynamicParamModAttrMeta, +} + +/// The inner meta of a `#[dynamic_params(..)]` attribute. +#[derive(derive_syn_parse::Parse)] +pub struct DynamicParamModAttrMeta { + name: syn::Ident, + _comma: Option, + #[parse_if(_comma.is_some())] + params_pallet: Option, +} + +impl DynamicParamModAttr { + pub fn parse(attr: TokenStream, item: TokenStream) -> Result { + let params_mod = parse2(item)?; + let meta = parse2(attr)?; + Ok(Self { params_mod, meta }) + } + + pub fn inner_mods(&self) -> Vec { + self.params_mod.content.as_ref().map_or(Vec::new(), |(_, items)| { + items + .iter() + .filter_map(|i| match i { + syn::Item::Mod(m) => Some(m), + _ => None, + }) + .cloned() + .collect() + }) + } +} + +impl ToTokens for DynamicParamModAttr { + fn to_tokens(&self, tokens: &mut TokenStream) { + let scrate = match crate_access() { + Ok(path) => path, + Err(err) => return tokens.extend(err), + }; + let (mut params_mod, name) = (self.params_mod.clone(), &self.meta.name); + let dynam_params_ident = ¶ms_mod.ident; + + let mut quoted_enum = quote! {}; + for m in self.inner_mods() { + let aggregate_name = + syn::Ident::new(&m.ident.to_string().to_class_case(), m.ident.span()); + let mod_name = &m.ident; + + let mut attrs = m.attrs.clone(); + attrs.retain(|attr| !attr.path().is_ident("dynamic_pallet_params")); + if let Err(err) = ensure_codec_index(&attrs, m.span()) { + tokens.extend(err.into_compile_error()); + return + } + + quoted_enum.extend(quote! { + #(#attrs)* + #aggregate_name(#dynam_params_ident::#mod_name::Parameters), + }); + } + + // Inject the outer args into the inner `#[dynamic_pallet_params(..)]` attribute. + if let Some(params_pallet) = &self.meta.params_pallet { + MacroInjectArgs { runtime_params: name.clone(), params_pallet: params_pallet.clone() } + .visit_item_mod_mut(&mut params_mod); + } + + tokens.extend(quote! { + #params_mod + + #[#scrate::dynamic_params::dynamic_aggregated_params_internal] + pub enum #name { + #quoted_enum + } + }); + } +} + +/// Ensure there is a `#[codec(index = ..)]` attribute. +fn ensure_codec_index(attrs: &Vec, span: Span) -> Result<()> { + let mut found = false; + + for attr in attrs.iter() { + if attr.path().is_ident("codec") { + let meta: syn::ExprAssign = attr.parse_args()?; + if meta.left.to_token_stream().to_string() == "index" { + found = true; + break + } + } + } + + if !found { + Err(syn::Error::new(span, "Missing explicit `#[codec(index = ..)]` attribute")) + } else { + Ok(()) + } +} + +/// Used to inject arguments into the inner `#[dynamic_pallet_params(..)]` attribute. +/// +/// This allows the outer `#[dynamic_params(..)]` attribute to specify some arguments that dont need +/// to be repeated every time. +struct MacroInjectArgs { + runtime_params: syn::Ident, + params_pallet: syn::Type, +} +impl VisitMut for MacroInjectArgs { + fn visit_item_mod_mut(&mut self, item: &mut syn::ItemMod) { + // Check if the mod has a `#[dynamic_pallet_params(..)]` attribute. + let attr = item.attrs.iter_mut().find(|attr| attr.path().is_ident("dynamic_pallet_params")); + + if let Some(attr) = attr { + match &attr.meta { + syn::Meta::Path(path) => + assert_eq!(path.to_token_stream().to_string(), "dynamic_pallet_params"), + _ => (), + } + + let runtime_params = &self.runtime_params; + let params_pallet = &self.params_pallet; + + attr.meta = syn::parse2::(quote! { + dynamic_pallet_params(#runtime_params, #params_pallet) + }) + .unwrap() + .into(); + } + + visit_mut::visit_item_mod_mut(self, item); + } +} +/// The helper attribute of a `#[dynamic_pallet_params(runtime_params, params_pallet)]` +/// attribute. +#[derive(derive_syn_parse::Parse)] +pub struct DynamicPalletParamAttr { + inner_mod: syn::ItemMod, + meta: DynamicPalletParamAttrMeta, +} + +/// The inner meta of a `#[dynamic_pallet_params(..)]` attribute. +#[derive(derive_syn_parse::Parse)] +pub struct DynamicPalletParamAttrMeta { + runtime_params: syn::Ident, + _comma: Token![,], + parameter_pallet: syn::Type, +} + +impl DynamicPalletParamAttr { + pub fn parse(attr: TokenStream, item: TokenStream) -> Result { + Ok(Self { inner_mod: parse2(item)?, meta: parse2(attr)? }) + } + + pub fn statics(&self) -> Vec { + self.inner_mod.content.as_ref().map_or(Vec::new(), |(_, items)| { + items + .iter() + .filter_map(|i| match i { + syn::Item::Static(s) => Some(s), + _ => None, + }) + .cloned() + .collect() + }) + } +} + +impl ToTokens for DynamicPalletParamAttr { + fn to_tokens(&self, tokens: &mut TokenStream) { + let scrate = match crate_access() { + Ok(path) => path, + Err(err) => return tokens.extend(err), + }; + let (params_mod, parameter_pallet, runtime_params) = + (&self.inner_mod, &self.meta.parameter_pallet, &self.meta.runtime_params); + + let aggregate_name = + syn::Ident::new(¶ms_mod.ident.to_string().to_class_case(), params_mod.ident.span()); + let (mod_name, vis) = (¶ms_mod.ident, ¶ms_mod.vis); + let statics = self.statics(); + + let (mut key_names, mut key_values, mut defaults, mut attrs, mut value_types): ( + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + ) = Default::default(); + + for s in statics.iter() { + if let Err(err) = ensure_codec_index(&s.attrs, s.span()) { + tokens.extend(err.into_compile_error()); + return + } + + key_names.push(&s.ident); + key_values.push(format_ident!("{}Value", &s.ident)); + defaults.push(&s.expr); + attrs.push(&s.attrs); + value_types.push(&s.ty); + } + + let key_ident = syn::Ident::new("ParametersKey", params_mod.ident.span()); + let value_ident = syn::Ident::new("ParametersValue", params_mod.ident.span()); + let runtime_key_ident = format_ident!("{}Key", runtime_params); + let runtime_value_ident = format_ident!("{}Value", runtime_params); + + tokens.extend(quote! { + pub mod #mod_name { + use super::*; + + #[doc(hidden)] + #[derive( + Clone, + PartialEq, + Eq, + #scrate::__private::codec::Encode, + #scrate::__private::codec::Decode, + #scrate::__private::codec::MaxEncodedLen, + #scrate::__private::RuntimeDebug, + #scrate::__private::scale_info::TypeInfo + )] + #vis enum Parameters { + #( + #(#attrs)* + #key_names(#key_names, Option<#value_types>), + )* + } + + #[doc(hidden)] + #[derive( + Clone, + PartialEq, + Eq, + #scrate::__private::codec::Encode, + #scrate::__private::codec::Decode, + #scrate::__private::codec::MaxEncodedLen, + #scrate::__private::RuntimeDebug, + #scrate::__private::scale_info::TypeInfo + )] + #vis enum #key_ident { + #( + #(#attrs)* + #key_names(#key_names), + )* + } + + #[doc(hidden)] + #[derive( + Clone, + PartialEq, + Eq, + #scrate::__private::codec::Encode, + #scrate::__private::codec::Decode, + #scrate::__private::codec::MaxEncodedLen, + #scrate::__private::RuntimeDebug, + #scrate::__private::scale_info::TypeInfo + )] + #vis enum #value_ident { + #( + #(#attrs)* + #key_names(#value_types), + )* + } + + impl #scrate::traits::dynamic_params::AggregratedKeyValue for Parameters { + type Key = #key_ident; + type Value = #value_ident; + + fn into_parts(self) -> (Self::Key, Option) { + match self { + #( + Parameters::#key_names(key, value) => { + (#key_ident::#key_names(key), value.map(#value_ident::#key_names)) + }, + )* + } + } + } + + #( + #[doc(hidden)] + #[derive( + Clone, + PartialEq, + Eq, + #scrate::__private::codec::Encode, + #scrate::__private::codec::Decode, + #scrate::__private::codec::MaxEncodedLen, + #scrate::__private::RuntimeDebug, + #scrate::__private::scale_info::TypeInfo + )] + #vis struct #key_names; + + impl #scrate::__private::Get<#value_types> for #key_names { + fn get() -> #value_types { + match + <#parameter_pallet as + #scrate::storage::StorageMap<#runtime_key_ident, #runtime_value_ident> + >::get(#runtime_key_ident::#aggregate_name(#key_ident::#key_names(#key_names))) + { + Some(#runtime_value_ident::#aggregate_name( + #value_ident::#key_names(inner))) => inner, + Some(_) => { + #scrate::defensive!("Unexpected value type at key - returning default"); + #defaults + }, + None => #defaults, + } + } + } + + impl #scrate::traits::dynamic_params::Key for #key_names { + type Value = #value_types; + type WrappedValue = #key_values; + } + + impl From<#key_names> for #key_ident { + fn from(key: #key_names) -> Self { + #key_ident::#key_names(key) + } + } + + impl TryFrom<#key_ident> for #key_names { + type Error = (); + + fn try_from(key: #key_ident) -> Result { + match key { + #key_ident::#key_names(key) => Ok(key), + _ => Err(()), + } + } + } + + #[doc(hidden)] + #[derive( + Clone, + PartialEq, + Eq, + #scrate::sp_runtime::RuntimeDebug, + )] + #vis struct #key_values(pub #value_types); + + impl From<#key_values> for #value_ident { + fn from(value: #key_values) -> Self { + #value_ident::#key_names(value.0) + } + } + + impl From<(#key_names, #value_types)> for Parameters { + fn from((key, value): (#key_names, #value_types)) -> Self { + Parameters::#key_names(key, Some(value)) + } + } + + impl From<#key_names> for Parameters { + fn from(key: #key_names) -> Self { + Parameters::#key_names(key, None) + } + } + + impl TryFrom<#value_ident> for #key_values { + type Error = (); + + fn try_from(value: #value_ident) -> Result { + match value { + #value_ident::#key_names(value) => Ok(#key_values(value)), + _ => Err(()), + } + } + } + + impl From<#key_values> for #value_types { + fn from(value: #key_values) -> Self { + value.0 + } + } + )* + } + }); + } +} + +#[derive(derive_syn_parse::Parse)] +pub struct DynamicParamAggregatedEnum { + aggregated_enum: syn::ItemEnum, +} + +impl ToTokens for DynamicParamAggregatedEnum { + fn to_tokens(&self, tokens: &mut TokenStream) { + let scrate = match crate_access() { + Ok(path) => path, + Err(err) => return tokens.extend(err), + }; + let params_enum = &self.aggregated_enum; + let (name, vis) = (¶ms_enum.ident, ¶ms_enum.vis); + + let (mut indices, mut param_names, mut param_types): (Vec<_>, Vec<_>, Vec<_>) = + Default::default(); + let mut attributes = Vec::new(); + for (i, variant) in params_enum.variants.iter().enumerate() { + indices.push(i); + param_names.push(&variant.ident); + attributes.push(&variant.attrs); + + param_types.push(match &variant.fields { + syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty, + _ => { + *tokens = quote! { compile_error!("Only unnamed enum variants with one inner item are supported") }; + return + }, + }); + } + + let params_key_ident = format_ident!("{}Key", params_enum.ident); + let params_value_ident = format_ident!("{}Value", params_enum.ident); + + tokens.extend(quote! { + #[doc(hidden)] + #[derive( + Clone, + PartialEq, + Eq, + #scrate::__private::codec::Encode, + #scrate::__private::codec::Decode, + #scrate::__private::codec::MaxEncodedLen, + #scrate::sp_runtime::RuntimeDebug, + #scrate::__private::scale_info::TypeInfo + )] + #vis enum #name { + #( + //#[codec(index = #indices)] + #(#attributes)* + #param_names(#param_types), + )* + } + + #[doc(hidden)] + #[derive( + Clone, + PartialEq, + Eq, + #scrate::__private::codec::Encode, + #scrate::__private::codec::Decode, + #scrate::__private::codec::MaxEncodedLen, + #scrate::sp_runtime::RuntimeDebug, + #scrate::__private::scale_info::TypeInfo + )] + #vis enum #params_key_ident { + #( + #(#attributes)* + #param_names(<#param_types as #scrate::traits::dynamic_params::AggregratedKeyValue>::Key), + )* + } + + #[doc(hidden)] + #[derive( + Clone, + PartialEq, + Eq, + #scrate::__private::codec::Encode, + #scrate::__private::codec::Decode, + #scrate::__private::codec::MaxEncodedLen, + #scrate::sp_runtime::RuntimeDebug, + #scrate::__private::scale_info::TypeInfo + )] + #vis enum #params_value_ident { + #( + #(#attributes)* + #param_names(<#param_types as #scrate::traits::dynamic_params::AggregratedKeyValue>::Value), + )* + } + + impl #scrate::traits::dynamic_params::AggregratedKeyValue for #name { + type Key = #params_key_ident; + type Value = #params_value_ident; + + fn into_parts(self) -> (Self::Key, Option) { + match self { + #( + #name::#param_names(parameter) => { + let (key, value) = parameter.into_parts(); + (#params_key_ident::#param_names(key), value.map(#params_value_ident::#param_names)) + }, + )* + } + } + } + + #( + impl ::core::convert::From<<#param_types as #scrate::traits::dynamic_params::AggregratedKeyValue>::Key> for #params_key_ident { + fn from(key: <#param_types as #scrate::traits::dynamic_params::AggregratedKeyValue>::Key) -> Self { + #params_key_ident::#param_names(key) + } + } + + impl ::core::convert::TryFrom<#params_value_ident> for <#param_types as #scrate::traits::dynamic_params::AggregratedKeyValue>::Value { + type Error = (); + + fn try_from(value: #params_value_ident) -> Result { + match value { + #params_value_ident::#param_names(value) => Ok(value), + _ => Err(()), + } + } + } + )* + }); + } +} + +/// Get access to the current crate and convert the error to a compile error. +fn crate_access() -> core::result::Result { + generate_access_from_frame_or_crate("frame-support").map_err(|e| e.to_compile_error()) +} diff --git a/substrate/frame/support/procedural/src/lib.rs b/substrate/frame/support/procedural/src/lib.rs index 34e68966b82..20b8d74310f 100644 --- a/substrate/frame/support/procedural/src/lib.rs +++ b/substrate/frame/support/procedural/src/lib.rs @@ -18,12 +18,14 @@ //! Proc macro of Support code for the runtime. #![recursion_limit = "512"] +#![deny(rustdoc::broken_intra_doc_links)] mod benchmark; mod construct_runtime; mod crate_version; mod derive_impl; mod dummy_part_checker; +mod dynamic_params; mod key_prefix; mod match_and_insert; mod no_bound; @@ -890,12 +892,13 @@ pub fn inject_runtime_type(_: TokenStream, tokens: TokenStream) -> TokenStream { item.ident != "RuntimeOrigin" && item.ident != "RuntimeHoldReason" && item.ident != "RuntimeFreezeReason" && + item.ident != "RuntimeParameters" && item.ident != "PalletInfo" { return syn::Error::new_spanned( item, "`#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, \ - `RuntimeTask`, `RuntimeOrigin` or `PalletInfo`", + `RuntimeTask`, `RuntimeOrigin`, `RuntimeParameters` or `PalletInfo`", ) .to_compile_error() .into() @@ -1685,3 +1688,52 @@ pub fn import_section(attr: TokenStream, tokens: TokenStream) -> TokenStream { } .into() } + +/// Mark a module that contains dynamic parameters. +/// +/// See the `pallet_parameters` for a full example. +/// +/// # Arguments +/// +/// The macro accepts two positional arguments, of which the second is optional. +/// +/// ## Aggregated Enum Name +/// +/// This sets the name that the aggregated Key-Value enum will be named after. Common names would be +/// `RuntimeParameters`, akin to `RuntimeCall`, `RuntimeOrigin` etc. There is no default value for +/// this argument. +/// +/// ## Parameter Storage Backend +/// +/// The second argument provides access to the storage of the parameters. It can either be set on +/// on this attribute, or on the inner ones. If set on both, the inner one takes precedence. +#[proc_macro_attribute] +pub fn dynamic_params(attrs: TokenStream, input: TokenStream) -> TokenStream { + dynamic_params::dynamic_params(attrs.into(), input.into()) + .unwrap_or_else(|r| r.into_compile_error()) + .into() +} + +/// Define a module inside a [`macro@dynamic_params`] module that contains dynamic parameters. +/// +/// See the `pallet_parameters` for a full example. +/// +/// # Argument +/// +/// This attribute takes one optional argument. The argument can either be put here or on the +/// surrounding `#[dynamic_params]` attribute. If set on both, the inner one takes precedence. +#[proc_macro_attribute] +pub fn dynamic_pallet_params(attrs: TokenStream, input: TokenStream) -> TokenStream { + dynamic_params::dynamic_pallet_params(attrs.into(), input.into()) + .unwrap_or_else(|r| r.into_compile_error()) + .into() +} + +/// Used internally by [`dynamic_params`]. +#[doc(hidden)] +#[proc_macro_attribute] +pub fn dynamic_aggregated_params_internal(attrs: TokenStream, input: TokenStream) -> TokenStream { + dynamic_params::dynamic_aggregated_params_internal(attrs.into(), input.into()) + .unwrap_or_else(|r| r.into_compile_error()) + .into() +} diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs index 0f6cf05959f..8935acf4e2b 100644 --- a/substrate/frame/support/src/lib.rs +++ b/substrate/frame/support/src/lib.rs @@ -44,7 +44,7 @@ pub mod __private { pub use paste; pub use scale_info; pub use serde; - pub use sp_core::{OpaqueMetadata, Void}; + pub use sp_core::{Get, OpaqueMetadata, Void}; pub use sp_crypto_hashing_proc_macro; pub use sp_inherents; #[cfg(feature = "std")] @@ -175,6 +175,14 @@ pub use frame_support_procedural::storage_alias; pub use frame_support_procedural::derive_impl; +/// Experimental macros for defining dynamic params that can be used in pallet configs. +#[cfg(feature = "experimental")] +pub mod dynamic_params { + pub use frame_support_procedural::{ + dynamic_aggregated_params_internal, dynamic_pallet_params, dynamic_params, + }; +} + /// Create new implementations of the [`Get`](crate::traits::Get) trait. /// /// The so-called parameter type can be created in four different ways: diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs index 2a42fca76b3..3d0429f71b1 100644 --- a/substrate/frame/support/src/traits.rs +++ b/substrate/frame/support/src/traits.rs @@ -124,6 +124,8 @@ pub use safe_mode::{SafeMode, SafeModeError, SafeModeNotify}; mod tx_pause; pub use tx_pause::{TransactionPause, TransactionPauseError}; +pub mod dynamic_params; + pub mod tasks; pub use tasks::Task; diff --git a/substrate/frame/support/src/traits/dynamic_params.rs b/substrate/frame/support/src/traits/dynamic_params.rs new file mode 100644 index 00000000000..8881df04141 --- /dev/null +++ b/substrate/frame/support/src/traits/dynamic_params.rs @@ -0,0 +1,153 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Types and traits for dynamic parameters. +//! +//! Can be used by 3rd party macros to define dynamic parameters that are compatible with the the +//! `parameters` pallet. + +use codec::MaxEncodedLen; +use frame_support::Parameter; + +/// A dynamic parameter store across an aggregated KV type. +pub trait RuntimeParameterStore { + type AggregratedKeyValue: AggregratedKeyValue; + + /// Get the value of a parametrized key. + /// + /// Should return `None` if no explicit value was set instead of a default. + fn get(key: K) -> Option + where + KV: AggregratedKeyValue, + K: Key + Into<::Key>, + ::Key: IntoKey< + <::AggregratedKeyValue as AggregratedKeyValue>::Key, + >, + <::AggregratedKeyValue as AggregratedKeyValue>::Value: + TryIntoKey<::Value>, + ::Value: TryInto; +} + +/// A dynamic parameter store across a concrete KV type. +pub trait ParameterStore { + /// Get the value of a parametrized key. + fn get(key: K) -> Option + where + K: Key + Into<::Key>, + ::Value: TryInto; +} + +/// Key of a dynamic parameter. +pub trait Key { + /// The value that the key is parametrized with. + type Value; + + /// An opaque representation of `Self::Value`. + type WrappedValue: Into; +} + +/// The aggregated key-value type of a dynamic parameter store. +pub trait AggregratedKeyValue: Parameter { + /// The aggregated key type. + type Key: Parameter + MaxEncodedLen; + + /// The aggregated value type. + type Value: Parameter + MaxEncodedLen; + + /// Split the aggregated key-value type into its parts. + fn into_parts(self) -> (Self::Key, Option); +} + +impl AggregratedKeyValue for () { + type Key = (); + type Value = (); + + fn into_parts(self) -> (Self::Key, Option) { + ((), None) + } +} + +/// Allows to create a `ParameterStore` from a `RuntimeParameterStore`. +/// +/// This concretization is useful when configuring pallets, since a pallet will require a parameter +/// store for its own KV type and not the aggregated runtime-wide KV type. +pub struct ParameterStoreAdapter(sp_std::marker::PhantomData<(PS, KV)>); + +impl ParameterStore for ParameterStoreAdapter +where + PS: RuntimeParameterStore, + KV: AggregratedKeyValue, + ::Key: + IntoKey<<::AggregratedKeyValue as AggregratedKeyValue>::Key>, + ::Value: TryFromKey< + <::AggregratedKeyValue as AggregratedKeyValue>::Value, + >, +{ + fn get(key: K) -> Option + where + K: Key + Into<::Key>, + ::Value: TryInto, + { + PS::get::(key) + } +} + +// workaround for rust bug https://github.com/rust-lang/rust/issues/51445 +mod workaround { + pub trait FromKey: Sized { + #[must_use] + fn from_key(value: T) -> Self; + } + + pub trait IntoKey: Sized { + #[must_use] + fn into_key(self) -> T; + } + + impl IntoKey for T + where + U: FromKey, + { + fn into_key(self) -> U { + U::from_key(self) + } + } + + pub trait TryIntoKey: Sized { + type Error; + + fn try_into_key(self) -> Result; + } + + pub trait TryFromKey: Sized { + type Error; + + fn try_from_key(value: T) -> Result; + } + + impl TryIntoKey for T + where + U: TryFromKey, + { + type Error = U::Error; + + fn try_into_key(self) -> Result { + U::try_from_key(self) + } + } +} +pub use workaround::*; diff --git a/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr b/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr index cda20288984..c7159b34afb 100644 --- a/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr +++ b/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr @@ -1,4 +1,4 @@ -error: `#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, `RuntimeTask`, `RuntimeOrigin` or `PalletInfo` +error: `#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, `RuntimeTask`, `RuntimeOrigin`, `RuntimeParameters` or `PalletInfo` --> tests/derive_impl_ui/inject_runtime_type_invalid.rs:32:5 | 32 | type RuntimeInfo = (); -- GitLab From b2c81b5800ffd5528dac26c2fc4059d43ed7404b Mon Sep 17 00:00:00 2001 From: Egor_P Date: Fri, 9 Feb 2024 14:41:40 +0700 Subject: [PATCH 035/133] [Backport] Version bumps from 1.7.0 release (#3254) This PR backports version bumps from `1.7.0` release branch and moves related prdoc files to the appropriate folder. --- .../parachains/runtimes/assets/asset-hub-rococo/src/lib.rs | 4 ++-- .../parachains/runtimes/assets/asset-hub-westend/src/lib.rs | 2 +- .../runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs | 2 +- .../runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs | 2 +- .../runtimes/collectives/collectives-westend/src/lib.rs | 2 +- .../parachains/runtimes/contracts/contracts-rococo/src/lib.rs | 2 +- .../parachains/runtimes/coretime/coretime-rococo/src/lib.rs | 2 +- .../parachains/runtimes/coretime/coretime-westend/src/lib.rs | 2 +- .../parachains/runtimes/glutton/glutton-westend/src/lib.rs | 2 +- cumulus/parachains/runtimes/people/people-rococo/src/lib.rs | 2 +- cumulus/parachains/runtimes/people/people-westend/src/lib.rs | 2 +- .../parachains/runtimes/testing/rococo-parachain/src/lib.rs | 2 +- polkadot/node/primitives/src/lib.rs | 2 +- polkadot/runtime/rococo/src/lib.rs | 2 +- polkadot/runtime/westend/src/lib.rs | 2 +- prdoc/{ => 1.7.0}/pr_1222.prdoc | 0 prdoc/{ => 1.7.0}/pr_1230.prdoc | 0 prdoc/{ => 1.7.0}/pr_1296.prdoc | 0 prdoc/{ => 1.7.0}/pr_1313.prdoc | 0 prdoc/{ => 1.7.0}/pr_1845.prdoc | 0 prdoc/{ => 1.7.0}/pr_1871.prdoc | 0 prdoc/{ => 1.7.0}/pr_2125.prdoc | 0 prdoc/{ => 1.7.0}/pr_2467.prdoc | 0 .../pr_2477-use-clone-instead-of-fork-on-pvf.prdoc | 0 prdoc/{ => 1.7.0}/pr_2587.prdoc | 0 prdoc/{ => 1.7.0}/pr_2657.prdoc | 0 prdoc/{ => 1.7.0}/pr_2796.prdoc | 0 prdoc/{ => 1.7.0}/pr_2826.prdoc | 0 prdoc/{ => 1.7.0}/pr_2889.prdoc | 0 prdoc/{ => 1.7.0}/pr_2920.prdoc | 0 prdoc/{ => 1.7.0}/pr_2924.prdoc | 0 prdoc/{ => 1.7.0}/pr_2942.prdoc | 0 prdoc/{ => 1.7.0}/pr_2970.prdoc | 0 prdoc/{ => 1.7.0}/pr_3001.prdoc | 0 prdoc/{ => 1.7.0}/pr_3009.prdoc | 0 prdoc/{ => 1.7.0}/pr_3020.prdoc | 0 prdoc/{ => 1.7.0}/pr_3029.prdoc | 0 prdoc/{ => 1.7.0}/pr_3040.prdoc | 0 prdoc/{ => 1.7.0}/pr_3057.prdoc | 0 prdoc/{ => 1.7.0}/pr_3061.prdoc | 0 prdoc/{ => 1.7.0}/pr_3077.prdoc | 0 prdoc/{ => 1.7.0}/pr_3108.prdoc | 0 prdoc/{ => 1.7.0}/pr_3110.prdoc | 0 prdoc/{ => 1.7.0}/pr_3156.prdoc | 0 prdoc/{ => 1.7.0}/pr_3228.prdoc | 0 45 files changed, 16 insertions(+), 16 deletions(-) rename prdoc/{ => 1.7.0}/pr_1222.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_1230.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_1296.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_1313.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_1845.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_1871.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_2125.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_2467.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_2477-use-clone-instead-of-fork-on-pvf.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_2587.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_2657.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_2796.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_2826.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_2889.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_2920.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_2924.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_2942.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_2970.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_3001.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_3009.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_3020.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_3029.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_3040.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_3057.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_3061.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_3077.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_3108.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_3110.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_3156.prdoc (100%) rename prdoc/{ => 1.7.0}/pr_3228.prdoc (100%) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index 553c70bc554..ed893091533 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -113,7 +113,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("statemine"), impl_name: create_runtime_str!("statemine"), authoring_version: 1, - spec_version: 1_006_000, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 14, @@ -126,7 +126,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("statemine"), impl_name: create_runtime_str!("statemine"), authoring_version: 1, - spec_version: 1_006_000, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 14, diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 409a5f1177d..bcd3a9aa896 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -110,7 +110,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("westmint"), impl_name: create_runtime_str!("westmint"), authoring_version: 1, - spec_version: 1_006_000, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 14, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 0d4c1defa37..066e37826b2 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -206,7 +206,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("bridge-hub-rococo"), impl_name: create_runtime_str!("bridge-hub-rococo"), authoring_version: 1, - spec_version: 1_006_001, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 4, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 6188b0acd7d..79d251517c6 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -178,7 +178,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("bridge-hub-westend"), impl_name: create_runtime_str!("bridge-hub-westend"), authoring_version: 1, - spec_version: 1_006_000, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 4, diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs index d245576494f..4299e8bb3ec 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs @@ -117,7 +117,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("collectives-westend"), impl_name: create_runtime_str!("collectives-westend"), authoring_version: 1, - spec_version: 1_006_000, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 5, diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs index 7d0dd43ea88..a0eb74657bd 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs @@ -133,7 +133,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("contracts-rococo"), impl_name: create_runtime_str!("contracts-rococo"), authoring_version: 1, - spec_version: 1_006_000, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 6, diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs index 8b952cfba22..b644640f2c3 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs @@ -133,7 +133,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("coretime-rococo"), impl_name: create_runtime_str!("coretime-rococo"), authoring_version: 1, - spec_version: 1_006_001, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 0, diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs index d8087c05b73..edc06688d0f 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs @@ -123,7 +123,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("coretime-westend"), impl_name: create_runtime_str!("coretime-westend"), authoring_version: 1, - spec_version: 1_006_000, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 0, diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs index e0ab9c590ba..10408aaf39a 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs @@ -100,7 +100,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("glutton-westend"), impl_name: create_runtime_str!("glutton-westend"), authoring_version: 1, - spec_version: 1_006_000, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs index 652485a69fd..5e96395532e 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -126,7 +126,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("people-rococo"), impl_name: create_runtime_str!("people-rococo"), authoring_version: 1, - spec_version: 1_006_002, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 0, diff --git a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs index b24f6767a71..4ff743aefe1 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs @@ -126,7 +126,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("people-westend"), impl_name: create_runtime_str!("people-westend"), authoring_version: 1, - spec_version: 1_006_001, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 0, diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs index c43d79271de..11a0f8b9570 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs @@ -109,7 +109,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("test-parachain"), impl_name: create_runtime_str!("test-parachain"), authoring_version: 1, - spec_version: 1_006_000, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 6, diff --git a/polkadot/node/primitives/src/lib.rs b/polkadot/node/primitives/src/lib.rs index e7fd2c46381..6e3eefbcbe8 100644 --- a/polkadot/node/primitives/src/lib.rs +++ b/polkadot/node/primitives/src/lib.rs @@ -58,7 +58,7 @@ pub use disputes::{ /// relatively rare. /// /// The associated worker binaries should use the same version as the node that spawns them. -pub const NODE_VERSION: &'static str = "1.6.0"; +pub const NODE_VERSION: &'static str = "1.7.0"; // For a 16-ary Merkle Prefix Trie, we can expect at most 16 32-byte hashes per node // plus some overhead: diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 184d0338eb0..45f27312697 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -150,7 +150,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("rococo"), impl_name: create_runtime_str!("parity-rococo-v2.0"), authoring_version: 0, - spec_version: 1_006_002, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 24, diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 8d903ebf62c..166c2d0d96e 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -147,7 +147,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("westend"), impl_name: create_runtime_str!("parity-westend"), authoring_version: 2, - spec_version: 1_006_001, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 24, diff --git a/prdoc/pr_1222.prdoc b/prdoc/1.7.0/pr_1222.prdoc similarity index 100% rename from prdoc/pr_1222.prdoc rename to prdoc/1.7.0/pr_1222.prdoc diff --git a/prdoc/pr_1230.prdoc b/prdoc/1.7.0/pr_1230.prdoc similarity index 100% rename from prdoc/pr_1230.prdoc rename to prdoc/1.7.0/pr_1230.prdoc diff --git a/prdoc/pr_1296.prdoc b/prdoc/1.7.0/pr_1296.prdoc similarity index 100% rename from prdoc/pr_1296.prdoc rename to prdoc/1.7.0/pr_1296.prdoc diff --git a/prdoc/pr_1313.prdoc b/prdoc/1.7.0/pr_1313.prdoc similarity index 100% rename from prdoc/pr_1313.prdoc rename to prdoc/1.7.0/pr_1313.prdoc diff --git a/prdoc/pr_1845.prdoc b/prdoc/1.7.0/pr_1845.prdoc similarity index 100% rename from prdoc/pr_1845.prdoc rename to prdoc/1.7.0/pr_1845.prdoc diff --git a/prdoc/pr_1871.prdoc b/prdoc/1.7.0/pr_1871.prdoc similarity index 100% rename from prdoc/pr_1871.prdoc rename to prdoc/1.7.0/pr_1871.prdoc diff --git a/prdoc/pr_2125.prdoc b/prdoc/1.7.0/pr_2125.prdoc similarity index 100% rename from prdoc/pr_2125.prdoc rename to prdoc/1.7.0/pr_2125.prdoc diff --git a/prdoc/pr_2467.prdoc b/prdoc/1.7.0/pr_2467.prdoc similarity index 100% rename from prdoc/pr_2467.prdoc rename to prdoc/1.7.0/pr_2467.prdoc diff --git a/prdoc/pr_2477-use-clone-instead-of-fork-on-pvf.prdoc b/prdoc/1.7.0/pr_2477-use-clone-instead-of-fork-on-pvf.prdoc similarity index 100% rename from prdoc/pr_2477-use-clone-instead-of-fork-on-pvf.prdoc rename to prdoc/1.7.0/pr_2477-use-clone-instead-of-fork-on-pvf.prdoc diff --git a/prdoc/pr_2587.prdoc b/prdoc/1.7.0/pr_2587.prdoc similarity index 100% rename from prdoc/pr_2587.prdoc rename to prdoc/1.7.0/pr_2587.prdoc diff --git a/prdoc/pr_2657.prdoc b/prdoc/1.7.0/pr_2657.prdoc similarity index 100% rename from prdoc/pr_2657.prdoc rename to prdoc/1.7.0/pr_2657.prdoc diff --git a/prdoc/pr_2796.prdoc b/prdoc/1.7.0/pr_2796.prdoc similarity index 100% rename from prdoc/pr_2796.prdoc rename to prdoc/1.7.0/pr_2796.prdoc diff --git a/prdoc/pr_2826.prdoc b/prdoc/1.7.0/pr_2826.prdoc similarity index 100% rename from prdoc/pr_2826.prdoc rename to prdoc/1.7.0/pr_2826.prdoc diff --git a/prdoc/pr_2889.prdoc b/prdoc/1.7.0/pr_2889.prdoc similarity index 100% rename from prdoc/pr_2889.prdoc rename to prdoc/1.7.0/pr_2889.prdoc diff --git a/prdoc/pr_2920.prdoc b/prdoc/1.7.0/pr_2920.prdoc similarity index 100% rename from prdoc/pr_2920.prdoc rename to prdoc/1.7.0/pr_2920.prdoc diff --git a/prdoc/pr_2924.prdoc b/prdoc/1.7.0/pr_2924.prdoc similarity index 100% rename from prdoc/pr_2924.prdoc rename to prdoc/1.7.0/pr_2924.prdoc diff --git a/prdoc/pr_2942.prdoc b/prdoc/1.7.0/pr_2942.prdoc similarity index 100% rename from prdoc/pr_2942.prdoc rename to prdoc/1.7.0/pr_2942.prdoc diff --git a/prdoc/pr_2970.prdoc b/prdoc/1.7.0/pr_2970.prdoc similarity index 100% rename from prdoc/pr_2970.prdoc rename to prdoc/1.7.0/pr_2970.prdoc diff --git a/prdoc/pr_3001.prdoc b/prdoc/1.7.0/pr_3001.prdoc similarity index 100% rename from prdoc/pr_3001.prdoc rename to prdoc/1.7.0/pr_3001.prdoc diff --git a/prdoc/pr_3009.prdoc b/prdoc/1.7.0/pr_3009.prdoc similarity index 100% rename from prdoc/pr_3009.prdoc rename to prdoc/1.7.0/pr_3009.prdoc diff --git a/prdoc/pr_3020.prdoc b/prdoc/1.7.0/pr_3020.prdoc similarity index 100% rename from prdoc/pr_3020.prdoc rename to prdoc/1.7.0/pr_3020.prdoc diff --git a/prdoc/pr_3029.prdoc b/prdoc/1.7.0/pr_3029.prdoc similarity index 100% rename from prdoc/pr_3029.prdoc rename to prdoc/1.7.0/pr_3029.prdoc diff --git a/prdoc/pr_3040.prdoc b/prdoc/1.7.0/pr_3040.prdoc similarity index 100% rename from prdoc/pr_3040.prdoc rename to prdoc/1.7.0/pr_3040.prdoc diff --git a/prdoc/pr_3057.prdoc b/prdoc/1.7.0/pr_3057.prdoc similarity index 100% rename from prdoc/pr_3057.prdoc rename to prdoc/1.7.0/pr_3057.prdoc diff --git a/prdoc/pr_3061.prdoc b/prdoc/1.7.0/pr_3061.prdoc similarity index 100% rename from prdoc/pr_3061.prdoc rename to prdoc/1.7.0/pr_3061.prdoc diff --git a/prdoc/pr_3077.prdoc b/prdoc/1.7.0/pr_3077.prdoc similarity index 100% rename from prdoc/pr_3077.prdoc rename to prdoc/1.7.0/pr_3077.prdoc diff --git a/prdoc/pr_3108.prdoc b/prdoc/1.7.0/pr_3108.prdoc similarity index 100% rename from prdoc/pr_3108.prdoc rename to prdoc/1.7.0/pr_3108.prdoc diff --git a/prdoc/pr_3110.prdoc b/prdoc/1.7.0/pr_3110.prdoc similarity index 100% rename from prdoc/pr_3110.prdoc rename to prdoc/1.7.0/pr_3110.prdoc diff --git a/prdoc/pr_3156.prdoc b/prdoc/1.7.0/pr_3156.prdoc similarity index 100% rename from prdoc/pr_3156.prdoc rename to prdoc/1.7.0/pr_3156.prdoc diff --git a/prdoc/pr_3228.prdoc b/prdoc/1.7.0/pr_3228.prdoc similarity index 100% rename from prdoc/pr_3228.prdoc rename to prdoc/1.7.0/pr_3228.prdoc -- GitLab From edd95b3749754d2ed0c5738588e872c87be91624 Mon Sep 17 00:00:00 2001 From: Evgeny Snitko Date: Fri, 9 Feb 2024 15:40:19 +0300 Subject: [PATCH 036/133] Add forklift to remaining jobs (#3236) Add [forklift caching](https://gitlab.parity.io/parity/infrastructure/ci_cd/forklift/forklift) to remainig jobs by .sh and .py scripts: - cargo-check-each-crate x6 (`.gitlab/check-each-crate.py`) - build-linux-stable (`polkadot/scripts/build-only-wasm.sh`) by before_script: - build-linux-substrate - build-subkey-linux (with `.build-subkey` job) - cargo-check-benches x2 **To disable feature set FORKLIFT_BYPASS variable to true in [project settings in gitlab](https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/settings/ci_cd)** (forklift now handles FORKLIFT_BYPASS by itself) --- .gitlab-ci.yml | 8 ++------ .gitlab/check-each-crate.py | 2 +- .gitlab/pipeline/build.yml | 6 ++++-- .gitlab/pipeline/test.yml | 1 + polkadot/scripts/build-only-wasm.sh | 12 ++++++++++-- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c90a6c8e6e2..7f8796ca512 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -121,9 +121,8 @@ default: before_script: - 'curl --header "PRIVATE-TOKEN: $FL_CI_GROUP_TOKEN" -o forklift -L "${CI_API_V4_URL}/projects/676/packages/generic/forklift/${FL_FORKLIFT_VERSION}/forklift_${FL_FORKLIFT_VERSION}_linux_amd64"' - chmod +x forklift - - mkdir .forklift - - cp $FL_FORKLIFT_CONFIG .forklift/config.toml - - export FORKLIFT_PACKAGE_SUFFIX=${CI_JOB_NAME/ [0-9 \/]*} + - mkdir ~/.forklift + - cp $FL_FORKLIFT_CONFIG ~/.forklift/config.toml - shopt -s expand_aliases - export PATH=$PATH:$(pwd) - | @@ -131,11 +130,8 @@ default: echo "FORKLIFT_BYPASS not set, creating alias cargo='forklift cargo'" alias cargo="forklift cargo" fi - - ls -al - - rm -f forklift.sock # - echo "FL_FORKLIFT_VERSION ${FL_FORKLIFT_VERSION}" - - echo "FORKLIFT_PACKAGE_SUFFIX $FORKLIFT_PACKAGE_SUFFIX" .common-refs: rules: diff --git a/.gitlab/check-each-crate.py b/.gitlab/check-each-crate.py index da2eaad36c5..9b654f8071a 100755 --- a/.gitlab/check-each-crate.py +++ b/.gitlab/check-each-crate.py @@ -55,7 +55,7 @@ for i in range(0, crates_per_group + overflow_crates): print(f"Checking {crates[crate][0]}", file=sys.stderr) - res = subprocess.run(["cargo", "check", "--locked"], cwd = crates[crate][1]) + res = subprocess.run(["forklift", "cargo", "check", "--locked"], cwd = crates[crate][1]) if res.returncode != 0: sys.exit(1) diff --git a/.gitlab/pipeline/build.yml b/.gitlab/pipeline/build.yml index d9c67af7efd..482fc222aa0 100644 --- a/.gitlab/pipeline/build.yml +++ b/.gitlab/pipeline/build.yml @@ -314,8 +314,9 @@ build-linux-substrate: # tldr: we need to checkout the branch HEAD explicitly because of our dynamic versioning approach while building the substrate binary # see https://github.com/paritytech/ci_cd/issues/682#issuecomment-1340953589 - git checkout -B "$CI_COMMIT_REF_NAME" "$CI_COMMIT_SHA" + - !reference [.forklift-cache, before_script] script: - - WASM_BUILD_NO_COLOR=1 time cargo build --locked --release -p staging-node-cli + - time WASM_BUILD_NO_COLOR=1 cargo build --locked --release -p staging-node-cli - mv $CARGO_TARGET_DIR/release/substrate-node ./artifacts/substrate/substrate - echo -n "Substrate version = " - if [ "${CI_COMMIT_TAG}" ]; then @@ -352,9 +353,10 @@ build-runtimes-polkavm: CARGO_TARGET_DIR: "$CI_PROJECT_DIR/target" before_script: - mkdir -p ./artifacts/subkey + - !reference [.forklift-cache, before_script] script: - cd ./substrate/bin/utils/subkey - - SKIP_WASM_BUILD=1 time cargo build --locked --release + - time SKIP_WASM_BUILD=1 cargo build --locked --release # - cd - # - mv $CARGO_TARGET_DIR/release/subkey ./artifacts/subkey/. # - echo -n "Subkey version = " diff --git a/.gitlab/pipeline/test.yml b/.gitlab/pipeline/test.yml index e75700ffddc..b5e26d19489 100644 --- a/.gitlab/pipeline/test.yml +++ b/.gitlab/pipeline/test.yml @@ -224,6 +224,7 @@ cargo-check-benches: git merge --verbose --no-edit FETCH_HEAD; fi fi' + - !reference [.forklift-cache, before_script] parallel: 2 script: - mkdir -p ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA diff --git a/polkadot/scripts/build-only-wasm.sh b/polkadot/scripts/build-only-wasm.sh index b6da3319c82..50b786dab41 100755 --- a/polkadot/scripts/build-only-wasm.sh +++ b/polkadot/scripts/build-only-wasm.sh @@ -13,6 +13,14 @@ fi WASM_BUILDER_RUNNER="$PROJECT_ROOT/target/release/wbuild-runner/$1" +fl_cargo () { + if command -v forklift >/dev/null 2>&1; then + forklift cargo "$@"; + else + cargo "$@"; + fi +} + if [ -z "$2" ]; then export WASM_TARGET_DIRECTORY=$(pwd) else @@ -22,8 +30,8 @@ fi if [ -d $WASM_BUILDER_RUNNER ]; then export DEBUG=false export OUT_DIR="$PROJECT_ROOT/target/release/build" - cargo run --release --manifest-path="$WASM_BUILDER_RUNNER/Cargo.toml" \ + fl_cargo run --release --manifest-path="$WASM_BUILDER_RUNNER/Cargo.toml" \ | grep -vE "cargo:rerun-if-|Executing build command" else - cargo build --release -p $1 + fl_cargo build --release -p $1 fi -- GitLab From 4883e1448210d0efd9a397b41cc184772401ec35 Mon Sep 17 00:00:00 2001 From: maksimryndin Date: Sun, 11 Feb 2024 10:59:10 +0100 Subject: [PATCH 037/133] refactor pvf security module (#3047) resolve https://github.com/paritytech/polkadot-sdk/issues/2321 - [x] refactor `security` module into a conditionally compiled - [x] rename `amd64` into x86-64 for consistency with conditional compilation guards and remove reference to a particular vendor - [x] run unit tests and zombienet --------- Co-authored-by: s0me0ne-unkn0wn <48632512+s0me0ne-unkn0wn@users.noreply.github.com> --- polkadot/node/core/pvf/README.md | 6 +- polkadot/node/core/pvf/common/Cargo.toml | 2 + polkadot/node/core/pvf/common/src/lib.rs | 30 +++++ polkadot/node/core/pvf/src/host.rs | 26 +++- polkadot/node/core/pvf/src/lib.rs | 20 +++ polkadot/node/core/pvf/src/security.rs | 160 ++++++++--------------- 6 files changed, 132 insertions(+), 112 deletions(-) diff --git a/polkadot/node/core/pvf/README.md b/polkadot/node/core/pvf/README.md index 796e17c05fa..5304b0720b2 100644 --- a/polkadot/node/core/pvf/README.md +++ b/polkadot/node/core/pvf/README.md @@ -13,7 +13,7 @@ See also: Running `cargo test` in the `pvf/` directory will run unit and integration tests. -**Note:** some tests run only under Linux, amd64, and/or with the +**Note:** some tests run only under Linux, x86-64, and/or with the `ci-only-tests` feature enabled. See the general [Testing][testing] instructions for more information on @@ -34,8 +34,8 @@ RUST_LOG=parachain::pvf=trace zombienet --provider=native spawn zombienet_tests/ ## Testing on Linux Some of the PVF functionality, especially related to security, is Linux-only, -and some is amd64-only. If you touch anything security-related, make sure to -test on Linux amd64! If you're on a Mac, you can either run a VM or you can hire +and some is x86-64-only. If you touch anything security-related, make sure to +test on Linux x86-64! If you're on a Mac, you can either run a VM or you can hire a VPS and use the open-source tool [EternalTerminal][et] to connect to it.[^et] [^et]: Unlike ssh, ET preserves your session across disconnects, and unlike diff --git a/polkadot/node/core/pvf/common/Cargo.toml b/polkadot/node/core/pvf/common/Cargo.toml index 583db0b29f4..631d4335b52 100644 --- a/polkadot/node/core/pvf/common/Cargo.toml +++ b/polkadot/node/core/pvf/common/Cargo.toml @@ -35,6 +35,8 @@ sp-tracing = { path = "../../../../../substrate/primitives/tracing" } [target.'cfg(target_os = "linux")'.dependencies] landlock = "0.3.0" nix = { version = "0.27.1", features = ["sched"] } + +[target.'cfg(all(target_os = "linux", target_arch = "x86_64"))'.dependencies] seccompiler = "0.4.0" [dev-dependencies] diff --git a/polkadot/node/core/pvf/common/src/lib.rs b/polkadot/node/core/pvf/common/src/lib.rs index d891c06bf2a..15097dbd3af 100644 --- a/polkadot/node/core/pvf/common/src/lib.rs +++ b/polkadot/node/core/pvf/common/src/lib.rs @@ -86,3 +86,33 @@ pub fn framed_recv_blocking(r: &mut (impl Read + Unpin)) -> io::Result> r.read_exact(&mut buf)?; Ok(buf) } + +#[cfg(all(test, not(feature = "test-utils")))] +mod tests { + use super::*; + + #[test] + fn default_secure_status() { + let status = SecurityStatus::default(); + assert!( + !status.secure_validator_mode, + "secure_validator_mode is false for default security status" + ); + assert!( + !status.can_enable_landlock, + "can_enable_landlock is false for default security status" + ); + assert!( + !status.can_enable_seccomp, + "can_enable_seccomp is false for default security status" + ); + assert!( + !status.can_unshare_user_namespace_and_change_root, + "can_unshare_user_namespace_and_change_root is false for default security status" + ); + assert!( + !status.can_do_secure_clone, + "can_do_secure_clone is false for default security status" + ); + } +} diff --git a/polkadot/node/core/pvf/src/host.rs b/polkadot/node/core/pvf/src/host.rs index e215f11b91d..ae9fdc7d2de 100644 --- a/polkadot/node/core/pvf/src/host.rs +++ b/polkadot/node/core/pvf/src/host.rs @@ -24,7 +24,7 @@ use crate::{ artifacts::{ArtifactId, ArtifactPathId, ArtifactState, Artifacts}, execute::{self, PendingExecutionRequest}, metrics::Metrics, - prepare, security, Priority, SecurityStatus, ValidationError, LOG_TARGET, + prepare, Priority, SecurityStatus, ValidationError, LOG_TARGET, }; use always_assert::never; use futures::{ @@ -225,10 +225,32 @@ pub async fn start( // Run checks for supported security features once per host startup. If some checks fail, warn // if Secure Validator Mode is disabled and return an error otherwise. - let security_status = match security::check_security_status(&config).await { + #[cfg(target_os = "linux")] + let security_status = match crate::security::check_security_status(&config).await { Ok(ok) => ok, Err(err) => return Err(SubsystemError::Context(err)), }; + #[cfg(not(target_os = "linux"))] + let security_status = if config.secure_validator_mode { + gum::error!( + target: LOG_TARGET, + "{}{}{}", + crate::SECURE_MODE_ERROR, + crate::SECURE_LINUX_NOTE, + crate::IGNORE_SECURE_MODE_TIP + ); + return Err(SubsystemError::Context( + "could not enable Secure Validator Mode for non-Linux; check logs".into(), + )); + } else { + gum::warn!( + target: LOG_TARGET, + "{}{}", + crate::SECURE_MODE_WARNING, + crate::SECURE_LINUX_NOTE, + ); + SecurityStatus::default() + }; let (to_host_tx, to_host_rx) = mpsc::channel(HOST_MESSAGE_QUEUE_SIZE); diff --git a/polkadot/node/core/pvf/src/lib.rs b/polkadot/node/core/pvf/src/lib.rs index 92263281eea..462498fa8f6 100644 --- a/polkadot/node/core/pvf/src/lib.rs +++ b/polkadot/node/core/pvf/src/lib.rs @@ -97,6 +97,7 @@ mod host; mod metrics; mod prepare; mod priority; +#[cfg(target_os = "linux")] mod security; mod worker_interface; @@ -135,3 +136,22 @@ pub fn get_worker_version(worker_path: &Path) -> std::io::Result { .trim() .to_string()) } + +// Trying to run securely and some mandatory errors occurred. +pub(crate) const SECURE_MODE_ERROR: &'static str = + "🚨 Your system cannot securely run a validator. \ +\nRunning validation of malicious PVF code has a higher risk of compromising this machine."; +// Some errors occurred when running insecurely, or some optional errors occurred when running +// securely. +pub(crate) const SECURE_MODE_WARNING: &'static str = "🚨 Some security issues have been detected. \ +\nRunning validation of malicious PVF code has a higher risk of compromising this machine."; +// Message to be printed only when running securely and mandatory errors occurred. +pub(crate) const IGNORE_SECURE_MODE_TIP: &'static str = +"\nYou can ignore this error with the `--insecure-validator-i-know-what-i-do` \ +command line argument if you understand and accept the risks of running insecurely. \ +With this flag, security features are enabled on a best-effort basis, but not mandatory. \ +\nMore information: https://wiki.polkadot.network/docs/maintain-guides-secure-validator#secure-validator-mode"; +// Only Linux supports security features +#[cfg(not(target_os = "linux"))] +pub(crate) const SECURE_LINUX_NOTE: &'static str = "\nSecure mode is enabled only for Linux \ +\nand a full secure mode is enabled only for Linux x86-64."; diff --git a/polkadot/node/core/pvf/src/security.rs b/polkadot/node/core/pvf/src/security.rs index f62a232abf2..733ef18bcad 100644 --- a/polkadot/node/core/pvf/src/security.rs +++ b/polkadot/node/core/pvf/src/security.rs @@ -169,20 +169,6 @@ impl fmt::Display for SecureModeError { /// Print an error if Secure Validator Mode and some mandatory errors occurred, warn otherwise. fn print_secure_mode_error_or_warning(security_status: &FullSecurityStatus) { - // Trying to run securely and some mandatory errors occurred. - const SECURE_MODE_ERROR: &'static str = "🚨 Your system cannot securely run a validator. \ - \nRunning validation of malicious PVF code has a higher risk of compromising this machine."; - // Some errors occurred when running insecurely, or some optional errors occurred when running - // securely. - const SECURE_MODE_WARNING: &'static str = "🚨 Some security issues have been detected. \ - \nRunning validation of malicious PVF code has a higher risk of compromising this machine."; - // Message to be printed only when running securely and mandatory errors occurred. - const IGNORE_SECURE_MODE_TIP: &'static str = - "\nYou can ignore this error with the `--insecure-validator-i-know-what-i-do` \ - command line argument if you understand and accept the risks of running insecurely. \ - With this flag, security features are enabled on a best-effort basis, but not mandatory. \ - \nMore information: https://wiki.polkadot.network/docs/maintain-guides-secure-validator#secure-validator-mode"; - let all_errs_allowed = security_status.all_errs_allowed(); let errs_string = security_status.errs_string(); @@ -190,16 +176,16 @@ fn print_secure_mode_error_or_warning(security_status: &FullSecurityStatus) { gum::warn!( target: LOG_TARGET, "{}{}", - SECURE_MODE_WARNING, + crate::SECURE_MODE_WARNING, errs_string, ); } else { gum::error!( target: LOG_TARGET, "{}{}{}", - SECURE_MODE_ERROR, + crate::SECURE_MODE_ERROR, errs_string, - IGNORE_SECURE_MODE_TIP + crate::IGNORE_SECURE_MODE_TIP ); } } @@ -210,29 +196,25 @@ fn print_secure_mode_error_or_warning(security_status: &FullSecurityStatus) { /// to running the check in a worker, we try it... in a worker. The expected return status is 0 on /// success and -1 on failure. async fn check_can_unshare_user_namespace_and_change_root( - #[cfg_attr(not(target_os = "linux"), allow(unused_variables))] prepare_worker_program_path: &Path, - #[cfg_attr(not(target_os = "linux"), allow(unused_variables))] cache_path: &Path, + cache_path: &Path, ) -> SecureModeResult { - cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { - let cache_dir_tempdir = tempfile::Builder::new() - .prefix("check-can-unshare-") - .tempdir_in(cache_path) - .map_err(|err| SecureModeError::CannotUnshareUserNamespaceAndChangeRoot( - format!("could not create a temporary directory in {:?}: {}", cache_path, err) - ))?; - spawn_process_for_security_check( - prepare_worker_program_path, - "--check-can-unshare-user-namespace-and-change-root", - &[cache_dir_tempdir.path()], - ).await.map_err(|err| SecureModeError::CannotUnshareUserNamespaceAndChangeRoot(err)) - } else { - Err(SecureModeError::CannotUnshareUserNamespaceAndChangeRoot( - "only available on Linux".into() + let cache_dir_tempdir = tempfile::Builder::new() + .prefix("check-can-unshare-") + .tempdir_in(cache_path) + .map_err(|err| { + SecureModeError::CannotUnshareUserNamespaceAndChangeRoot(format!( + "could not create a temporary directory in {:?}: {}", + cache_path, err )) - } - } + })?; + spawn_process_for_security_check( + prepare_worker_program_path, + "--check-can-unshare-user-namespace-and-change-root", + &[cache_dir_tempdir.path()], + ) + .await + .map_err(|err| SecureModeError::CannotUnshareUserNamespaceAndChangeRoot(err)) } /// Check if landlock is supported and return an error if not. @@ -240,25 +222,15 @@ async fn check_can_unshare_user_namespace_and_change_root( /// We do this check by spawning a new process and trying to sandbox it. To get as close as possible /// to running the check in a worker, we try it... in a worker. The expected return status is 0 on /// success and -1 on failure. -async fn check_landlock( - #[cfg_attr(not(target_os = "linux"), allow(unused_variables))] - prepare_worker_program_path: &Path, -) -> SecureModeResult { - cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { - let abi = polkadot_node_core_pvf_common::worker::security::landlock::LANDLOCK_ABI as u8; - spawn_process_for_security_check( - prepare_worker_program_path, - "--check-can-enable-landlock", - std::iter::empty::<&str>(), - ).await.map_err(|err| SecureModeError::CannotEnableLandlock { err, abi }) - } else { - Err(SecureModeError::CannotEnableLandlock { - err: "only available on Linux".into(), - abi: 0, - }) - } - } +async fn check_landlock(prepare_worker_program_path: &Path) -> SecureModeResult { + let abi = polkadot_node_core_pvf_common::worker::security::landlock::LANDLOCK_ABI as u8; + spawn_process_for_security_check( + prepare_worker_program_path, + "--check-can-enable-landlock", + std::iter::empty::<&str>(), + ) + .await + .map_err(|err| SecureModeError::CannotEnableLandlock { err, abi }) } /// Check if seccomp is supported and return an error if not. @@ -266,39 +238,23 @@ async fn check_landlock( /// We do this check by spawning a new process and trying to sandbox it. To get as close as possible /// to running the check in a worker, we try it... in a worker. The expected return status is 0 on /// success and -1 on failure. -async fn check_seccomp( - #[cfg_attr(not(all(target_os = "linux", target_arch = "x86_64")), allow(unused_variables))] - prepare_worker_program_path: &Path, -) -> SecureModeResult { - cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { - cfg_if::cfg_if! { - if #[cfg(target_arch = "x86_64")] { - spawn_process_for_security_check( - prepare_worker_program_path, - "--check-can-enable-seccomp", - std::iter::empty::<&str>(), - ).await.map_err(|err| SecureModeError::CannotEnableSeccomp(err)) - } else { - Err(SecureModeError::CannotEnableSeccomp( - "only supported on CPUs from the x86_64 family (usually Intel or AMD)".into() - )) - } - } - } else { - cfg_if::cfg_if! { - if #[cfg(target_arch = "x86_64")] { - Err(SecureModeError::CannotEnableSeccomp( - "only supported on Linux".into() - )) - } else { - Err(SecureModeError::CannotEnableSeccomp( - "only supported on Linux and on CPUs from the x86_64 family (usually Intel or AMD).".into() - )) - } - } - } - } + +#[cfg(target_arch = "x86_64")] +async fn check_seccomp(prepare_worker_program_path: &Path) -> SecureModeResult { + spawn_process_for_security_check( + prepare_worker_program_path, + "--check-can-enable-seccomp", + std::iter::empty::<&str>(), + ) + .await + .map_err(|err| SecureModeError::CannotEnableSeccomp(err)) +} + +#[cfg(not(target_arch = "x86_64"))] +async fn check_seccomp(_: &Path) -> SecureModeResult { + Err(SecureModeError::CannotEnableSeccomp( + "only supported on CPUs from the x86_64 family (usually Intel or AMD)".into(), + )) } /// Check if we can call `clone` with all sandboxing flags, and return an error if not. @@ -306,26 +262,16 @@ async fn check_seccomp( /// We do this check by spawning a new process and trying to sandbox it. To get as close as possible /// to running the check in a worker, we try it... in a worker. The expected return status is 0 on /// success and -1 on failure. -async fn check_can_do_secure_clone( - #[cfg_attr(not(target_os = "linux"), allow(unused_variables))] - prepare_worker_program_path: &Path, -) -> SecureModeResult { - cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { - spawn_process_for_security_check( - prepare_worker_program_path, - "--check-can-do-secure-clone", - std::iter::empty::<&str>(), - ).await.map_err(|err| SecureModeError::CannotDoSecureClone(err)) - } else { - Err(SecureModeError::CannotDoSecureClone( - "only available on Linux".into() - )) - } - } +async fn check_can_do_secure_clone(prepare_worker_program_path: &Path) -> SecureModeResult { + spawn_process_for_security_check( + prepare_worker_program_path, + "--check-can-do-secure-clone", + std::iter::empty::<&str>(), + ) + .await + .map_err(|err| SecureModeError::CannotDoSecureClone(err)) } -#[cfg(target_os = "linux")] async fn spawn_process_for_security_check( prepare_worker_program_path: &Path, check_arg: &'static str, -- GitLab From cbd684673b2a30e55ac6b82c21057490b3854253 Mon Sep 17 00:00:00 2001 From: Andrei Eres Date: Mon, 12 Feb 2024 10:17:29 +0100 Subject: [PATCH 038/133] subsystem-bench: polish imports (#3262) --- .../subsystem-bench/src/approval/helpers.rs | 6 +- .../src/approval/message_generator.rs | 56 ++++++------- .../src/approval/mock_chain_selection.rs | 4 +- .../node/subsystem-bench/src/approval/mod.rs | 32 ++++---- .../src/approval/test_message.rs | 8 +- .../src/availability/av_store_helpers.rs | 11 +-- .../subsystem-bench/src/availability/mod.rs | 81 +++++++++---------- .../subsystem-bench/src/core/configuration.rs | 15 ++-- .../node/subsystem-bench/src/core/display.rs | 5 +- .../subsystem-bench/src/core/environment.rs | 14 ++-- .../subsystem-bench/src/core/mock/av_store.rs | 18 ++--- .../src/core/mock/chain_api.rs | 8 +- .../subsystem-bench/src/core/mock/dummy.rs | 3 +- .../node/subsystem-bench/src/core/mock/mod.rs | 10 +-- .../src/core/mock/network_bridge.rs | 23 +++--- .../src/core/mock/runtime_api.rs | 18 ++--- polkadot/node/subsystem-bench/src/core/mod.rs | 15 ++-- .../node/subsystem-bench/src/core/network.rs | 45 +++++------ .../subsystem-bench/src/subsystem-bench.rs | 38 ++++----- 19 files changed, 190 insertions(+), 220 deletions(-) diff --git a/polkadot/node/subsystem-bench/src/approval/helpers.rs b/polkadot/node/subsystem-bench/src/approval/helpers.rs index 7cbe3ba7594..623d91848f5 100644 --- a/polkadot/node/subsystem-bench/src/approval/helpers.rs +++ b/polkadot/node/subsystem-bench/src/approval/helpers.rs @@ -11,6 +11,9 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + use crate::core::configuration::TestAuthorities; use itertools::Itertools; use polkadot_node_core_approval_voting::time::{Clock, SystemClock, Tick}; @@ -39,9 +42,6 @@ use sp_keyring::sr25519::Keyring as Sr25519Keyring; use sp_runtime::{Digest, DigestItem}; use std::sync::{atomic::AtomicU64, Arc}; -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . - /// A fake system clock used for driving the approval voting and make /// it process blocks, assignments and approvals from the past. #[derive(Clone)] diff --git a/polkadot/node/subsystem-bench/src/approval/message_generator.rs b/polkadot/node/subsystem-bench/src/approval/message_generator.rs index 0d308a2fecc..a7103401324 100644 --- a/polkadot/node/subsystem-bench/src/approval/message_generator.rs +++ b/polkadot/node/subsystem-bench/src/approval/message_generator.rs @@ -14,15 +14,20 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use std::{ - cmp::max, - collections::{BTreeMap, HashSet}, - fs, - io::Write, - path::{Path, PathBuf}, - time::Duration, +use crate::{ + approval::{ + helpers::{generate_babe_epoch, generate_topology}, + test_message::{MessagesBundle, TestMessageInfo}, + ApprovalTestState, BlockTestData, GeneratedState, BUFFER_FOR_GENERATION_MILLIS, LOG_TARGET, + SLOT_DURATION_MILLIS, + }, + core::{ + configuration::{TestAuthorities, TestConfiguration}, + mock::runtime_api::session_info_for_peers, + NODE_UNDER_TEST, + }, + ApprovalsOptions, TestObjective, }; - use futures::SinkExt; use itertools::Itertools; use parity_scale_codec::Encode; @@ -30,8 +35,9 @@ use polkadot_node_core_approval_voting::{ criteria::{compute_assignments, Config}, time::tranche_to_tick, }; -use polkadot_node_network_protocol::grid_topology::{ - GridNeighbors, RandomRouting, RequiredRouting, SessionGridTopology, +use polkadot_node_network_protocol::{ + grid_topology::{GridNeighbors, RandomRouting, RequiredRouting, SessionGridTopology}, + v3 as protocol_v3, }; use polkadot_node_primitives::approval::{ self, @@ -39,38 +45,28 @@ use polkadot_node_primitives::approval::{ }; use polkadot_primitives::{ vstaging::ApprovalVoteMultipleCandidates, CandidateEvent, CandidateHash, CandidateIndex, - CoreIndex, SessionInfo, Slot, ValidatorId, ValidatorIndex, ASSIGNMENT_KEY_TYPE_ID, + CoreIndex, Hash, SessionInfo, Slot, ValidatorId, ValidatorIndex, ASSIGNMENT_KEY_TYPE_ID, }; use rand::{seq::SliceRandom, RngCore, SeedableRng}; use rand_chacha::ChaCha20Rng; use rand_distr::{Distribution, Normal}; use sc_keystore::LocalKeystore; use sc_network::PeerId; +use sc_service::SpawnTaskHandle; use sha1::Digest; use sp_application_crypto::AppCrypto; use sp_consensus_babe::SlotDuration; use sp_keystore::Keystore; use sp_timestamp::Timestamp; - -use super::{ - test_message::{MessagesBundle, TestMessageInfo}, - ApprovalTestState, ApprovalsOptions, BlockTestData, -}; -use crate::{ - approval::{ - helpers::{generate_babe_epoch, generate_topology}, - GeneratedState, BUFFER_FOR_GENERATION_MILLIS, LOG_TARGET, SLOT_DURATION_MILLIS, - }, - core::{ - configuration::{TestAuthorities, TestConfiguration}, - mock::session_info_for_peers, - NODE_UNDER_TEST, - }, - TestObjective, +use std::{ + cmp::max, + collections::{BTreeMap, HashSet}, + fs, + io::Write, + path::{Path, PathBuf}, + time::Duration, }; -use polkadot_node_network_protocol::v3 as protocol_v3; -use polkadot_primitives::Hash; -use sc_service::SpawnTaskHandle; + /// A generator of messages coming from a given Peer/Validator pub struct PeerMessagesGenerator { /// The grid neighbors of the node under test. diff --git a/polkadot/node/subsystem-bench/src/approval/mock_chain_selection.rs b/polkadot/node/subsystem-bench/src/approval/mock_chain_selection.rs index ab23a8ced48..77ba80d4b2b 100644 --- a/polkadot/node/subsystem-bench/src/approval/mock_chain_selection.rs +++ b/polkadot/node/subsystem-bench/src/approval/mock_chain_selection.rs @@ -14,9 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use crate::approval::{LOG_TARGET, SLOT_DURATION_MILLIS}; - -use super::{ApprovalTestState, PastSystemClock}; +use crate::approval::{ApprovalTestState, PastSystemClock, LOG_TARGET, SLOT_DURATION_MILLIS}; use futures::FutureExt; use polkadot_node_core_approval_voting::time::{slot_number_to_tick, Clock, TICK_DURATION_MILLIS}; use polkadot_node_subsystem::{overseer, SpawnedSubsystem, SubsystemError}; diff --git a/polkadot/node/subsystem-bench/src/approval/mod.rs b/polkadot/node/subsystem-bench/src/approval/mod.rs index b1ab5363870..f07912de188 100644 --- a/polkadot/node/subsystem-bench/src/approval/mod.rs +++ b/polkadot/node/subsystem-bench/src/approval/mod.rs @@ -14,18 +14,15 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use self::{ - helpers::{make_candidates, make_header}, - test_message::{MessagesBundle, TestMessageInfo}, -}; use crate::{ approval::{ helpers::{ generate_babe_epoch, generate_new_session_topology, generate_peer_view_change_for, - PastSystemClock, + make_header, PastSystemClock, }, message_generator::PeerMessagesGenerator, mock_chain_selection::MockChainSelection, + test_message::{MessagesBundle, TestMessageInfo}, }, core::{ configuration::TestAuthorities, @@ -33,9 +30,11 @@ use crate::{ BenchmarkUsage, TestEnvironment, TestEnvironmentDependencies, MAX_TIME_OF_FLIGHT, }, mock::{ + chain_api::{ChainApiState, MockChainApi}, dummy_builder, network_bridge::{MockNetworkBridgeRx, MockNetworkBridgeTx}, - AlwaysSupportsParachains, ChainApiState, MockChainApi, MockRuntimeApi, TestSyncOracle, + runtime_api::MockRuntimeApi, + AlwaysSupportsParachains, TestSyncOracle, }, network::{ new_network, HandleNetworkMessage, NetworkEmulatorHandle, NetworkInterface, @@ -92,8 +91,7 @@ mod message_generator; mod mock_chain_selection; mod test_message; -pub const LOG_TARGET: &str = "subsystem-bench::approval"; -const DATA_COL: u32 = 0; +pub(crate) const LOG_TARGET: &str = "subsystem-bench::approval"; pub(crate) const NUM_COLUMNS: u32 = 1; pub(crate) const SLOT_DURATION_MILLIS: u64 = 6000; pub(crate) const TEST_CONFIG: ApprovalVotingConfig = ApprovalVotingConfig { @@ -101,6 +99,8 @@ pub(crate) const TEST_CONFIG: ApprovalVotingConfig = ApprovalVotingConfig { slot_duration_millis: SLOT_DURATION_MILLIS, }; +const DATA_COL: u32 = 0; + /// Start generating messages for a slot into the future, so that the /// generation nevers falls behind the current slot. const BUFFER_FOR_GENERATION_MILLIS: u64 = 30_000; @@ -235,7 +235,7 @@ impl CandidateTestData { #[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] struct GeneratedState { /// All assignments and approvals - all_messages: Option>, + all_messages: Option>, /// The first slot in the test. initial_slot: Slot, } @@ -368,7 +368,7 @@ impl ApprovalTestState { block_number: block_number as BlockNumber, hash: block_hash, header, - candidates: make_candidates( + candidates: helpers::make_candidates( block_hash, block_number as BlockNumber, configuration.n_cores as u32, @@ -505,12 +505,16 @@ struct PeerMessageProducer { impl PeerMessageProducer { /// Generates messages by spawning a blocking task in the background which begins creating /// the assignments/approvals and peer view changes at the begining of each block. - fn produce_messages(mut self, env: &TestEnvironment, all_messages: Vec) { + fn produce_messages( + mut self, + env: &TestEnvironment, + all_messages: Vec, + ) { env.spawn_blocking("produce-messages", async move { let mut initialized_blocks = HashSet::new(); let mut per_candidate_data: HashMap<(Hash, CandidateIndex), CandidateTestData> = self.initialize_candidates_test_data(); - let mut skipped_messages: Vec = Vec::new(); + let mut skipped_messages: Vec = Vec::new(); let mut re_process_skipped = false; let system_clock = @@ -607,9 +611,9 @@ impl PeerMessageProducer { // send the message in our simulation. pub fn process_message( &mut self, - bundle: MessagesBundle, + bundle: test_message::MessagesBundle, per_candidate_data: &mut HashMap<(Hash, CandidateIndex), CandidateTestData>, - skipped_messages: &mut Vec, + skipped_messages: &mut Vec, ) -> bool { let mut reprocess_skipped = false; let block_info = self diff --git a/polkadot/node/subsystem-bench/src/approval/test_message.rs b/polkadot/node/subsystem-bench/src/approval/test_message.rs index ea578776f26..8aaabc3426c 100644 --- a/polkadot/node/subsystem-bench/src/approval/test_message.rs +++ b/polkadot/node/subsystem-bench/src/approval/test_message.rs @@ -14,12 +14,14 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use super::{ApprovalsOptions, BlockTestData, CandidateTestData}; -use crate::core::configuration::TestAuthorities; +use crate::{ + approval::{BlockTestData, CandidateTestData}, + core::configuration::TestAuthorities, + ApprovalsOptions, +}; use itertools::Itertools; use parity_scale_codec::{Decode, Encode}; use polkadot_node_network_protocol::v3 as protocol_v3; - use polkadot_primitives::{CandidateIndex, Hash, ValidatorIndex}; use sc_network::PeerId; use std::collections::{HashMap, HashSet}; diff --git a/polkadot/node/subsystem-bench/src/availability/av_store_helpers.rs b/polkadot/node/subsystem-bench/src/availability/av_store_helpers.rs index e6827f1d8ae..261dbd0376c 100644 --- a/polkadot/node/subsystem-bench/src/availability/av_store_helpers.rs +++ b/polkadot/node/subsystem-bench/src/availability/av_store_helpers.rs @@ -14,16 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use crate::core::mock::TestSyncOracle; - -use super::*; - +use crate::core::{environment::TestEnvironmentDependencies, mock::TestSyncOracle}; +use polkadot_node_core_av_store::{AvailabilityStoreSubsystem, Config}; use polkadot_node_metrics::metrics::Metrics; - -use polkadot_node_core_av_store::Config; use polkadot_node_subsystem_util::database::Database; - -use polkadot_node_core_av_store::AvailabilityStoreSubsystem; +use std::sync::Arc; mod columns { pub const DATA: u32 = 0; diff --git a/polkadot/node/subsystem-bench/src/availability/mod.rs b/polkadot/node/subsystem-bench/src/availability/mod.rs index 8ed39525a1e..ad9a17ff8f4 100644 --- a/polkadot/node/subsystem-bench/src/availability/mod.rs +++ b/polkadot/node/subsystem-bench/src/availability/mod.rs @@ -13,74 +13,73 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . + use crate::{ - core::{environment::BenchmarkUsage, mock::ChainApiState}, - TestEnvironment, + core::{ + configuration::TestConfiguration, + environment::{BenchmarkUsage, TestEnvironmentDependencies}, + mock::{ + av_store, + av_store::MockAvailabilityStore, + chain_api::{ChainApiState, MockChainApi}, + dummy_builder, + network_bridge::{self, MockNetworkBridgeRx, MockNetworkBridgeTx}, + runtime_api, + runtime_api::MockRuntimeApi, + AlwaysSupportsParachains, + }, + network::new_network, + }, + TestEnvironment, TestObjective, GENESIS_HASH, }; use av_store::NetworkAvailabilityState; +use av_store_helpers::new_av_store; use bitvec::bitvec; use colored::Colorize; +use futures::{channel::oneshot, stream::FuturesUnordered, StreamExt}; use itertools::Itertools; +use parity_scale_codec::Encode; use polkadot_availability_bitfield_distribution::BitfieldDistribution; -use polkadot_node_core_av_store::AvailabilityStoreSubsystem; -use polkadot_node_subsystem::{Overseer, OverseerConnector, SpawnGlue}; -use polkadot_node_subsystem_types::{ - messages::{AvailabilityStoreMessage, NetworkBridgeEvent}, - Span, -}; -use polkadot_overseer::{metrics::Metrics as OverseerMetrics, Handle as OverseerHandle}; -use sc_network::{request_responses::ProtocolConfig, PeerId}; -use sp_core::H256; -use std::{collections::HashMap, iter::Cycle, ops::Sub, sync::Arc, time::Instant}; - -use av_store_helpers::new_av_store; -use futures::{channel::oneshot, stream::FuturesUnordered, StreamExt}; use polkadot_availability_distribution::{ AvailabilityDistributionSubsystem, IncomingRequestReceivers, }; -use polkadot_node_metrics::metrics::Metrics; - use polkadot_availability_recovery::AvailabilityRecoverySubsystem; -use polkadot_node_primitives::{AvailableData, ErasureChunk}; - -use crate::GENESIS_HASH; -use parity_scale_codec::Encode; +use polkadot_node_core_av_store::AvailabilityStoreSubsystem; +use polkadot_node_metrics::metrics::Metrics; use polkadot_node_network_protocol::{ request_response::{v1::ChunkFetchingRequest, IncomingRequest, ReqProtocolNames}, OurView, Versioned, VersionedValidationProtocol, }; -use sc_network::request_responses::IncomingRequest as RawIncomingRequest; - -use polkadot_node_primitives::{BlockData, PoV}; -use polkadot_node_subsystem::messages::{AllMessages, AvailabilityRecoveryMessage}; - -use crate::core::{ - environment::TestEnvironmentDependencies, - mock::{ - av_store, - network_bridge::{self, MockNetworkBridgeRx, MockNetworkBridgeTx}, - runtime_api, MockAvailabilityStore, MockChainApi, MockRuntimeApi, - }, +use polkadot_node_primitives::{AvailableData, BlockData, ErasureChunk, PoV}; +use polkadot_node_subsystem::{ + messages::{AllMessages, AvailabilityRecoveryMessage}, + Overseer, OverseerConnector, SpawnGlue, }; - -use super::core::{configuration::TestConfiguration, mock::dummy_builder, network::*}; - -const LOG_TARGET: &str = "subsystem-bench::availability"; - -use super::{core::mock::AlwaysSupportsParachains, TestObjective}; use polkadot_node_subsystem_test_helpers::{ derive_erasure_chunks_with_proofs_and_root, mock::new_block_import_info, }; +use polkadot_node_subsystem_types::{ + messages::{AvailabilityStoreMessage, NetworkBridgeEvent}, + Span, +}; +use polkadot_overseer::{metrics::Metrics as OverseerMetrics, Handle as OverseerHandle}; use polkadot_primitives::{ AvailabilityBitfield, BlockNumber, CandidateHash, CandidateReceipt, GroupIndex, Hash, HeadData, Header, PersistedValidationData, Signed, SigningContext, ValidatorIndex, }; use polkadot_primitives_test_helpers::{dummy_candidate_receipt, dummy_hash}; +use sc_network::{ + request_responses::{IncomingRequest as RawIncomingRequest, ProtocolConfig}, + PeerId, +}; use sc_service::SpawnTaskHandle; +use sp_core::H256; +use std::{collections::HashMap, iter::Cycle, ops::Sub, sync::Arc, time::Instant}; mod av_store_helpers; -mod cli; -pub use cli::{DataAvailabilityReadOptions, NetworkEmulation}; +pub(crate) mod cli; + +const LOG_TARGET: &str = "subsystem-bench::availability"; fn build_overseer_for_availability_read( spawn_task_handle: SpawnTaskHandle, diff --git a/polkadot/node/subsystem-bench/src/core/configuration.rs b/polkadot/node/subsystem-bench/src/core/configuration.rs index d9eec43873a..00be2a86b17 100644 --- a/polkadot/node/subsystem-bench/src/core/configuration.rs +++ b/polkadot/node/subsystem-bench/src/core/configuration.rs @@ -13,21 +13,18 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -// + //! Test configuration definition and helpers. -use super::*; -use itertools::Itertools; -use keyring::Keyring; -use sc_network::PeerId; -use sp_consensus_babe::AuthorityId; -use std::{collections::HashMap, path::Path}; -use crate::TestObjective; +use crate::{core::keyring::Keyring, TestObjective}; +use itertools::Itertools; use polkadot_primitives::{AssignmentId, AuthorityDiscoveryId, ValidatorId}; use rand::thread_rng; use rand_distr::{Distribution, Normal, Uniform}; - +use sc_network::PeerId; use serde::{Deserialize, Serialize}; +use sp_consensus_babe::AuthorityId; +use std::{collections::HashMap, path::Path}; pub fn random_pov_size(min_pov_size: usize, max_pov_size: usize) -> usize { random_uniform_sample(min_pov_size, max_pov_size) diff --git a/polkadot/node/subsystem-bench/src/core/display.rs b/polkadot/node/subsystem-bench/src/core/display.rs index b130afdcfad..13a349382e2 100644 --- a/polkadot/node/subsystem-bench/src/core/display.rs +++ b/polkadot/node/subsystem-bench/src/core/display.rs @@ -13,12 +13,13 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -// + //! Display implementations and helper methods for parsing prometheus metrics //! to a format that can be displayed in the CLI. //! //! Currently histogram buckets are skipped. -use super::{configuration::TestConfiguration, LOG_TARGET}; + +use crate::{TestConfiguration, LOG_TARGET}; use colored::Colorize; use prometheus::{ proto::{MetricFamily, MetricType}, diff --git a/polkadot/node/subsystem-bench/src/core/environment.rs b/polkadot/node/subsystem-bench/src/core/environment.rs index cffda38f211..ca4c41cf45f 100644 --- a/polkadot/node/subsystem-bench/src/core/environment.rs +++ b/polkadot/node/subsystem-bench/src/core/environment.rs @@ -13,29 +13,31 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . + //! Test environment implementation + use crate::{ - core::{mock::AlwaysSupportsParachains, network::NetworkEmulatorHandle}, + core::{ + configuration::TestAuthorities, mock::AlwaysSupportsParachains, + network::NetworkEmulatorHandle, + }, TestConfiguration, }; use colored::Colorize; use core::time::Duration; use futures::{Future, FutureExt}; -use polkadot_overseer::{BlockInfo, Handle as OverseerHandle}; -use serde::{Deserialize, Serialize}; - use polkadot_node_subsystem::{messages::AllMessages, Overseer, SpawnGlue, TimeoutExt}; use polkadot_node_subsystem_types::Hash; use polkadot_node_subsystem_util::metrics::prometheus::{ self, Gauge, Histogram, PrometheusError, Registry, U64, }; - +use polkadot_overseer::{BlockInfo, Handle as OverseerHandle}; use sc_service::{SpawnTaskHandle, TaskManager}; +use serde::{Deserialize, Serialize}; use std::net::{Ipv4Addr, SocketAddr}; use tokio::runtime::Handle; const LOG_TARGET: &str = "subsystem-bench::environment"; -use super::configuration::TestAuthorities; /// Test environment/configuration metrics #[derive(Clone)] diff --git a/polkadot/node/subsystem-bench/src/core/mock/av_store.rs b/polkadot/node/subsystem-bench/src/core/mock/av_store.rs index 76609ab5dba..0a7725c91e0 100644 --- a/polkadot/node/subsystem-bench/src/core/mock/av_store.rs +++ b/polkadot/node/subsystem-bench/src/core/mock/av_store.rs @@ -13,30 +13,24 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! + //! A generic av store subsystem mockup suitable to be used in benchmarks. +use crate::core::network::{HandleNetworkMessage, NetworkMessage}; +use futures::{channel::oneshot, FutureExt}; use parity_scale_codec::Encode; use polkadot_node_network_protocol::request_response::{ v1::{AvailableDataFetchingResponse, ChunkFetchingResponse, ChunkResponse}, Requests, }; -use polkadot_primitives::CandidateHash; -use sc_network::ProtocolName; - -use std::collections::HashMap; - -use futures::{channel::oneshot, FutureExt}; - use polkadot_node_primitives::{AvailableData, ErasureChunk}; - use polkadot_node_subsystem::{ messages::AvailabilityStoreMessage, overseer, SpawnedSubsystem, SubsystemError, }; - use polkadot_node_subsystem_types::OverseerSignal; - -use crate::core::network::{HandleNetworkMessage, NetworkMessage}; +use polkadot_primitives::CandidateHash; +use sc_network::ProtocolName; +use std::collections::HashMap; pub struct AvailabilityStoreState { candidate_hashes: HashMap, diff --git a/polkadot/node/subsystem-bench/src/core/mock/chain_api.rs b/polkadot/node/subsystem-bench/src/core/mock/chain_api.rs index 7a5ee80de80..bee15c3cefd 100644 --- a/polkadot/node/subsystem-bench/src/core/mock/chain_api.rs +++ b/polkadot/node/subsystem-bench/src/core/mock/chain_api.rs @@ -13,21 +13,19 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! + //! A generic runtime api subsystem mockup suitable to be used in benchmarks. +use futures::FutureExt; use itertools::Itertools; -use polkadot_primitives::Header; - use polkadot_node_subsystem::{ messages::ChainApiMessage, overseer, SpawnedSubsystem, SubsystemError, }; use polkadot_node_subsystem_types::OverseerSignal; +use polkadot_primitives::Header; use sp_core::H256; use std::collections::HashMap; -use futures::FutureExt; - const LOG_TARGET: &str = "subsystem-bench::chain-api-mock"; /// State used to respond to `BlockHeader` requests. diff --git a/polkadot/node/subsystem-bench/src/core/mock/dummy.rs b/polkadot/node/subsystem-bench/src/core/mock/dummy.rs index a0a908750c5..8783b35f1c0 100644 --- a/polkadot/node/subsystem-bench/src/core/mock/dummy.rs +++ b/polkadot/node/subsystem-bench/src/core/mock/dummy.rs @@ -13,10 +13,11 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . + //! Dummy subsystem mocks. -use paste::paste; use futures::FutureExt; +use paste::paste; use polkadot_node_subsystem::{overseer, SpawnedSubsystem, SubsystemError}; use std::time::Duration; use tokio::time::sleep; diff --git a/polkadot/node/subsystem-bench/src/core/mock/mod.rs b/polkadot/node/subsystem-bench/src/core/mock/mod.rs index e766b07e8b1..46fdeb196c0 100644 --- a/polkadot/node/subsystem-bench/src/core/mock/mod.rs +++ b/polkadot/node/subsystem-bench/src/core/mock/mod.rs @@ -16,6 +16,7 @@ use polkadot_node_subsystem::HeadSupportsParachains; use polkadot_node_subsystem_types::Hash; +use sp_consensus::SyncOracle; pub mod av_store; pub mod chain_api; @@ -23,11 +24,8 @@ pub mod dummy; pub mod network_bridge; pub mod runtime_api; -pub use av_store::*; -pub use chain_api::*; -pub use runtime_api::*; - pub struct AlwaysSupportsParachains {} + #[async_trait::async_trait] impl HeadSupportsParachains for AlwaysSupportsParachains { async fn head_supports_parachains(&self, _head: &Hash) -> bool { @@ -38,7 +36,7 @@ impl HeadSupportsParachains for AlwaysSupportsParachains { // An orchestra with dummy subsystems macro_rules! dummy_builder { ($spawn_task_handle: ident, $metrics: ident) => {{ - use super::core::mock::dummy::*; + use $crate::core::mock::dummy::*; // Initialize a mock overseer. // All subsystem except approval_voting and approval_distribution are mock subsystems. @@ -74,9 +72,7 @@ macro_rules! dummy_builder { .spawner(SpawnGlue($spawn_task_handle)) }}; } - pub(crate) use dummy_builder; -use sp_consensus::SyncOracle; #[derive(Clone)] pub struct TestSyncOracle {} diff --git a/polkadot/node/subsystem-bench/src/core/mock/network_bridge.rs b/polkadot/node/subsystem-bench/src/core/mock/network_bridge.rs index a171deb2e71..4682c7ec79a 100644 --- a/polkadot/node/subsystem-bench/src/core/mock/network_bridge.rs +++ b/polkadot/node/subsystem-bench/src/core/mock/network_bridge.rs @@ -13,28 +13,25 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! + //! Mocked `network-bridge` subsystems that uses a `NetworkInterface` to access //! the emulated network. + +use crate::core::{ + configuration::TestAuthorities, + network::{NetworkEmulatorHandle, NetworkInterfaceReceiver, NetworkMessage, RequestExt}, +}; use futures::{channel::mpsc::UnboundedSender, FutureExt, StreamExt}; +use polkadot_node_network_protocol::Versioned; +use polkadot_node_subsystem::{ + messages::NetworkBridgeTxMessage, overseer, SpawnedSubsystem, SubsystemError, +}; use polkadot_node_subsystem_types::{ messages::{ApprovalDistributionMessage, BitfieldDistributionMessage, NetworkBridgeEvent}, OverseerSignal, }; - use sc_network::{request_responses::ProtocolConfig, RequestFailure}; -use polkadot_node_subsystem::{ - messages::NetworkBridgeTxMessage, overseer, SpawnedSubsystem, SubsystemError, -}; - -use polkadot_node_network_protocol::Versioned; - -use crate::core::{ - configuration::TestAuthorities, - network::{NetworkEmulatorHandle, NetworkInterfaceReceiver, NetworkMessage, RequestExt}, -}; - const LOG_TARGET: &str = "subsystem-bench::network-bridge"; const CHUNK_REQ_PROTOCOL_NAME_V1: &str = "/ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff/req_chunk/1"; diff --git a/polkadot/node/subsystem-bench/src/core/mock/runtime_api.rs b/polkadot/node/subsystem-bench/src/core/mock/runtime_api.rs index ca6896dbb29..0dd76efcbaf 100644 --- a/polkadot/node/subsystem-bench/src/core/mock/runtime_api.rs +++ b/polkadot/node/subsystem-bench/src/core/mock/runtime_api.rs @@ -13,28 +13,26 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! -//! A generic runtime api subsystem mockup suitable to be used in benchmarks. -use itertools::Itertools; -use polkadot_primitives::{ - vstaging::NodeFeatures, CandidateEvent, CandidateReceipt, CoreState, GroupIndex, IndexedVec, - OccupiedCore, SessionIndex, SessionInfo, ValidatorIndex, -}; +//! A generic runtime api subsystem mockup suitable to be used in benchmarks. +use crate::core::configuration::{TestAuthorities, TestConfiguration}; use bitvec::prelude::BitVec; +use futures::FutureExt; +use itertools::Itertools; use polkadot_node_subsystem::{ messages::{RuntimeApiMessage, RuntimeApiRequest}, overseer, SpawnedSubsystem, SubsystemError, }; use polkadot_node_subsystem_types::OverseerSignal; +use polkadot_primitives::{ + vstaging::NodeFeatures, CandidateEvent, CandidateReceipt, CoreState, GroupIndex, IndexedVec, + OccupiedCore, SessionIndex, SessionInfo, ValidatorIndex, +}; use sp_consensus_babe::Epoch as BabeEpoch; use sp_core::H256; use std::collections::HashMap; -use crate::core::configuration::{TestAuthorities, TestConfiguration}; -use futures::FutureExt; - const LOG_TARGET: &str = "subsystem-bench::runtime-api-mock"; /// Minimal state to answer requests. diff --git a/polkadot/node/subsystem-bench/src/core/mod.rs b/polkadot/node/subsystem-bench/src/core/mod.rs index 507dd1aa83f..764184c5b37 100644 --- a/polkadot/node/subsystem-bench/src/core/mod.rs +++ b/polkadot/node/subsystem-bench/src/core/mod.rs @@ -14,13 +14,12 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -const LOG_TARGET: &str = "subsystem-bench::core"; // The validator index that represent the node that is under test. -pub const NODE_UNDER_TEST: u32 = 0; +pub(crate) const NODE_UNDER_TEST: u32 = 0; -pub mod configuration; -pub mod display; -pub mod environment; -pub mod keyring; -pub mod mock; -pub mod network; +pub(crate) mod configuration; +pub(crate) mod display; +pub(crate) mod environment; +pub(crate) mod keyring; +pub(crate) mod mock; +pub(crate) mod network; diff --git a/polkadot/node/subsystem-bench/src/core/network.rs b/polkadot/node/subsystem-bench/src/core/network.rs index 8e7c2814063..e9124726d7c 100644 --- a/polkadot/node/subsystem-bench/src/core/network.rs +++ b/polkadot/node/subsystem-bench/src/core/network.rs @@ -12,10 +12,10 @@ // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! + //! Implements network emulation and interfaces to control and specialize //! network peer behaviour. -// + // [TestEnvironment] // [NetworkEmulatorHandle] // || @@ -33,20 +33,22 @@ // | // Subsystems under test -use crate::core::configuration::random_latency; - -use super::{ - configuration::{TestAuthorities, TestConfiguration}, +use crate::core::{ + configuration::{random_latency, TestAuthorities, TestConfiguration}, environment::TestEnvironmentDependencies, - *, + NODE_UNDER_TEST, }; use colored::Colorize; use futures::{ - channel::{mpsc, oneshot}, + channel::{ + mpsc, + mpsc::{UnboundedReceiver, UnboundedSender}, + oneshot, + }, lock::Mutex, stream::FuturesUnordered, + Future, FutureExt, StreamExt, }; - use itertools::Itertools; use net_protocol::{ peer_set::{ProtocolVersion, ValidationVersion}, @@ -54,7 +56,11 @@ use net_protocol::{ ObservedRole, VersionedValidationProtocol, }; use parity_scale_codec::Encode; +use polkadot_node_network_protocol::{self as net_protocol, Versioned}; use polkadot_node_subsystem_types::messages::{ApprovalDistributionMessage, NetworkBridgeEvent}; +use polkadot_node_subsystem_util::metrics::prometheus::{ + self, CounterVec, Opts, PrometheusError, Registry, +}; use polkadot_overseer::AllMessages; use polkadot_primitives::AuthorityDiscoveryId; use prometheus_endpoint::U64; @@ -67,14 +73,12 @@ use sc_service::SpawnTaskHandle; use std::{ collections::HashMap, sync::Arc, + task::Poll, time::{Duration, Instant}, }; -use polkadot_node_network_protocol::{self as net_protocol, Versioned}; - -use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender}; +const LOG_TARGET: &str = "subsystem-bench::network"; -use futures::{Future, FutureExt, StreamExt}; // An emulated node egress traffic rate_limiter. #[derive(Debug)] pub struct RateLimit { @@ -202,8 +206,6 @@ struct ProxiedResponse { pub result: Result, RequestFailure>, } -use std::task::Poll; - impl Future for ProxiedRequest { // The sender and result. type Output = ProxiedResponse; @@ -265,9 +267,9 @@ impl NetworkInterface { task_rx_limiter.lock().await.reap(size).await; rx_network.inc_received(size); - // To be able to apply the configured bandwidth limits for responses being sent - // over channels, we need to implement a simple proxy that allows this loop - // to receive the response and enforce the configured bandwidth before + // To be able to apply the configured bandwidth limits for responses being sent + // over channels, we need to implement a simple proxy that allows this loop + // to receive the response and enforce the configured bandwidth before // sending it to the original recipient. if let NetworkMessage::RequestFromPeer(request) = peer_message { let (response_sender, response_receiver) = oneshot::channel(); @@ -914,10 +916,6 @@ impl NetworkEmulatorHandle { } } -use polkadot_node_subsystem_util::metrics::prometheus::{ - self, CounterVec, Opts, PrometheusError, Registry, -}; - /// Emulated network metrics. #[derive(Clone)] pub(crate) struct Metrics { @@ -1036,9 +1034,8 @@ impl RequestExt for Requests { #[cfg(test)] mod tests { - use std::time::Instant; - use super::RateLimit; + use std::time::Instant; #[tokio::test] async fn test_expected_rate() { diff --git a/polkadot/node/subsystem-bench/src/subsystem-bench.rs b/polkadot/node/subsystem-bench/src/subsystem-bench.rs index a5dfbc52d60..0803f175474 100644 --- a/polkadot/node/subsystem-bench/src/subsystem-bench.rs +++ b/polkadot/node/subsystem-bench/src/subsystem-bench.rs @@ -16,36 +16,33 @@ //! A tool for running subsystem benchmark tests designed for development and //! CI regression testing. -use clap::Parser; -use serde::{Deserialize, Serialize}; - -use colored::Colorize; +use approval::{bench_approvals, ApprovalsOptions}; +use availability::{ + cli::{DataAvailabilityReadOptions, NetworkEmulation}, + prepare_test, TestState, +}; +use clap::Parser; +use clap_num::number_range; use color_eyre::eyre; +use colored::Colorize; +use core::{ + configuration::TestConfiguration, + display::display_configuration, + environment::{TestEnvironment, GENESIS_HASH}, +}; use pyroscope::PyroscopeAgent; use pyroscope_pprofrs::{pprof_backend, PprofConfig}; - +use serde::{Deserialize, Serialize}; use std::path::Path; -pub(crate) mod approval; -pub(crate) mod availability; -pub(crate) mod core; +mod approval; +mod availability; +mod core; mod valgrind; const LOG_TARGET: &str = "subsystem-bench"; -use availability::{prepare_test, NetworkEmulation, TestState}; - -use approval::{bench_approvals, ApprovalsOptions}; -use availability::DataAvailabilityReadOptions; -use core::{ - configuration::TestConfiguration, - display::display_configuration, - environment::{TestEnvironment, GENESIS_HASH}, -}; - -use clap_num::number_range; - fn le_100(s: &str) -> Result { number_range(s, 0, 100) } @@ -213,7 +210,6 @@ fn main() -> eyre::Result<()> { .filter(Some("polkadot_overseer"), log::LevelFilter::Error) .filter(None, log::LevelFilter::Info) .format_timestamp_millis() - // .filter(None, log::LevelFilter::Trace) .try_init() .unwrap(); -- GitLab From 4f13d5b790d847d7fca62a88a024080d515aba6e Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Mon, 12 Feb 2024 11:53:00 +0200 Subject: [PATCH 039/133] transaction-pool: Improve transaction status documentation and add helpers (#3215) This PR improves the transaction status documentation. - Added doc references for describing the main states - Extra comment wrt pool ready / future queues - `FinalityTimeout` no longer describes a lagging finality gadget, it signals that the maximum number of finality gadgets has been reached A few helper methods are added to indicate when: - a final event is generated by the transaction pool for a given event - a final event is provided, although the transaction might become valid at a later time and could be re-submitted The helper methods are used and taken from https://github.com/paritytech/polkadot-sdk/pull/3079 to help us better keep it in sync. cc @paritytech/subxt-team --------- Signed-off-by: Alexandru Vasile --- .../client/transaction-pool/api/src/error.rs | 24 +++++ .../client/transaction-pool/api/src/lib.rs | 89 +++++++++++++++---- 2 files changed, 97 insertions(+), 16 deletions(-) diff --git a/substrate/client/transaction-pool/api/src/error.rs b/substrate/client/transaction-pool/api/src/error.rs index e521502f66f..d0744bfa3e1 100644 --- a/substrate/client/transaction-pool/api/src/error.rs +++ b/substrate/client/transaction-pool/api/src/error.rs @@ -71,6 +71,30 @@ pub enum Error { RejectedFutureTransaction, } +impl Error { + /// Returns true if the transaction could be re-submitted to the pool in the future. + /// + /// For example, `Error::ImmediatelyDropped` is retriable, because the transaction + /// may enter the pool if there is space for it in the future. + pub fn is_retriable(&self) -> bool { + match self { + // An invalid transaction is temporarily banned, however it can + // become valid at a later time. + Error::TemporarilyBanned | + // The pool is full at the moment. + Error::ImmediatelyDropped | + // The block id is not known to the pool. + // The node might be lagging behind, or during a warp sync. + Error::InvalidBlockId(_) | + // The pool is configured to not accept future transactions. + Error::RejectedFutureTransaction => { + true + } + _ => false + } + } +} + /// Transaction pool error conversion. pub trait IntoPoolError: std::error::Error + Send + Sized + Sync { /// Try to extract original `Error` diff --git a/substrate/client/transaction-pool/api/src/lib.rs b/substrate/client/transaction-pool/api/src/lib.rs index a795917528f..0a313c5b782 100644 --- a/substrate/client/transaction-pool/api/src/lib.rs +++ b/substrate/client/transaction-pool/api/src/lib.rs @@ -62,20 +62,27 @@ impl PoolStatus { /// /// The status events can be grouped based on their kinds as: /// 1. Entering/Moving within the pool: -/// - `Future` -/// - `Ready` +/// - [Future](TransactionStatus::Future) +/// - [Ready](TransactionStatus::Ready) /// 2. Inside `Ready` queue: -/// - `Broadcast` +/// - [Broadcast](TransactionStatus::Broadcast) /// 3. Leaving the pool: -/// - `InBlock` -/// - `Invalid` -/// - `Usurped` -/// - `Dropped` +/// - [InBlock](TransactionStatus::InBlock) +/// - [Invalid](TransactionStatus::Invalid) +/// - [Usurped](TransactionStatus::Usurped) +/// - [Dropped](TransactionStatus::Dropped) /// 4. Re-entering the pool: -/// - `Retracted` +/// - [Retracted](TransactionStatus::Retracted) /// 5. Block finalized: -/// - `Finalized` -/// - `FinalityTimeout` +/// - [Finalized](TransactionStatus::Finalized) +/// - [FinalityTimeout](TransactionStatus::FinalityTimeout) +/// +/// Transactions are first placed in either the `Ready` or `Future` queues of the transaction pool. +/// Substrate validates the transaction before it enters the pool. +/// +/// A transaction is placed in the `Future` queue if it will become valid at a future time. +/// For example, submitting a transaction with a higher account nonce than the current +/// expected nonce will place the transaction in the `Future` queue. /// /// The events will always be received in the order described above, however /// there might be cases where transactions alternate between `Future` and `Ready` @@ -88,19 +95,37 @@ impl PoolStatus { /// 1. Due to possible forks, the transaction that ends up being in included /// in one block, may later re-enter the pool or be marked as invalid. /// 2. Transaction `Dropped` at one point, may later re-enter the pool if some other -/// transactions are removed. +/// transactions are removed. A `Dropped` transaction may re-enter the pool only if it is +/// resubmitted. /// 3. `Invalid` transaction may become valid at some point in the future. /// (Note that runtimes are encouraged to use `UnknownValidity` to inform the pool about -/// such case). +/// such case). An `Invalid` transaction may re-enter the pool only if it is resubmitted. /// 4. `Retracted` transactions might be included in some next block. /// -/// The stream is considered finished only when either `Finalized` or `FinalityTimeout` -/// event is triggered. You are however free to unsubscribe from notifications at any point. -/// The first one will be emitted when the block, in which transaction was included gets -/// finalized. The `FinalityTimeout` event will be emitted when the block did not reach finality +/// The `FinalityTimeout` event will be emitted when the block did not reach finality /// within 512 blocks. This either indicates that finality is not available for your chain, /// or that finality gadget is lagging behind. If you choose to wait for finality longer, you can /// re-subscribe for a particular transaction hash manually again. +/// +/// ### Last Event +/// +/// The stream is considered finished when one of the following events happen: +/// - [Finalized](TransactionStatus::Finalized) +/// - [FinalityTimeout](TransactionStatus::FinalityTimeout) +/// - [Usurped](TransactionStatus::Usurped) +/// - [Invalid](TransactionStatus::Invalid) +/// - [Dropped](TransactionStatus::Dropped) +/// +/// See [`TransactionStatus::is_final`] for more details. +/// +/// ### Resubmit Transactions +/// +/// Users might resubmit the transaction at a later time for the following events: +/// - [FinalityTimeout](TransactionStatus::FinalityTimeout) +/// - [Invalid](TransactionStatus::Invalid) +/// - [Dropped](TransactionStatus::Dropped) +/// +/// See [`TransactionStatus::is_retriable`] for more details. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum TransactionStatus { @@ -131,6 +156,38 @@ pub enum TransactionStatus { Invalid, } +impl TransactionStatus { + /// Returns true if this is the last event emitted by [`TransactionStatusStream`]. + pub fn is_final(&self) -> bool { + // The state must be kept in sync with `crate::graph::Sender`. + match self { + Self::Usurped(_) | + Self::Finalized(_) | + Self::FinalityTimeout(_) | + Self::Invalid | + Self::Dropped => true, + _ => false, + } + } + + /// Returns true if the transaction could be re-submitted to the pool in the future. + /// + /// For example, `TransactionStatus::Dropped` is retriable, because the transaction + /// may enter the pool if there is space for it in the future. + pub fn is_retriable(&self) -> bool { + match self { + // The number of finality watchers has been reached. + Self::FinalityTimeout(_) | + // An invalid transaction might be valid at a later time. + Self::Invalid | + // The transaction was dropped because of the limits of the pool. + // It can reenter the pool when other transactions are removed / finalized. + Self::Dropped => true, + _ => false, + } + } +} + /// The stream of transaction events. pub type TransactionStatusStream = dyn Stream> + Send; -- GitLab From dfc8e4696c6edfb76ccb05f469a221ebb5b270ff Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Mon, 12 Feb 2024 10:55:57 +0100 Subject: [PATCH 040/133] Bridge zombienet tests refactoring (#3260) Related to https://github.com/paritytech/polkadot-sdk/issues/3242 Reorganizing the bridge zombienet tests in order to: - separate the environment spawning from the actual tests - offer better control over the tests and some possibility to orchestrate them as opposed to running everything from the zndsl file Only rewrote the asset transfer test using this new "framework". The old logic and old tests weren't functionally modified or deleted. The plan is to get feedback on this approach first and if this is agreed upon, migrate the other 2 tests later in separate PRs and also do other improvements later. --- bridges/zombienet/README.md | 2 +- .../environments/rococo-westend/helper.sh | 3 + .../rococo-westend/rococo-init.zndsl | 8 +++ .../environments/rococo-westend/rococo.zndsl | 7 ++ .../environments/rococo-westend/spawn.sh | 71 +++++++++++++++++++ .../rococo-westend/westend-init.zndsl | 7 ++ .../environments/rococo-westend/westend.zndsl | 6 ++ .../best-finalized-header-at-bridged-chain.js | 2 +- .../native-assets-balance-increased.js | 2 +- bridges/zombienet/helpers/relayer-rewards.js | 2 +- .../helpers/wait-hrmp-channel-opened.js | 2 +- .../helpers/wrapped-assets-balance.js | 2 +- bridges/zombienet/run-new-test.sh | 47 ++++++++++++ bridges/zombienet/run-tests.sh | 23 +++--- bridges/zombienet/scripts/invoke-script.sh | 2 +- bridges/zombienet/scripts/start-relayer.sh | 2 +- .../roc-reaches-westend.zndsl | 12 ++++ .../tests/0001-asset-transfer/run.sh | 23 ++++++ .../wnd-reaches-rococo.zndsl | 12 ++++ .../wroc-reaches-rococo.zndsl | 10 +++ .../wwnd-reaches-westend.zndsl | 10 +++ bridges/zombienet/utils/common.sh | 45 ++++++++++++ bridges/zombienet/utils/zombienet.sh | 39 ++++++++++ .../parachains/runtimes/bridge-hubs/README.md | 10 ++- cumulus/scripts/bridges_common.sh | 2 +- cumulus/scripts/bridges_rococo_westend.sh | 2 +- .../bridge_hub_rococo_local_network.toml | 16 ++--- .../bridge_hub_westend_local_network.toml | 16 ++--- 28 files changed, 336 insertions(+), 49 deletions(-) create mode 100755 bridges/zombienet/environments/rococo-westend/helper.sh create mode 100644 bridges/zombienet/environments/rococo-westend/rococo-init.zndsl create mode 100644 bridges/zombienet/environments/rococo-westend/rococo.zndsl create mode 100755 bridges/zombienet/environments/rococo-westend/spawn.sh create mode 100644 bridges/zombienet/environments/rococo-westend/westend-init.zndsl create mode 100644 bridges/zombienet/environments/rococo-westend/westend.zndsl create mode 100755 bridges/zombienet/run-new-test.sh create mode 100644 bridges/zombienet/tests/0001-asset-transfer/roc-reaches-westend.zndsl create mode 100755 bridges/zombienet/tests/0001-asset-transfer/run.sh create mode 100644 bridges/zombienet/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl create mode 100644 bridges/zombienet/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl create mode 100644 bridges/zombienet/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl create mode 100644 bridges/zombienet/utils/common.sh create mode 100644 bridges/zombienet/utils/zombienet.sh diff --git a/bridges/zombienet/README.md b/bridges/zombienet/README.md index b601154b624..bd467a410d0 100644 --- a/bridges/zombienet/README.md +++ b/bridges/zombienet/README.md @@ -23,7 +23,7 @@ To start those tests, you need to: - copy fresh `substrate-relay` binary, built in previous point, to the `~/local_bridge_testing/bin/substrate-relay`; -- change the `POLKADOT_SDK_FOLDER` and `ZOMBIENET_BINARY_PATH` (and ensure that the nearby variables +- change the `POLKADOT_SDK_PATH` and `ZOMBIENET_BINARY_PATH` (and ensure that the nearby variables have correct values) in the `./run-tests.sh`. After that, you could run tests with the `./run-tests.sh` command. Hopefully, it'll show the diff --git a/bridges/zombienet/environments/rococo-westend/helper.sh b/bridges/zombienet/environments/rococo-westend/helper.sh new file mode 100755 index 00000000000..049693e0e29 --- /dev/null +++ b/bridges/zombienet/environments/rococo-westend/helper.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +$POLKADOT_SDK_PATH/cumulus/scripts/bridges_rococo_westend.sh "$@" diff --git a/bridges/zombienet/environments/rococo-westend/rococo-init.zndsl b/bridges/zombienet/environments/rococo-westend/rococo-init.zndsl new file mode 100644 index 00000000000..ae3e3e7cb67 --- /dev/null +++ b/bridges/zombienet/environments/rococo-westend/rococo-init.zndsl @@ -0,0 +1,8 @@ +Description: User is able to transfer WND from Westend Asset Hub to Rococo Asset Hub and back +Network: ../../../../cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml +Creds: config + +# ensure that initialization has completed +asset-hub-rococo-collator1: js-script ../../helpers/wait-hrmp-channel-opened.js with "1013" within 300 seconds + + diff --git a/bridges/zombienet/environments/rococo-westend/rococo.zndsl b/bridges/zombienet/environments/rococo-westend/rococo.zndsl new file mode 100644 index 00000000000..7ecb05ceefa --- /dev/null +++ b/bridges/zombienet/environments/rococo-westend/rococo.zndsl @@ -0,0 +1,7 @@ +Description: User is able to transfer WND from Westend Asset Hub to Rococo Asset Hub and back +Network: ../../../../cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml +Creds: config + +# relay is already started - let's wait until with-Westend GRANPDA pallet is initialized at Rococo +bridge-hub-rococo-collator1: js-script ../../helpers/best-finalized-header-at-bridged-chain.js with "Westend,0" within 400 seconds + diff --git a/bridges/zombienet/environments/rococo-westend/spawn.sh b/bridges/zombienet/environments/rococo-westend/spawn.sh new file mode 100755 index 00000000000..dbdc58d5fa1 --- /dev/null +++ b/bridges/zombienet/environments/rococo-westend/spawn.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +set -e + +trap "trap - SIGTERM && kill -9 -$$" SIGINT SIGTERM EXIT + +source "${BASH_SOURCE%/*}/../../utils/common.sh" +source "${BASH_SOURCE%/*}/../../utils/zombienet.sh" + +# whether to init the chains (open HRMP channels, set XCM version, create reserve assets, etc) +init=0 +while [ $# -ne 0 ] +do + arg="$1" + case "$arg" in + --init) + init=1 + ;; + esac + shift +done + +logs_dir=$TEST_DIR/logs +helper_script="${BASH_SOURCE%/*}/helper.sh" + +rococo_def=$POLKADOT_SDK_PATH/cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml +start_zombienet $TEST_DIR $rococo_def rococo_dir rococo_pid +echo + +westend_def=$POLKADOT_SDK_PATH/cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml +start_zombienet $TEST_DIR $westend_def westend_dir westend_pid +echo + +if [[ $init -eq 1 ]]; then + rococo_init_log=$logs_dir/rococo-init.log + echo -e "Setting up the rococo side of the bridge. Logs available at: $rococo_init_log\n" + + westend_init_log=$logs_dir/westend-init.log + echo -e "Setting up the westend side of the bridge. Logs available at: $westend_init_log\n" + + $helper_script init-asset-hub-rococo-local >> $rococo_init_log 2>&1 & + rococo_init_pid=$! + $helper_script init-asset-hub-westend-local >> $westend_init_log 2>&1 & + westend_init_pid=$! + wait -n $rococo_init_pid $westend_init_pid + + + $helper_script init-bridge-hub-rococo-local >> $rococo_init_log 2>&1 & + rococo_init_pid=$! + $helper_script init-bridge-hub-westend-local >> $westend_init_log 2>&1 & + westend_init_pid=$! + wait -n $rococo_init_pid $westend_init_pid + + run_zndsl ${BASH_SOURCE%/*}/rococo-init.zndsl $rococo_dir + run_zndsl ${BASH_SOURCE%/*}/westend-init.zndsl $westend_dir +fi + +relay_log=$logs_dir/relay.log +echo -e "Starting rococo-westend relay. Logs available at: $relay_log\n" +start_background_process "$helper_script run-relay" $relay_log relay_pid + +run_zndsl ${BASH_SOURCE%/*}/rococo.zndsl $rococo_dir +echo $rococo_dir > $TEST_DIR/rococo.env +echo + +run_zndsl ${BASH_SOURCE%/*}/westend.zndsl $westend_dir +echo $westend_dir > $TEST_DIR/westend.env +echo + +wait -n $rococo_pid $westend_pid $relay_pid +kill -9 -$$ diff --git a/bridges/zombienet/environments/rococo-westend/westend-init.zndsl b/bridges/zombienet/environments/rococo-westend/westend-init.zndsl new file mode 100644 index 00000000000..3b3e4bd91e2 --- /dev/null +++ b/bridges/zombienet/environments/rococo-westend/westend-init.zndsl @@ -0,0 +1,7 @@ +Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back +Network: ../../../../cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml +Creds: config + +# ensure that initialization has completed +asset-hub-westend-collator1: js-script ../../helpers/wait-hrmp-channel-opened.js with "1002" within 600 seconds + diff --git a/bridges/zombienet/environments/rococo-westend/westend.zndsl b/bridges/zombienet/environments/rococo-westend/westend.zndsl new file mode 100644 index 00000000000..b3864d12a0e --- /dev/null +++ b/bridges/zombienet/environments/rococo-westend/westend.zndsl @@ -0,0 +1,6 @@ +Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back +Network: ../../../../cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml +Creds: config + +# relay is already started - let's wait until with-Rococo GRANPDA pallet is initialized at Westend +bridge-hub-westend-collator1: js-script ../../helpers/best-finalized-header-at-bridged-chain.js with "Rococo,0" within 400 seconds diff --git a/bridges/zombienet/helpers/best-finalized-header-at-bridged-chain.js b/bridges/zombienet/helpers/best-finalized-header-at-bridged-chain.js index f7e1eefc84b..af4f18aee9b 100644 --- a/bridges/zombienet/helpers/best-finalized-header-at-bridged-chain.js +++ b/bridges/zombienet/helpers/best-finalized-header-at-bridged-chain.js @@ -18,7 +18,7 @@ async function run(nodeName, networkInfo, args) { } // else sleep and retry - await new Promise((resolve) => setTimeout(resolve, 12000)); + await new Promise((resolve) => setTimeout(resolve, 6000)); } } diff --git a/bridges/zombienet/helpers/native-assets-balance-increased.js b/bridges/zombienet/helpers/native-assets-balance-increased.js index 9ee1a769e9f..a35c753d973 100644 --- a/bridges/zombienet/helpers/native-assets-balance-increased.js +++ b/bridges/zombienet/helpers/native-assets-balance-increased.js @@ -13,7 +13,7 @@ async function run(nodeName, networkInfo, args) { } // else sleep and retry - await new Promise((resolve) => setTimeout(resolve, 12000)); + await new Promise((resolve) => setTimeout(resolve, 6000)); } } diff --git a/bridges/zombienet/helpers/relayer-rewards.js b/bridges/zombienet/helpers/relayer-rewards.js index a5f567db797..5347c649604 100644 --- a/bridges/zombienet/helpers/relayer-rewards.js +++ b/bridges/zombienet/helpers/relayer-rewards.js @@ -21,7 +21,7 @@ async function run(nodeName, networkInfo, args) { } // else sleep and retry - await new Promise((resolve) => setTimeout(resolve, 12000)); + await new Promise((resolve) => setTimeout(resolve, 6000)); } } diff --git a/bridges/zombienet/helpers/wait-hrmp-channel-opened.js b/bridges/zombienet/helpers/wait-hrmp-channel-opened.js index e700cab1d74..765d48cc498 100644 --- a/bridges/zombienet/helpers/wait-hrmp-channel-opened.js +++ b/bridges/zombienet/helpers/wait-hrmp-channel-opened.js @@ -15,7 +15,7 @@ async function run(nodeName, networkInfo, args) { } // else sleep and retry - await new Promise((resolve) => setTimeout(resolve, 12000)); + await new Promise((resolve) => setTimeout(resolve, 6000)); } } diff --git a/bridges/zombienet/helpers/wrapped-assets-balance.js b/bridges/zombienet/helpers/wrapped-assets-balance.js index bb3cea8858a..27287118547 100644 --- a/bridges/zombienet/helpers/wrapped-assets-balance.js +++ b/bridges/zombienet/helpers/wrapped-assets-balance.js @@ -19,7 +19,7 @@ async function run(nodeName, networkInfo, args) { } // else sleep and retry - await new Promise((resolve) => setTimeout(resolve, 12000)); + await new Promise((resolve) => setTimeout(resolve, 6000)); } } diff --git a/bridges/zombienet/run-new-test.sh b/bridges/zombienet/run-new-test.sh new file mode 100755 index 00000000000..3bfcb6d83e9 --- /dev/null +++ b/bridges/zombienet/run-new-test.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +set -e + +test=$1 +shift + +# whether to use paths for zombienet+bridges tests container or for local testing +ZOMBIENET_DOCKER_PATHS=0 +while [ $# -ne 0 ] +do + arg="$1" + case "$arg" in + --docker) + ZOMBIENET_DOCKER_PATHS=1 + ;; + esac + shift +done + +export POLKADOT_SDK_PATH=`realpath ${BASH_SOURCE%/*}/../..` + +# set path to binaries +if [ "$ZOMBIENET_DOCKER_PATHS" -eq 1 ]; then + # otherwise zombienet uses some hardcoded paths + unset RUN_IN_CONTAINER + unset ZOMBIENET_IMAGE + + export POLKADOT_BINARY=/usr/local/bin/polkadot + export POLKADOT_PARACHAIN_BINARY=/usr/local/bin/polkadot-parachain + + export ZOMBIENET_BINARY=/usr/local/bin/zombie + export SUBSTRATE_RELAY_BINARY=/usr/local/bin/substrate-relay +else + export POLKADOT_BINARY=$POLKADOT_SDK_PATH/target/release/polkadot + export POLKADOT_PARACHAIN_BINARY=$POLKADOT_SDK_PATH/target/release/polkadot-parachain + + export ZOMBIENET_BINARY=~/local_bridge_testing/bin/zombienet-linux-x64 + export SUBSTRATE_RELAY_BINARY=~/local_bridge_testing/bin/substrate-relay +fi + +export TEST_DIR=`mktemp -d /tmp/bridges-tests-run-XXXXX` +echo -e "Test folder: $TEST_DIR\n" + +${BASH_SOURCE%/*}/tests/$test/run.sh + +kill -9 -$$ || echo "Environment already teared down" \ No newline at end of file diff --git a/bridges/zombienet/run-tests.sh b/bridges/zombienet/run-tests.sh index cf3b529e6a9..42818a5cdf3 100755 --- a/bridges/zombienet/run-tests.sh +++ b/bridges/zombienet/run-tests.sh @@ -27,34 +27,27 @@ done # assuming that we'll be using native provide && all processes will be executing locally # (we need absolute paths here, because they're used when scripts are called by zombienet from tmp folders) -export POLKADOT_SDK_FOLDER=`realpath $(dirname "$0")/../..` -export BRIDGE_TESTS_FOLDER=$POLKADOT_SDK_FOLDER/bridges/zombienet/tests +export POLKADOT_SDK_PATH=`realpath $(dirname "$0")/../..` +export BRIDGE_TESTS_FOLDER=$POLKADOT_SDK_PATH/bridges/zombienet/tests # set pathc to binaries if [ "$ZOMBIENET_DOCKER_PATHS" -eq 1 ]; then - export POLKADOT_BINARY_PATH=/usr/local/bin/polkadot - export POLKADOT_PARACHAIN_BINARY_PATH=/usr/local/bin/polkadot-parachain - export POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_ROCOCO=/usr/local/bin/polkadot-parachain - export POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_WESTEND=/usr/local/bin/polkadot-parachain + export POLKADOT_BINARY=/usr/local/bin/polkadot + export POLKADOT_PARACHAIN_BINARY=/usr/local/bin/polkadot-parachain - export SUBSTRATE_RELAY_PATH=/usr/local/bin/substrate-relay + export SUBSTRATE_RELAY_BINARY=/usr/local/bin/substrate-relay export ZOMBIENET_BINARY_PATH=/usr/local/bin/zombie else - export POLKADOT_BINARY_PATH=$POLKADOT_SDK_FOLDER/target/release/polkadot - export POLKADOT_PARACHAIN_BINARY_PATH=$POLKADOT_SDK_FOLDER/target/release/polkadot-parachain - export POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_ROCOCO=$POLKADOT_PARACHAIN_BINARY_PATH - export POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_WESTEND=$POLKADOT_PARACHAIN_BINARY_PATH + export POLKADOT_BINARY=$POLKADOT_SDK_PATH/target/release/polkadot + export POLKADOT_PARACHAIN_BINARY=$POLKADOT_SDK_PATH/target/release/polkadot-parachain - export SUBSTRATE_RELAY_PATH=~/local_bridge_testing/bin/substrate-relay + export SUBSTRATE_RELAY_BINARY=~/local_bridge_testing/bin/substrate-relay export ZOMBIENET_BINARY_PATH=~/local_bridge_testing/bin/zombienet-linux fi # check if `wait` supports -p flag if [ `printf "$BASH_VERSION\n5.1" | sort -V | head -n 1` = "5.1" ]; then IS_BASH_5_1=1; else IS_BASH_5_1=0; fi -# check if `wait` supports -p flag -if [ `printf "$BASH_VERSION\n5.1" | sort -V | head -n 1` = "5.1" ]; then IS_BASH_5_1=1; else IS_BASH_5_1=0; fi - # bridge configuration export LANE_ID="00000002" diff --git a/bridges/zombienet/scripts/invoke-script.sh b/bridges/zombienet/scripts/invoke-script.sh index 835b4fe500f..1f814cd0856 100755 --- a/bridges/zombienet/scripts/invoke-script.sh +++ b/bridges/zombienet/scripts/invoke-script.sh @@ -2,6 +2,6 @@ INVOKE_LOG=`mktemp -p $TEST_FOLDER invoke.XXXXX` -pushd $POLKADOT_SDK_FOLDER/cumulus/scripts +pushd $POLKADOT_SDK_PATH/cumulus/scripts ./bridges_rococo_westend.sh $1 >$INVOKE_LOG 2>&1 popd diff --git a/bridges/zombienet/scripts/start-relayer.sh b/bridges/zombienet/scripts/start-relayer.sh index 2f72b5ee556..630fd7316fe 100755 --- a/bridges/zombienet/scripts/start-relayer.sh +++ b/bridges/zombienet/scripts/start-relayer.sh @@ -2,6 +2,6 @@ RELAY_LOG=`mktemp -p $TEST_FOLDER relay.XXXXX` -pushd $POLKADOT_SDK_FOLDER/cumulus/scripts +pushd $POLKADOT_SDK_PATH/cumulus/scripts ./bridges_rococo_westend.sh run-relay >$RELAY_LOG 2>&1& popd diff --git a/bridges/zombienet/tests/0001-asset-transfer/roc-reaches-westend.zndsl b/bridges/zombienet/tests/0001-asset-transfer/roc-reaches-westend.zndsl new file mode 100644 index 00000000000..c7fb73a88c0 --- /dev/null +++ b/bridges/zombienet/tests/0001-asset-transfer/roc-reaches-westend.zndsl @@ -0,0 +1,12 @@ +Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back +Network: ../../../../cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml +Creds: config + +# send ROC to //Alice from Rococo AH to Westend AH +asset-hub-westend-collator1: run ../../environments/rococo-westend/helper.sh with "reserve-transfer-assets-from-asset-hub-rococo-local" within 120 seconds + +# check that //Alice received the ROC on Westend AH +asset-hub-westend-collator1: js-script ../../helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,0,Rococo" within 300 seconds + +# check that the relayer //Charlie is rewarded by Westend AH +bridge-hub-westend-collator1: js-script ../../helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x6268726F,ThisChain,0" within 30 seconds diff --git a/bridges/zombienet/tests/0001-asset-transfer/run.sh b/bridges/zombienet/tests/0001-asset-transfer/run.sh new file mode 100755 index 00000000000..07951a0e321 --- /dev/null +++ b/bridges/zombienet/tests/0001-asset-transfer/run.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -e + +source "${BASH_SOURCE%/*}/../../utils/common.sh" +source "${BASH_SOURCE%/*}/../../utils/zombienet.sh" + +${BASH_SOURCE%/*}/../../environments/rococo-westend/spawn.sh --init & +env_pid=$! + +ensure_process_file $env_pid $TEST_DIR/rococo.env 400 +rococo_dir=`cat $TEST_DIR/rococo.env` +echo + +ensure_process_file $env_pid $TEST_DIR/westend.env 180 +westend_dir=`cat $TEST_DIR/westend.env` +echo + +run_zndsl ${BASH_SOURCE%/*}/roc-reaches-westend.zndsl $westend_dir +run_zndsl ${BASH_SOURCE%/*}/wnd-reaches-rococo.zndsl $rococo_dir + +run_zndsl ${BASH_SOURCE%/*}/wroc-reaches-rococo.zndsl $rococo_dir +run_zndsl ${BASH_SOURCE%/*}/wwnd-reaches-westend.zndsl $westend_dir diff --git a/bridges/zombienet/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl b/bridges/zombienet/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl new file mode 100644 index 00000000000..91ce8e5cbff --- /dev/null +++ b/bridges/zombienet/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl @@ -0,0 +1,12 @@ +Description: User is able to transfer WND from Westend Asset Hub to Rococo Asset Hub and back +Network: ../../../../cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml +Creds: config + +# send WND to //Alice from Westend AH to Rococo AH +asset-hub-rococo-collator1: run ../../environments/rococo-westend/helper.sh with "reserve-transfer-assets-from-asset-hub-westend-local" within 120 seconds + +# check that //Alice received the WND on Rococo AH +asset-hub-rococo-collator1: js-script ../../helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,0,Westend" within 300 seconds + +# check that the relayer //Charlie is rewarded by Rococo AH +bridge-hub-rococo-collator1: js-script ../../helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x62687764,ThisChain,0" within 30 seconds diff --git a/bridges/zombienet/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl b/bridges/zombienet/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl new file mode 100644 index 00000000000..7fbdecadeae --- /dev/null +++ b/bridges/zombienet/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl @@ -0,0 +1,10 @@ +Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back +Network: ../../../../cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml +Creds: config + +# send wROC back to Alice from Westend AH to Rococo AH +asset-hub-rococo-collator1: run ../../environments/rococo-westend/helper.sh with "withdraw-reserve-assets-from-asset-hub-westend-local" within 120 seconds + +# check that //Alice received the wROC on Rococo AH +# (we wait until //Alice account increases here - there are no other transactions that may increase it) +asset-hub-rococo-collator1: js-script ../../helpers/native-assets-balance-increased.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" within 300 seconds diff --git a/bridges/zombienet/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl b/bridges/zombienet/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl new file mode 100644 index 00000000000..662e8875268 --- /dev/null +++ b/bridges/zombienet/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl @@ -0,0 +1,10 @@ +Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back +Network: ../../../../cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml +Creds: config + +# send wWND back to Alice from Rococo AH to Westend AH +asset-hub-westend-collator1: run ../../environments/rococo-westend/helper.sh with "withdraw-reserve-assets-from-asset-hub-rococo-local" within 120 seconds + +# check that //Alice received the wWND on Westend AH +# (we wait until //Alice account increases here - there are no other transactions that may increase it) +asset-hub-westend-collator1: js-script ../../helpers/native-assets-balance-increased.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" within 300 seconds diff --git a/bridges/zombienet/utils/common.sh b/bridges/zombienet/utils/common.sh new file mode 100644 index 00000000000..06f41320be1 --- /dev/null +++ b/bridges/zombienet/utils/common.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +function start_background_process() { + local command=$1 + local log_file=$2 + local __pid=$3 + + $command > $log_file 2>&1 & + eval $__pid="'$!'" +} + +function wait_for_process_file() { + local pid=$1 + local file=$2 + local timeout=$3 + local __found=$4 + + local time=0 + until [ -e $file ]; do + if ! kill -0 $pid; then + echo "Process finished unsuccessfully" + return + fi + if (( time++ >= timeout )); then + echo "Timeout waiting for file $file: $timeout seconds" + eval $__found=0 + return + fi + sleep 1 + done + + echo "File $file found after $time seconds" + eval $__found=1 +} + +function ensure_process_file() { + local pid=$1 + local file=$2 + local timeout=$3 + + wait_for_process_file $pid $file $timeout file_found + if [ "$file_found" != "1" ]; then + exit 1 + fi +} diff --git a/bridges/zombienet/utils/zombienet.sh b/bridges/zombienet/utils/zombienet.sh new file mode 100644 index 00000000000..bbcd1a30620 --- /dev/null +++ b/bridges/zombienet/utils/zombienet.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +source "${BASH_SOURCE%/*}/common.sh" + +function start_zombienet() { + local test_dir=$1 + local definition_path=$2 + local __zombienet_dir=$3 + local __zombienet_pid=$4 + + local zombienet_name=`basename $definition_path .toml` + local zombienet_dir=$test_dir/$zombienet_name + eval $__zombienet_dir="'$zombienet_dir'" + mkdir -p $zombienet_dir + rm -rf $zombienet_dir + + local logs_dir=$test_dir/logs + mkdir -p $logs_dir + local zombienet_log=$logs_dir/$zombienet_name.log + + echo "Starting $zombienet_name zombienet. Logs available at: $zombienet_log" + start_background_process \ + "$ZOMBIENET_BINARY spawn --dir $zombienet_dir --provider native $definition_path" \ + "$zombienet_log" zombienet_pid + + ensure_process_file $zombienet_pid "$zombienet_dir/zombie.json" 180 + echo "$zombienet_name zombienet started successfully" + + eval $__zombienet_pid="'$zombienet_pid'" +} + +function run_zndsl() { + local zndsl_file=$1 + local zombienet_dir=$2 + + echo "Running $zndsl_file." + $ZOMBIENET_BINARY test --dir $zombienet_dir --provider native $zndsl_file $zombienet_dir/zombie.json + echo +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/README.md b/cumulus/parachains/runtimes/bridge-hubs/README.md index cf617db730d..4e9403bf25e 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/README.md +++ b/cumulus/parachains/runtimes/bridge-hubs/README.md @@ -89,9 +89,8 @@ cp target/release/polkadot-parachain ~/local_bridge_testing/bin/polkadot-paracha cd # Rococo + BridgeHubRococo + AssetHub for Rococo (mirroring Kusama) -POLKADOT_BINARY_PATH=~/local_bridge_testing/bin/polkadot \ -POLKADOT_PARACHAIN_BINARY_PATH=~/local_bridge_testing/bin/polkadot-parachain \ -POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_ROCOCO=~/local_bridge_testing/bin/polkadot-parachain-asset-hub \ +POLKADOT_BINARY=~/local_bridge_testing/bin/polkadot \ +POLKADOT_PARACHAIN_BINARY=~/local_bridge_testing/bin/polkadot-parachain \ ~/local_bridge_testing/bin/zombienet-linux --provider native spawn ./cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml ``` @@ -99,9 +98,8 @@ POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_ROCOCO=~/local_bridge_testing/bin/p cd # Westend + BridgeHubWestend + AssetHub for Westend (mirroring Polkadot) -POLKADOT_BINARY_PATH=~/local_bridge_testing/bin/polkadot \ -POLKADOT_PARACHAIN_BINARY_PATH=~/local_bridge_testing/bin/polkadot-parachain \ -POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_WESTEND=~/local_bridge_testing/bin/polkadot-parachain-asset-hub \ +POLKADOT_BINARY=~/local_bridge_testing/bin/polkadot \ +POLKADOT_PARACHAIN_BINARY=~/local_bridge_testing/bin/polkadot-parachain \ ~/local_bridge_testing/bin/zombienet-linux --provider native spawn ./cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml ``` diff --git a/cumulus/scripts/bridges_common.sh b/cumulus/scripts/bridges_common.sh index 029d4cd4ff7..2064dbbb5b8 100755 --- a/cumulus/scripts/bridges_common.sh +++ b/cumulus/scripts/bridges_common.sh @@ -2,7 +2,7 @@ function relayer_path() { local default_path=~/local_bridge_testing/bin/substrate-relay - local path="${SUBSTRATE_RELAY_PATH:-$default_path}" + local path="${SUBSTRATE_RELAY_BINARY:-$default_path}" echo "$path" } diff --git a/cumulus/scripts/bridges_rococo_westend.sh b/cumulus/scripts/bridges_rococo_westend.sh index 3b6f8e89285..7bde5ede77f 100755 --- a/cumulus/scripts/bridges_rococo_westend.sh +++ b/cumulus/scripts/bridges_rococo_westend.sh @@ -185,8 +185,8 @@ function run_relay() { case "$1" in run-relay) - init_ro_wnd init_wnd_ro + init_ro_wnd run_relay ;; init-asset-hub-rococo-local) diff --git a/cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml b/cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml index 99a7d0035b5..52271f94421 100644 --- a/cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml +++ b/cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml @@ -2,7 +2,7 @@ node_spawn_timeout = 240 [relaychain] -default_command = "{{POLKADOT_BINARY_PATH}}" +default_command = "{{POLKADOT_BINARY}}" default_args = [ "-lparachain=debug,xcm=trace" ] chain = "rococo-local" @@ -36,24 +36,22 @@ cumulus_based = true [[parachains.collators]] name = "bridge-hub-rococo-collator1" validator = true - command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" + command = "{{POLKADOT_PARACHAIN_BINARY}}" rpc_port = 8933 ws_port = 8943 args = [ - "-lparachain=debug,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace", - "--force-authoring" + "-lparachain=debug,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace" ] # run bob as parachain collator [[parachains.collators]] name = "bridge-hub-rococo-collator2" validator = true - command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" + command = "{{POLKADOT_PARACHAIN_BINARY}}" rpc_port = 8934 ws_port = 8944 args = [ - "-lparachain=trace,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace", - "--force-authoring" + "-lparachain=trace,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace" ] [[parachains]] @@ -65,14 +63,14 @@ cumulus_based = true name = "asset-hub-rococo-collator1" rpc_port = 9911 ws_port = 9910 - command = "{{POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_ROCOCO}}" + command = "{{POLKADOT_PARACHAIN_BINARY}}" args = [ "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace" ] [[parachains.collators]] name = "asset-hub-rococo-collator2" - command = "{{POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_ROCOCO}}" + command = "{{POLKADOT_PARACHAIN_BINARY}}" args = [ "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace" ] diff --git a/cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml b/cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml index 1919d1c63f2..f2550bcc995 100644 --- a/cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml +++ b/cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml @@ -2,7 +2,7 @@ node_spawn_timeout = 240 [relaychain] -default_command = "{{POLKADOT_BINARY_PATH}}" +default_command = "{{POLKADOT_BINARY}}" default_args = [ "-lparachain=debug,xcm=trace" ] chain = "westend-local" @@ -36,24 +36,22 @@ cumulus_based = true [[parachains.collators]] name = "bridge-hub-westend-collator1" validator = true - command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" + command = "{{POLKADOT_PARACHAIN_BINARY}}" rpc_port = 8935 ws_port = 8945 args = [ - "-lparachain=debug,runtime::mmr=info,substrate=info,runtime=info,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace", - "--force-authoring" + "-lparachain=debug,runtime::mmr=info,substrate=info,runtime=info,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace" ] # run bob as parachain collator [[parachains.collators]] name = "bridge-hub-westend-collator2" validator = true - command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" + command = "{{POLKADOT_PARACHAIN_BINARY}}" rpc_port = 8936 ws_port = 8946 args = [ - "-lparachain=trace,runtime::mmr=info,substrate=info,runtime=info,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace", - "--force-authoring" + "-lparachain=trace,runtime::mmr=info,substrate=info,runtime=info,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace" ] [[parachains]] @@ -65,14 +63,14 @@ cumulus_based = true name = "asset-hub-westend-collator1" rpc_port = 9011 ws_port = 9010 - command = "{{POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_WESTEND}}" + command = "{{POLKADOT_PARACHAIN_BINARY}}" args = [ "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace" ] [[parachains.collators]] name = "asset-hub-westend-collator2" - command = "{{POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_WESTEND}}" + command = "{{POLKADOT_PARACHAIN_BINARY}}" args = [ "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace" ] -- GitLab From c3489aaf2f5b248c82cb86b102dba1b591a3e90f Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Mon, 12 Feb 2024 11:30:46 +0100 Subject: [PATCH 041/133] [ci] Don't run prdoc and lables GHA on master (#3257) PR adds condition to ignore master branch for prdoc and labels GHA. This option doesn't work because all PRs are for master thus the actions won't start: ```yml on: pull_request: branches-ignore: - master ``` This option doesn't work because actions don't see the PR number and [break](https://github.com/paritytech/polkadot-sdk/actions/runs/7827272667/job/21354764953): ```yml on: push: branches-ignore: - master ``` cc https://github.com/paritytech/ci_cd/issues/940 cc https://github.com/paritytech/polkadot-sdk/issues/3240 --- .github/workflows/check-labels.yml | 18 ++++++++++-------- .github/workflows/check-prdoc.yml | 18 ++++++------------ 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/.github/workflows/check-labels.yml b/.github/workflows/check-labels.yml index 97562f0da09..1d1a8770058 100644 --- a/.github/workflows/check-labels.yml +++ b/.github/workflows/check-labels.yml @@ -9,14 +9,6 @@ jobs: check-labels: runs-on: ubuntu-latest steps: - - name: Skip merge queue - if: ${{ contains(github.ref, 'gh-readonly-queue') }} - run: exit 0 - - name: Pull image - env: - IMAGE: paritytech/ruled_labels:0.4.0 - run: docker pull $IMAGE - - name: Check labels env: IMAGE: paritytech/ruled_labels:0.4.0 @@ -28,6 +20,16 @@ jobs: RULES_PATH: labels/ruled_labels CHECK_SPECS: "specs_polkadot-sdk.yaml" run: | + if [ ${{ github.ref }} == "refs/heads/master" ]; then + echo "Skipping master" + exit 0 + fi + if [ $(echo ${{ github.ref }} | grep -c "gh-readonly-queue") -eq 1 ]; then + echo "Skipping merge queue" + exit 0 + fi + + docker pull $IMAGE echo "REPO: ${REPO}" echo "GITHUB_PR: ${GITHUB_PR}" diff --git a/.github/workflows/check-prdoc.yml b/.github/workflows/check-prdoc.yml index f47404744a4..5503b61d681 100644 --- a/.github/workflows/check-prdoc.yml +++ b/.github/workflows/check-prdoc.yml @@ -17,31 +17,25 @@ env: jobs: check-prdoc: runs-on: ubuntu-latest + if: github.event.pull_request.number != '' steps: + - name: Checkout repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 # we cannot show the version in this step (ie before checking out the repo) # due to https://github.com/paritytech/prdoc/issues/15 - - name: Skip merge queue - if: ${{ contains(github.ref, 'gh-readonly-queue') }} - run: exit 0 - - name: Pull image + - name: Check if PRdoc is required + id: get-labels run: | echo "Pulling $IMAGE" $ENGINE pull $IMAGE - - name: Check if PRdoc is required - id: get-labels - run: | # Fetch the labels for the PR under test echo "Fetch the labels for $API_BASE/${REPO}/pulls/${GITHUB_PR}" labels=$( curl -H "Authorization: token ${GITHUB_TOKEN}" -s "$API_BASE/${REPO}/pulls/${GITHUB_PR}" | jq '.labels | .[] | .name' | tr "\n" ",") echo "Labels: ${labels}" echo "labels=${labels}" >> "$GITHUB_OUTPUT" - - name: Checkout repo - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 - - - name: Check PRDoc version - run: | + echo "Checking PRdoc version" $ENGINE run --rm -v $PWD:/repo $IMAGE --version - name: Early exit if PR is silent -- GitLab From 8362a6810dd66be5ec02d7de32c521a095cc8bd6 Mon Sep 17 00:00:00 2001 From: Alexandru Gheorghe <49718502+alexggh@users.noreply.github.com> Date: Mon, 12 Feb 2024 12:44:16 +0200 Subject: [PATCH 042/133] statement-distribution: Fix CostMinor("Unexpected Statement") (#3223) On grid distribution messages have two paths of reaching a node, so there is the possiblity of a race when two peers send each other the same statement around the same time. Statement local_knowledge will tell us that the peer should have not send the statement because we sent it to it. Fix it by also keeping track only of the statement we received from a given peer and penalize it only if it sends it to us more than once. Fixes: https://github.com/paritytech/polkadot-sdk/issues/2346 Additionally, also use different Cost labels for different paths to make it easier to debug things. --------- Signed-off-by: Alexandru Gheorghe --- .../statement-distribution/src/v2/grid.rs | 59 ++++++-- .../statement-distribution/src/v2/mod.rs | 135 ++++++++++++------ .../src/v2/tests/cluster.rs | 6 +- 3 files changed, 143 insertions(+), 57 deletions(-) diff --git a/polkadot/node/network/statement-distribution/src/v2/grid.rs b/polkadot/node/network/statement-distribution/src/v2/grid.rs index 19f23053192..24d846c840e 100644 --- a/polkadot/node/network/statement-distribution/src/v2/grid.rs +++ b/polkadot/node/network/statement-distribution/src/v2/grid.rs @@ -527,12 +527,16 @@ impl GridTracker { } /// Determine the validators which can send a statement to us by direct broadcast. + /// + /// Returns a list of tuples representing each potential sender(ValidatorIndex) + /// and if the sender should already know about the statement, because we just + /// sent it to it. pub fn direct_statement_providers( &self, groups: &Groups, originator: ValidatorIndex, statement: &CompactStatement, - ) -> Vec { + ) -> Vec<(ValidatorIndex, bool)> { let (g, c_h, kind, in_group) = match extract_statement_and_group_info(groups, originator, statement) { None => return Vec::new(), @@ -616,12 +620,13 @@ impl GridTracker { originator: ValidatorIndex, counterparty: ValidatorIndex, statement: &CompactStatement, + received: bool, ) { if let Some((_, c_h, kind, in_group)) = extract_statement_and_group_info(groups, originator, statement) { if let Some(known) = self.confirmed_backed.get_mut(&c_h) { - known.sent_or_received_direct_statement(counterparty, in_group, kind); + known.sent_or_received_direct_statement(counterparty, in_group, kind, received); if let Some(pending) = self.pending_statements.get_mut(&counterparty) { pending.remove(&(originator, statement.clone())); @@ -908,6 +913,12 @@ struct MutualKnowledge { /// `Some` only if we have advertised, acknowledged, or requested the candidate /// from them. local_knowledge: Option, + /// Knowledge peer circulated to us, this is different from `local_knowledge` and + /// `remote_knowledge`, through the fact that includes only statements that we received from + /// peer while the other two, after manifest exchange part will include both what we sent to + /// the peer and what we received from peer, see `sent_or_received_direct_statement` for more + /// details. + received_knowledge: Option, } // A utility struct for keeping track of metadata about candidates @@ -933,10 +944,13 @@ impl KnownBackedCandidate { } fn manifest_sent_to(&mut self, validator: ValidatorIndex, local_knowledge: StatementFilter) { - let k = self - .mutual_knowledge - .entry(validator) - .or_insert_with(|| MutualKnowledge { remote_knowledge: None, local_knowledge: None }); + let k = self.mutual_knowledge.entry(validator).or_insert_with(|| MutualKnowledge { + remote_knowledge: None, + local_knowledge: None, + received_knowledge: None, + }); + k.received_knowledge = + Some(StatementFilter::blank(local_knowledge.seconded_in_group.len())); k.local_knowledge = Some(local_knowledge); } @@ -946,20 +960,24 @@ impl KnownBackedCandidate { validator: ValidatorIndex, remote_knowledge: StatementFilter, ) { - let k = self - .mutual_knowledge - .entry(validator) - .or_insert_with(|| MutualKnowledge { remote_knowledge: None, local_knowledge: None }); + let k = self.mutual_knowledge.entry(validator).or_insert_with(|| MutualKnowledge { + remote_knowledge: None, + local_knowledge: None, + received_knowledge: None, + }); k.remote_knowledge = Some(remote_knowledge); } + /// Returns a list of tuples representing each potential sender(ValidatorIndex) + /// and if the sender should already know about the statement, because we just + /// sent it to it. fn direct_statement_senders( &self, group_index: GroupIndex, originator_index_in_group: usize, statement_kind: StatementKind, - ) -> Vec { + ) -> Vec<(ValidatorIndex, bool)> { if group_index != self.group_index { return Vec::new() } @@ -968,11 +986,18 @@ impl KnownBackedCandidate { .iter() .filter(|(_, k)| k.remote_knowledge.is_some()) .filter(|(_, k)| { - k.local_knowledge + k.received_knowledge .as_ref() .map_or(false, |r| !r.contains(originator_index_in_group, statement_kind)) }) - .map(|(v, _)| *v) + .map(|(v, k)| { + ( + *v, + k.local_knowledge + .as_ref() + .map_or(false, |r| r.contains(originator_index_in_group, statement_kind)), + ) + }) .collect() } @@ -1014,12 +1039,19 @@ impl KnownBackedCandidate { validator: ValidatorIndex, statement_index_in_group: usize, statement_kind: StatementKind, + received: bool, ) { if let Some(k) = self.mutual_knowledge.get_mut(&validator) { if let (Some(r), Some(l)) = (k.remote_knowledge.as_mut(), k.local_knowledge.as_mut()) { r.set(statement_index_in_group, statement_kind); l.set(statement_index_in_group, statement_kind); } + + if received { + k.received_knowledge + .as_mut() + .map(|knowledge| knowledge.set(statement_index_in_group, statement_kind)); + } } } @@ -2238,6 +2270,7 @@ mod tests { validator_index, counterparty, &statement, + false, ); // There should be no pending statements now (for the counterparty). diff --git a/polkadot/node/network/statement-distribution/src/v2/mod.rs b/polkadot/node/network/statement-distribution/src/v2/mod.rs index 52852657870..dc29c19a48e 100644 --- a/polkadot/node/network/statement-distribution/src/v2/mod.rs +++ b/polkadot/node/network/statement-distribution/src/v2/mod.rs @@ -93,7 +93,19 @@ mod statement_store; #[cfg(test)] mod tests; -const COST_UNEXPECTED_STATEMENT: Rep = Rep::CostMinor("Unexpected Statement"); +const COST_UNEXPECTED_STATEMENT_NOT_VALIDATOR: Rep = + Rep::CostMinor("Unexpected Statement, not a validator"); +const COST_UNEXPECTED_STATEMENT_VALIDATOR_NOT_FOUND: Rep = + Rep::CostMinor("Unexpected Statement, validator not found"); +const COST_UNEXPECTED_STATEMENT_INVALID_SENDER: Rep = + Rep::CostMinor("Unexpected Statement, invalid sender"); +const COST_UNEXPECTED_STATEMENT_BAD_ADVERTISE: Rep = + Rep::CostMinor("Unexpected Statement, bad advertise"); +const COST_UNEXPECTED_STATEMENT_CLUSTER_REJECTED: Rep = + Rep::CostMinor("Unexpected Statement, cluster rejected"); +const COST_UNEXPECTED_STATEMENT_NOT_IN_GROUP: Rep = + Rep::CostMinor("Unexpected Statement, not in group"); + const COST_UNEXPECTED_STATEMENT_MISSING_KNOWLEDGE: Rep = Rep::CostMinor("Unexpected Statement, missing knowledge for relay parent"); const COST_EXCESSIVE_SECONDED: Rep = Rep::CostMinor("Sent Excessive `Seconded` Statements"); @@ -1086,6 +1098,7 @@ async fn send_pending_grid_messages( originator, peer_validator_id, &compact, + false, ); } @@ -1378,6 +1391,7 @@ async fn circulate_statement( originator, target, &compact_statement, + false, ); }, } @@ -1515,7 +1529,13 @@ async fn handle_incoming_statement( // we shouldn't be receiving statements unless we're a validator // this session. if per_session.is_not_validator() { - modify_reputation(reputation, ctx.sender(), peer, COST_UNEXPECTED_STATEMENT).await; + modify_reputation( + reputation, + ctx.sender(), + peer, + COST_UNEXPECTED_STATEMENT_NOT_VALIDATOR, + ) + .await; } return }, @@ -1526,7 +1546,13 @@ async fn handle_incoming_statement( match per_session.groups.by_validator_index(statement.unchecked_validator_index()) { Some(g) => g, None => { - modify_reputation(reputation, ctx.sender(), peer, COST_UNEXPECTED_STATEMENT).await; + modify_reputation( + reputation, + ctx.sender(), + peer, + COST_UNEXPECTED_STATEMENT_VALIDATOR_NOT_FOUND, + ) + .await; return }, }; @@ -1565,38 +1591,45 @@ async fn handle_incoming_statement( (active, idx) }; - let checked_statement = - if let Some((active, cluster_sender_index)) = active.zip(cluster_sender_index) { - match handle_cluster_statement( - relay_parent, - &mut active.cluster_tracker, - per_relay_parent.session, - &per_session.session_info, - statement, - cluster_sender_index, - ) { - Ok(Some(s)) => s, - Ok(None) => return, - Err(rep) => { - modify_reputation(reputation, ctx.sender(), peer, rep).await; - return - }, - } - } else { - let grid_sender_index = local_validator - .grid_tracker - .direct_statement_providers( - &per_session.groups, - statement.unchecked_validator_index(), - statement.unchecked_payload(), - ) - .into_iter() - .filter_map(|i| session_info.discovery_keys.get(i.0 as usize).map(|ad| (i, ad))) - .filter(|(_, ad)| peer_state.is_authority(ad)) - .map(|(i, _)| i) - .next(); + let checked_statement = if let Some((active, cluster_sender_index)) = + active.zip(cluster_sender_index) + { + match handle_cluster_statement( + relay_parent, + &mut active.cluster_tracker, + per_relay_parent.session, + &per_session.session_info, + statement, + cluster_sender_index, + ) { + Ok(Some(s)) => s, + Ok(None) => return, + Err(rep) => { + modify_reputation(reputation, ctx.sender(), peer, rep).await; + return + }, + } + } else { + let grid_sender_index = local_validator + .grid_tracker + .direct_statement_providers( + &per_session.groups, + statement.unchecked_validator_index(), + statement.unchecked_payload(), + ) + .into_iter() + .filter_map(|(i, validator_knows_statement)| { + session_info + .discovery_keys + .get(i.0 as usize) + .map(|ad| (i, ad, validator_knows_statement)) + }) + .filter(|(_, ad, _)| peer_state.is_authority(ad)) + .map(|(i, _, validator_knows_statement)| (i, validator_knows_statement)) + .next(); - if let Some(grid_sender_index) = grid_sender_index { + if let Some((grid_sender_index, validator_knows_statement)) = grid_sender_index { + if !validator_knows_statement { match handle_grid_statement( relay_parent, &mut local_validator.grid_tracker, @@ -1612,11 +1645,22 @@ async fn handle_incoming_statement( }, } } else { - // Not a cluster or grid peer. - modify_reputation(reputation, ctx.sender(), peer, COST_UNEXPECTED_STATEMENT).await; - return + // Reward the peer for sending us the statement + modify_reputation(reputation, ctx.sender(), peer, BENEFIT_VALID_STATEMENT).await; + return; } - }; + } else { + // Not a cluster or grid peer. + modify_reputation( + reputation, + ctx.sender(), + peer, + COST_UNEXPECTED_STATEMENT_INVALID_SENDER, + ) + .await; + return + } + }; let statement = checked_statement.payload().clone(); let originator_index = checked_statement.validator_index(); @@ -1635,7 +1679,13 @@ async fn handle_incoming_statement( ); if let Err(BadAdvertisement) = res { - modify_reputation(reputation, ctx.sender(), peer, COST_UNEXPECTED_STATEMENT).await; + modify_reputation( + reputation, + ctx.sender(), + peer, + COST_UNEXPECTED_STATEMENT_BAD_ADVERTISE, + ) + .await; return } } @@ -1743,11 +1793,11 @@ fn handle_cluster_statement( Ok(ClusterAccept::WithPrejudice) => false, Err(ClusterRejectIncoming::ExcessiveSeconded) => return Err(COST_EXCESSIVE_SECONDED), Err(ClusterRejectIncoming::CandidateUnknown | ClusterRejectIncoming::Duplicate) => - return Err(COST_UNEXPECTED_STATEMENT), + return Err(COST_UNEXPECTED_STATEMENT_CLUSTER_REJECTED), Err(ClusterRejectIncoming::NotInGroup) => { // sanity: shouldn't be possible; we already filtered this // out above. - return Err(COST_UNEXPECTED_STATEMENT) + return Err(COST_UNEXPECTED_STATEMENT_NOT_IN_GROUP) }, } }; @@ -1798,6 +1848,7 @@ fn handle_grid_statement( checked_statement.validator_index(), grid_sender_index, &checked_statement.payload(), + true, ); Ok(checked_statement) @@ -2376,6 +2427,7 @@ fn post_acknowledgement_statement_messages( statement.validator_index(), recipient, statement.payload(), + false, ); match peer.1.into() { ValidationVersion::V2 => messages.push(Versioned::V2( @@ -3255,6 +3307,7 @@ pub(crate) fn answer_request(state: &mut State, message: ResponderMessage) { statement.unchecked_validator_index(), validator_id, statement.unchecked_payload(), + false, ); } } diff --git a/polkadot/node/network/statement-distribution/src/v2/tests/cluster.rs b/polkadot/node/network/statement-distribution/src/v2/tests/cluster.rs index 7ffed9d47d4..a944a9cd6d0 100644 --- a/polkadot/node/network/statement-distribution/src/v2/tests/cluster.rs +++ b/polkadot/node/network/statement-distribution/src/v2/tests/cluster.rs @@ -170,7 +170,7 @@ fn cluster_valid_statement_before_seconded_ignored() { overseer.recv().await, AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) => { assert_eq!(p, peer_a); - assert_eq!(r, COST_UNEXPECTED_STATEMENT.into()); + assert_eq!(r, COST_UNEXPECTED_STATEMENT_CLUSTER_REJECTED.into()); } ); @@ -305,7 +305,7 @@ fn useful_cluster_statement_from_non_cluster_peer_rejected() { assert_matches!( overseer.recv().await, AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) - if p == peer_a && r == COST_UNEXPECTED_STATEMENT.into() => { } + if p == peer_a && r == COST_UNEXPECTED_STATEMENT_INVALID_SENDER.into() => { } ); overseer @@ -359,7 +359,7 @@ fn statement_from_non_cluster_originator_unexpected() { assert_matches!( overseer.recv().await, AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) - if p == peer_a && r == COST_UNEXPECTED_STATEMENT.into() => { } + if p == peer_a && r == COST_UNEXPECTED_STATEMENT_INVALID_SENDER.into() => { } ); overseer -- GitLab From e80c24733f900d4c4f5da88d449737cb01ea6fec Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 12 Feb 2024 12:19:20 +0100 Subject: [PATCH 043/133] Lift dependencies to the workspace (Part 1) (#2070) Changes (partial https://github.com/paritytech/polkadot-sdk/issues/994): - Set log to `0.4.20` everywhere - Lift `log` to the workspace Starting with a simpler one after seeing https://github.com/paritytech/polkadot-sdk/pull/2065 from @jsdw. This sets the `default-features` to `false` in the root and then overwrites that in each create to its original value. This is necessary since otherwise the `default` features are additive and its impossible to disable them in the crate again once they are enabled in the workspace. I am using a tool to do this, so its mostly a test to see that it works as expected. --------- Signed-off-by: Oliver Tale-Yazdi --- Cargo.toml | 1 + bridges/bin/runtime-common/Cargo.toml | 2 +- bridges/modules/grandpa/Cargo.toml | 2 +- bridges/modules/messages/Cargo.toml | 2 +- bridges/modules/parachains/Cargo.toml | 2 +- bridges/modules/relayers/Cargo.toml | 2 +- bridges/modules/xcm-bridge-hub-router/Cargo.toml | 2 +- bridges/modules/xcm-bridge-hub/Cargo.toml | 2 +- bridges/primitives/runtime/Cargo.toml | 2 +- bridges/snowbridge/pallets/ethereum-client/Cargo.toml | 2 +- bridges/snowbridge/pallets/inbound-queue/Cargo.toml | 2 +- bridges/snowbridge/pallets/system/Cargo.toml | 2 +- bridges/snowbridge/primitives/router/Cargo.toml | 2 +- bridges/snowbridge/runtime/runtime-common/Cargo.toml | 2 +- bridges/snowbridge/runtime/test-common/Cargo.toml | 2 +- cumulus/client/consensus/common/Cargo.toml | 2 +- cumulus/pallets/collator-selection/Cargo.toml | 2 +- cumulus/pallets/dmp-queue/Cargo.toml | 2 +- cumulus/pallets/parachain-system/Cargo.toml | 2 +- cumulus/pallets/xcmp-queue/Cargo.toml | 2 +- cumulus/parachain-template/node/Cargo.toml | 2 +- cumulus/parachain-template/runtime/Cargo.toml | 2 +- cumulus/parachains/common/Cargo.toml | 2 +- cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml | 2 +- cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml | 2 +- cumulus/parachains/runtimes/assets/common/Cargo.toml | 2 +- .../runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml | 2 +- .../runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml | 2 +- cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml | 2 +- .../runtimes/collectives/collectives-westend/Cargo.toml | 2 +- .../parachains/runtimes/contracts/contracts-rococo/Cargo.toml | 2 +- cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml | 2 +- .../parachains/runtimes/coretime/coretime-westend/Cargo.toml | 2 +- cumulus/parachains/runtimes/people/people-rococo/Cargo.toml | 2 +- cumulus/parachains/runtimes/people/people-westend/Cargo.toml | 2 +- cumulus/parachains/runtimes/testing/penpal/Cargo.toml | 2 +- cumulus/polkadot-parachain/Cargo.toml | 2 +- cumulus/primitives/utility/Cargo.toml | 2 +- cumulus/xcm/xcm-emulator/Cargo.toml | 2 +- polkadot/cli/Cargo.toml | 2 +- polkadot/node/core/approval-voting/Cargo.toml | 2 +- polkadot/node/core/av-store/Cargo.toml | 2 +- polkadot/node/jaeger/Cargo.toml | 2 +- polkadot/node/metrics/Cargo.toml | 2 +- polkadot/node/network/approval-distribution/Cargo.toml | 2 +- polkadot/node/network/availability-recovery/Cargo.toml | 2 +- polkadot/node/network/bitfield-distribution/Cargo.toml | 2 +- polkadot/node/network/collator-protocol/Cargo.toml | 2 +- polkadot/node/service/Cargo.toml | 2 +- polkadot/node/subsystem-bench/Cargo.toml | 2 +- polkadot/node/subsystem-util/Cargo.toml | 2 +- polkadot/parachain/test-parachains/adder/collator/Cargo.toml | 2 +- polkadot/parachain/test-parachains/undying/Cargo.toml | 2 +- polkadot/parachain/test-parachains/undying/collator/Cargo.toml | 2 +- polkadot/runtime/common/Cargo.toml | 2 +- polkadot/runtime/parachains/Cargo.toml | 2 +- polkadot/runtime/rococo/Cargo.toml | 2 +- polkadot/runtime/test-runtime/Cargo.toml | 2 +- polkadot/runtime/westend/Cargo.toml | 2 +- polkadot/utils/remote-ext-tests/bags-list/Cargo.toml | 2 +- polkadot/xcm/Cargo.toml | 2 +- polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml | 2 +- polkadot/xcm/pallet-xcm/Cargo.toml | 2 +- polkadot/xcm/xcm-builder/Cargo.toml | 2 +- polkadot/xcm/xcm-executor/Cargo.toml | 2 +- polkadot/xcm/xcm-simulator/example/Cargo.toml | 2 +- substrate/bin/node/bench/Cargo.toml | 2 +- substrate/bin/node/cli/Cargo.toml | 2 +- substrate/bin/node/runtime/Cargo.toml | 2 +- substrate/bin/node/testing/Cargo.toml | 2 +- substrate/bin/utils/chain-spec-builder/Cargo.toml | 2 +- substrate/client/allocator/Cargo.toml | 2 +- substrate/client/api/Cargo.toml | 2 +- substrate/client/authority-discovery/Cargo.toml | 2 +- substrate/client/basic-authorship/Cargo.toml | 2 +- substrate/client/chain-spec/Cargo.toml | 2 +- substrate/client/cli/Cargo.toml | 2 +- substrate/client/consensus/aura/Cargo.toml | 2 +- substrate/client/consensus/babe/Cargo.toml | 2 +- substrate/client/consensus/beefy/Cargo.toml | 2 +- substrate/client/consensus/beefy/rpc/Cargo.toml | 2 +- substrate/client/consensus/common/Cargo.toml | 2 +- substrate/client/consensus/grandpa/Cargo.toml | 2 +- substrate/client/consensus/grandpa/rpc/Cargo.toml | 2 +- substrate/client/consensus/manual-seal/Cargo.toml | 2 +- substrate/client/consensus/pow/Cargo.toml | 2 +- substrate/client/consensus/slots/Cargo.toml | 2 +- substrate/client/db/Cargo.toml | 2 +- substrate/client/executor/wasmtime/Cargo.toml | 2 +- substrate/client/informant/Cargo.toml | 2 +- substrate/client/merkle-mountain-range/Cargo.toml | 2 +- substrate/client/mixnet/Cargo.toml | 2 +- substrate/client/network-gossip/Cargo.toml | 2 +- substrate/client/network/Cargo.toml | 2 +- substrate/client/network/bitswap/Cargo.toml | 2 +- substrate/client/network/light/Cargo.toml | 2 +- substrate/client/network/statement/Cargo.toml | 2 +- substrate/client/network/sync/Cargo.toml | 2 +- substrate/client/network/test/Cargo.toml | 2 +- substrate/client/network/transactions/Cargo.toml | 2 +- substrate/client/offchain/Cargo.toml | 2 +- substrate/client/proposer-metrics/Cargo.toml | 2 +- substrate/client/rpc-servers/Cargo.toml | 2 +- substrate/client/rpc-spec-v2/Cargo.toml | 2 +- substrate/client/rpc/Cargo.toml | 2 +- substrate/client/service/Cargo.toml | 2 +- substrate/client/service/test/Cargo.toml | 2 +- substrate/client/state-db/Cargo.toml | 2 +- substrate/client/statement-store/Cargo.toml | 2 +- substrate/client/storage-monitor/Cargo.toml | 2 +- substrate/client/sysinfo/Cargo.toml | 2 +- substrate/client/telemetry/Cargo.toml | 2 +- substrate/client/tracing/Cargo.toml | 2 +- substrate/client/transaction-pool/Cargo.toml | 2 +- substrate/client/transaction-pool/api/Cargo.toml | 2 +- substrate/client/utils/Cargo.toml | 2 +- substrate/frame/Cargo.toml | 2 +- substrate/frame/alliance/Cargo.toml | 2 +- substrate/frame/assets/Cargo.toml | 2 +- substrate/frame/aura/Cargo.toml | 2 +- substrate/frame/babe/Cargo.toml | 2 +- substrate/frame/bags-list/Cargo.toml | 2 +- substrate/frame/bags-list/remote-tests/Cargo.toml | 2 +- substrate/frame/balances/Cargo.toml | 2 +- substrate/frame/beefy-mmr/Cargo.toml | 2 +- substrate/frame/beefy/Cargo.toml | 2 +- substrate/frame/benchmarking/Cargo.toml | 2 +- substrate/frame/bounties/Cargo.toml | 2 +- substrate/frame/child-bounties/Cargo.toml | 2 +- substrate/frame/collective/Cargo.toml | 2 +- substrate/frame/contracts/Cargo.toml | 2 +- substrate/frame/core-fellowship/Cargo.toml | 2 +- substrate/frame/democracy/Cargo.toml | 2 +- substrate/frame/election-provider-multi-phase/Cargo.toml | 2 +- .../election-provider-multi-phase/test-staking-e2e/Cargo.toml | 2 +- substrate/frame/elections-phragmen/Cargo.toml | 2 +- substrate/frame/examples/basic/Cargo.toml | 2 +- substrate/frame/examples/default-config/Cargo.toml | 2 +- substrate/frame/examples/dev-mode/Cargo.toml | 2 +- substrate/frame/examples/kitchensink/Cargo.toml | 2 +- substrate/frame/examples/offchain-worker/Cargo.toml | 2 +- substrate/frame/examples/split/Cargo.toml | 2 +- substrate/frame/examples/tasks/Cargo.toml | 2 +- substrate/frame/executive/Cargo.toml | 2 +- substrate/frame/fast-unstake/Cargo.toml | 2 +- substrate/frame/glutton/Cargo.toml | 2 +- substrate/frame/grandpa/Cargo.toml | 2 +- substrate/frame/identity/Cargo.toml | 2 +- substrate/frame/im-online/Cargo.toml | 2 +- substrate/frame/membership/Cargo.toml | 2 +- substrate/frame/merkle-mountain-range/Cargo.toml | 2 +- substrate/frame/message-queue/Cargo.toml | 2 +- substrate/frame/mixnet/Cargo.toml | 2 +- substrate/frame/multisig/Cargo.toml | 2 +- substrate/frame/nft-fractionalization/Cargo.toml | 2 +- substrate/frame/nfts/Cargo.toml | 2 +- substrate/frame/node-authorization/Cargo.toml | 2 +- substrate/frame/nomination-pools/Cargo.toml | 2 +- substrate/frame/nomination-pools/fuzzer/Cargo.toml | 2 +- substrate/frame/nomination-pools/test-staking/Cargo.toml | 2 +- substrate/frame/offences/Cargo.toml | 2 +- substrate/frame/offences/benchmarking/Cargo.toml | 2 +- substrate/frame/preimage/Cargo.toml | 2 +- substrate/frame/ranked-collective/Cargo.toml | 2 +- substrate/frame/referenda/Cargo.toml | 2 +- substrate/frame/salary/Cargo.toml | 2 +- substrate/frame/sassafras/Cargo.toml | 2 +- substrate/frame/scheduler/Cargo.toml | 2 +- substrate/frame/session/Cargo.toml | 2 +- substrate/frame/society/Cargo.toml | 2 +- substrate/frame/staking/Cargo.toml | 2 +- substrate/frame/staking/reward-fn/Cargo.toml | 2 +- substrate/frame/state-trie-migration/Cargo.toml | 2 +- substrate/frame/statement/Cargo.toml | 2 +- substrate/frame/support/Cargo.toml | 2 +- substrate/frame/system/Cargo.toml | 2 +- substrate/frame/timestamp/Cargo.toml | 2 +- substrate/frame/tips/Cargo.toml | 2 +- substrate/frame/transaction-storage/Cargo.toml | 2 +- substrate/frame/uniques/Cargo.toml | 2 +- substrate/frame/vesting/Cargo.toml | 2 +- substrate/primitives/api/Cargo.toml | 2 +- substrate/primitives/api/test/Cargo.toml | 2 +- substrate/primitives/blockchain/Cargo.toml | 2 +- substrate/primitives/consensus/common/Cargo.toml | 2 +- substrate/primitives/consensus/grandpa/Cargo.toml | 2 +- substrate/primitives/core/Cargo.toml | 2 +- substrate/primitives/io/Cargo.toml | 2 +- substrate/primitives/merkle-mountain-range/Cargo.toml | 2 +- substrate/primitives/runtime/Cargo.toml | 2 +- substrate/primitives/state-machine/Cargo.toml | 2 +- substrate/primitives/wasm-interface/Cargo.toml | 2 +- substrate/test-utils/runtime/Cargo.toml | 2 +- substrate/utils/binary-merkle-tree/Cargo.toml | 2 +- substrate/utils/frame/benchmarking-cli/Cargo.toml | 2 +- substrate/utils/frame/remote-externalities/Cargo.toml | 2 +- substrate/utils/frame/rpc/client/Cargo.toml | 2 +- substrate/utils/frame/rpc/system/Cargo.toml | 2 +- substrate/utils/frame/try-runtime/cli/Cargo.toml | 2 +- substrate/utils/prometheus/Cargo.toml | 2 +- 200 files changed, 200 insertions(+), 199 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0a70bb03756..d09f19f280c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -536,6 +536,7 @@ default_constructed_unit_structs = { level = "allow", priority = 2 } # stylistic [workspace.dependencies] polkavm-linker = "0.8.2" polkavm-derive = "0.8.0" +log = { version = "0.4.20", default-features = false } [profile.release] # Polkadot runtime requires unwinding. diff --git a/bridges/bin/runtime-common/Cargo.toml b/bridges/bin/runtime-common/Cargo.toml index c0072064507..fac88b20ca5 100644 --- a/bridges/bin/runtime-common/Cargo.toml +++ b/bridges/bin/runtime-common/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive"] } hash-db = { version = "0.16.0", default-features = false } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } static_assertions = { version = "1.1", optional = true } diff --git a/bridges/modules/grandpa/Cargo.toml b/bridges/modules/grandpa/Cargo.toml index 562a6e43e02..dccd7b3bdca 100644 --- a/bridges/modules/grandpa/Cargo.toml +++ b/bridges/modules/grandpa/Cargo.toml @@ -14,7 +14,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } finality-grandpa = { version = "0.16.2", default-features = false } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } # Bridge Dependencies diff --git a/bridges/modules/messages/Cargo.toml b/bridges/modules/messages/Cargo.toml index 224aad7b36a..173d6f1c164 100644 --- a/bridges/modules/messages/Cargo.toml +++ b/bridges/modules/messages/Cargo.toml @@ -11,7 +11,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } num-traits = { version = "0.2", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } diff --git a/bridges/modules/parachains/Cargo.toml b/bridges/modules/parachains/Cargo.toml index 41aeca4b3bc..e454a6f2888 100644 --- a/bridges/modules/parachains/Cargo.toml +++ b/bridges/modules/parachains/Cargo.toml @@ -11,7 +11,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } # Bridge Dependencies diff --git a/bridges/modules/relayers/Cargo.toml b/bridges/modules/relayers/Cargo.toml index 35017ebbd30..b78da5cbeec 100644 --- a/bridges/modules/relayers/Cargo.toml +++ b/bridges/modules/relayers/Cargo.toml @@ -11,7 +11,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } # Bridge dependencies diff --git a/bridges/modules/xcm-bridge-hub-router/Cargo.toml b/bridges/modules/xcm-bridge-hub-router/Cargo.toml index ff33b19a581..20f8ff4407b 100644 --- a/bridges/modules/xcm-bridge-hub-router/Cargo.toml +++ b/bridges/modules/xcm-bridge-hub-router/Cargo.toml @@ -11,7 +11,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["bit-vec", "derive", "serde"] } # Bridge dependencies diff --git a/bridges/modules/xcm-bridge-hub/Cargo.toml b/bridges/modules/xcm-bridge-hub/Cargo.toml index 89e261dc6d7..e10119e8649 100644 --- a/bridges/modules/xcm-bridge-hub/Cargo.toml +++ b/bridges/modules/xcm-bridge-hub/Cargo.toml @@ -11,7 +11,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } # Bridge Dependencies diff --git a/bridges/primitives/runtime/Cargo.toml b/bridges/primitives/runtime/Cargo.toml index 6786bf8f21c..2ea31c26d6b 100644 --- a/bridges/primitives/runtime/Cargo.toml +++ b/bridges/primitives/runtime/Cargo.toml @@ -13,7 +13,7 @@ workspace = true codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } hash-db = { version = "0.16.0", default-features = false } impl-trait-for-tuples = "0.2.2" -log = { version = "0.4.19", default-features = false } +log = { workspace = true } num-traits = { version = "0.2", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } diff --git a/bridges/snowbridge/pallets/ethereum-client/Cargo.toml b/bridges/snowbridge/pallets/ethereum-client/Cargo.toml index 2f76d5b8357..dd7051fe3f0 100644 --- a/bridges/snowbridge/pallets/ethereum-client/Cargo.toml +++ b/bridges/snowbridge/pallets/ethereum-client/Cargo.toml @@ -24,7 +24,7 @@ ssz_rs_derive = { version = "0.9.0", default-features = false } byte-slice-cast = { version = "1.2.1", default-features = false } rlp = { version = "0.5.2", default-features = false } hex-literal = { version = "0.4.1", optional = true } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } frame-benchmarking = { path = "../../../../substrate/frame/benchmarking", default-features = false, optional = true } frame-support = { path = "../../../../substrate/frame/support", default-features = false } diff --git a/bridges/snowbridge/pallets/inbound-queue/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue/Cargo.toml index 1081b162dda..8756982ed7e 100644 --- a/bridges/snowbridge/pallets/inbound-queue/Cargo.toml +++ b/bridges/snowbridge/pallets/inbound-queue/Cargo.toml @@ -19,7 +19,7 @@ serde = { version = "1.0.195", optional = true } codec = { version = "3.6.1", package = "parity-scale-codec", default-features = false, features = ["derive"] } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1", optional = true } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } alloy-primitives = { version = "0.4.2", default-features = false, features = ["rlp"] } alloy-sol-types = { version = "0.4.2", default-features = false } alloy-rlp = { version = "0.3.3", default-features = false, features = ["derive"] } diff --git a/bridges/snowbridge/pallets/system/Cargo.toml b/bridges/snowbridge/pallets/system/Cargo.toml index aa600511633..f6c642e7376 100644 --- a/bridges/snowbridge/pallets/system/Cargo.toml +++ b/bridges/snowbridge/pallets/system/Cargo.toml @@ -22,7 +22,7 @@ scale-info = { version = "2.9.0", default-features = false, features = ["derive" frame-benchmarking = { path = "../../../../substrate/frame/benchmarking", default-features = false, optional = true } frame-support = { path = "../../../../substrate/frame/support", default-features = false } frame-system = { path = "../../../../substrate/frame/system", default-features = false } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } sp-core = { path = "../../../../substrate/primitives/core", default-features = false } sp-std = { path = "../../../../substrate/primitives/std", default-features = false } diff --git a/bridges/snowbridge/primitives/router/Cargo.toml b/bridges/snowbridge/primitives/router/Cargo.toml index 712c60c2148..3dbf5483e1e 100644 --- a/bridges/snowbridge/primitives/router/Cargo.toml +++ b/bridges/snowbridge/primitives/router/Cargo.toml @@ -15,7 +15,7 @@ workspace = true serde = { version = "1.0.195", optional = true, features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } frame-support = { path = "../../../../substrate/frame/support", default-features = false } frame-system = { path = "../../../../substrate/frame/system", default-features = false } diff --git a/bridges/snowbridge/runtime/runtime-common/Cargo.toml b/bridges/snowbridge/runtime/runtime-common/Cargo.toml index f5b44b25585..d4c86f8aa75 100644 --- a/bridges/snowbridge/runtime/runtime-common/Cargo.toml +++ b/bridges/snowbridge/runtime/runtime-common/Cargo.toml @@ -12,7 +12,7 @@ categories = ["cryptography::cryptocurrencies"] workspace = true [dependencies] -log = { version = "0.4.20", default-features = false } +log = { workspace = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } frame-support = { path = "../../../../substrate/frame/support", default-features = false } frame-system = { path = "../../../../substrate/frame/system", default-features = false } diff --git a/bridges/snowbridge/runtime/test-common/Cargo.toml b/bridges/snowbridge/runtime/test-common/Cargo.toml index a2994e61891..ecf0a6fcc69 100644 --- a/bridges/snowbridge/runtime/test-common/Cargo.toml +++ b/bridges/snowbridge/runtime/test-common/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1" } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } serde = { version = "1.0.195", optional = true, features = ["derive"] } smallvec = "1.11.0" diff --git a/cumulus/client/consensus/common/Cargo.toml b/cumulus/client/consensus/common/Cargo.toml index 7fee51310d0..5a014b10e35 100644 --- a/cumulus/client/consensus/common/Cargo.toml +++ b/cumulus/client/consensus/common/Cargo.toml @@ -14,7 +14,7 @@ async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } dyn-clone = "1.0.16" futures = "0.3.28" -log = "0.4.20" +log = { workspace = true, default-features = true } tracing = "0.1.37" # Substrate diff --git a/cumulus/pallets/collator-selection/Cargo.toml b/cumulus/pallets/collator-selection/Cargo.toml index 4216776fe8a..20f048b97d5 100644 --- a/cumulus/pallets/collator-selection/Cargo.toml +++ b/cumulus/pallets/collator-selection/Cargo.toml @@ -16,7 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = { version = "0.4.20", default-features = false } +log = { workspace = true } codec = { default-features = false, features = ["derive"], package = "parity-scale-codec", version = "3.0.0" } rand = { version = "0.8.5", features = ["std_rng"], default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } diff --git a/cumulus/pallets/dmp-queue/Cargo.toml b/cumulus/pallets/dmp-queue/Cargo.toml index 301a77003cb..83ed994d041 100644 --- a/cumulus/pallets/dmp-queue/Cargo.toml +++ b/cumulus/pallets/dmp-queue/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true } diff --git a/cumulus/pallets/parachain-system/Cargo.toml b/cumulus/pallets/parachain-system/Cargo.toml index 848efd3eab6..86647290563 100644 --- a/cumulus/pallets/parachain-system/Cargo.toml +++ b/cumulus/pallets/parachain-system/Cargo.toml @@ -14,7 +14,7 @@ bytes = { version = "1.4.0", default-features = false } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } environmental = { version = "1.1.4", default-features = false } impl-trait-for-tuples = "0.2.1" -log = { version = "0.4.20", default-features = false } +log = { workspace = true } trie-db = { version = "0.28.0", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } diff --git a/cumulus/pallets/xcmp-queue/Cargo.toml b/cumulus/pallets/xcmp-queue/Cargo.toml index 8dde44ca0ff..9078d5eda99 100644 --- a/cumulus/pallets/xcmp-queue/Cargo.toml +++ b/cumulus/pallets/xcmp-queue/Cargo.toml @@ -11,7 +11,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"], default-features = false } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } # Substrate diff --git a/cumulus/parachain-template/node/Cargo.toml b/cumulus/parachain-template/node/Cargo.toml index c66c96056b9..0ad07ac7aac 100644 --- a/cumulus/parachain-template/node/Cargo.toml +++ b/cumulus/parachain-template/node/Cargo.toml @@ -15,7 +15,7 @@ workspace = true [dependencies] clap = { version = "4.4.18", features = ["derive"] } -log = "0.4.20" +log = { workspace = true, default-features = true } codec = { package = "parity-scale-codec", version = "3.0.0" } serde = { version = "1.0.195", features = ["derive"] } jsonrpsee = { version = "0.20.3", features = ["server"] } diff --git a/cumulus/parachain-template/runtime/Cargo.toml b/cumulus/parachain-template/runtime/Cargo.toml index e4575d196c7..44d96ffc4e6 100644 --- a/cumulus/parachain-template/runtime/Cargo.toml +++ b/cumulus/parachain-template/runtime/Cargo.toml @@ -20,7 +20,7 @@ substrate-wasm-builder = { path = "../../../substrate/utils/wasm-builder", optio [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1", optional = true } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } smallvec = "1.11.0" diff --git a/cumulus/parachains/common/Cargo.toml b/cumulus/parachains/common/Cargo.toml index fe5e24ee607..ebc9f822beb 100644 --- a/cumulus/parachains/common/Cargo.toml +++ b/cumulus/parachains/common/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"], default-features = false } -log = { version = "0.4.19", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } # Substrate diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml index d1b302dc6d4..05936e93993 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml @@ -12,7 +12,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } hex-literal = { version = "0.4.1" } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } # Substrate diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml index 415c8c1cb06..78c48507a7a 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml @@ -12,7 +12,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } hex-literal = { version = "0.4.1", optional = true } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } # Substrate diff --git a/cumulus/parachains/runtimes/assets/common/Cargo.toml b/cumulus/parachains/runtimes/assets/common/Cargo.toml index 74e5e44ce15..c9252375cfb 100644 --- a/cumulus/parachains/runtimes/assets/common/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/common/Cargo.toml @@ -12,7 +12,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } impl-trait-for-tuples = "0.2.2" # Substrate diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index 920e7b1571c..88fb56a2f0b 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -17,7 +17,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = "derive", ] } hex-literal = { version = "0.4.1" } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = [ "derive", ] } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml index 4cbfde91d00..4ce77f8060c 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -15,7 +15,7 @@ substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1" } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } serde = { version = "1.0.195", optional = true, features = ["derive"] } diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml index f2cf60354ad..d34b5cd0eed 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml @@ -12,7 +12,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } impl-trait-for-tuples = "0.2" -log = { version = "0.4.20", default-features = false } +log = { workspace = true } # Substrate frame-support = { path = "../../../../../substrate/frame/support", default-features = false } diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml index c16cee4fedd..ed264f28c26 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml @@ -12,7 +12,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } hex-literal = { version = "0.4.1" } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } # Substrate diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml b/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml index 9f719421b9d..dcc6c4e853a 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml @@ -18,7 +18,7 @@ substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1", optional = true } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } # Substrate diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml index ae604da87a9..ef9869d727a 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml @@ -15,7 +15,7 @@ substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } hex-literal = "0.4.1" -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } serde = { version = "1.0.195", optional = true, features = ["derive"] } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml index f85f6896cbe..aae194f9ad8 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml @@ -15,7 +15,7 @@ substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } hex-literal = "0.4.1" -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } serde = { version = "1.0.195", optional = true, features = ["derive"] } diff --git a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml index 5e5e1e01606..b44d40c7013 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml @@ -13,7 +13,7 @@ substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } enumflags2 = { version = "0.7.7" } hex-literal = { version = "0.4.1" } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } serde = { version = "1.0.195", optional = true, features = ["derive"] } diff --git a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml index c4c76074d75..7295b22f5a3 100644 --- a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml @@ -13,7 +13,7 @@ substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } enumflags2 = { version = "0.7.7" } hex-literal = { version = "0.4.1" } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } serde = { version = "1.0.195", optional = true, features = ["derive"] } diff --git a/cumulus/parachains/runtimes/testing/penpal/Cargo.toml b/cumulus/parachains/runtimes/testing/penpal/Cargo.toml index dab687c5277..08e5987d43a 100644 --- a/cumulus/parachains/runtimes/testing/penpal/Cargo.toml +++ b/cumulus/parachains/runtimes/testing/penpal/Cargo.toml @@ -20,7 +20,7 @@ substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1", optional = true } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } smallvec = "1.11.0" diff --git a/cumulus/polkadot-parachain/Cargo.toml b/cumulus/polkadot-parachain/Cargo.toml index cc0bd47cc77..646efe3c5f5 100644 --- a/cumulus/polkadot-parachain/Cargo.toml +++ b/cumulus/polkadot-parachain/Cargo.toml @@ -20,7 +20,7 @@ clap = { version = "4.4.18", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.28" hex-literal = "0.4.1" -log = "0.4.20" +log = { workspace = true, default-features = true } serde = { version = "1.0.195", features = ["derive"] } serde_json = "1.0.111" diff --git a/cumulus/primitives/utility/Cargo.toml b/cumulus/primitives/utility/Cargo.toml index 27e9fbe3c7e..45c0e667094 100644 --- a/cumulus/primitives/utility/Cargo.toml +++ b/cumulus/primitives/utility/Cargo.toml @@ -11,7 +11,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } # Substrate frame-support = { path = "../../../substrate/frame/support", default-features = false } diff --git a/cumulus/xcm/xcm-emulator/Cargo.toml b/cumulus/xcm/xcm-emulator/Cargo.toml index fb37fd01765..6b45770a8e3 100644 --- a/cumulus/xcm/xcm-emulator/Cargo.toml +++ b/cumulus/xcm/xcm-emulator/Cargo.toml @@ -12,7 +12,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } paste = "1.0.14" -log = { version = "0.4.20", default-features = false } +log = { workspace = true } lazy_static = "1.4.0" impl-trait-for-tuples = "0.2.2" diff --git a/polkadot/cli/Cargo.toml b/polkadot/cli/Cargo.toml index 75afa2d199f..c5d9cb45baa 100644 --- a/polkadot/cli/Cargo.toml +++ b/polkadot/cli/Cargo.toml @@ -20,7 +20,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] cfg-if = "1.0" clap = { version = "4.4.18", features = ["derive"], optional = true } -log = "0.4.17" +log = { workspace = true, default-features = true } thiserror = "1.0.48" futures = "0.3.21" pyro = { package = "pyroscope", version = "0.5.3", optional = true } diff --git a/polkadot/node/core/approval-voting/Cargo.toml b/polkadot/node/core/approval-voting/Cargo.toml index 5ab823f4894..1010779a1b3 100644 --- a/polkadot/node/core/approval-voting/Cargo.toml +++ b/polkadot/node/core/approval-voting/Cargo.toml @@ -51,5 +51,5 @@ polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" } assert_matches = "1.4.0" kvdb-memorydb = "0.13.0" test-helpers = { package = "polkadot-primitives-test-helpers", path = "../../../primitives/test-helpers" } -log = "0.4.17" +log = { workspace = true, default-features = true } env_logger = "0.9.0" diff --git a/polkadot/node/core/av-store/Cargo.toml b/polkadot/node/core/av-store/Cargo.toml index 68769fb8cc8..4f5c18a68d6 100644 --- a/polkadot/node/core/av-store/Cargo.toml +++ b/polkadot/node/core/av-store/Cargo.toml @@ -28,7 +28,7 @@ sp-consensus = { path = "../../../../substrate/primitives/consensus/common", def polkadot-node-jaeger = { path = "../../jaeger" } [dev-dependencies] -log = "0.4.17" +log = { workspace = true, default-features = true } env_logger = "0.9.0" assert_matches = "1.4.0" kvdb-memorydb = "0.13.0" diff --git a/polkadot/node/jaeger/Cargo.toml b/polkadot/node/jaeger/Cargo.toml index 892292c714b..c8fd3d46283 100644 --- a/polkadot/node/jaeger/Cargo.toml +++ b/polkadot/node/jaeger/Cargo.toml @@ -19,5 +19,5 @@ sc-network = { path = "../../../substrate/client/network" } sp-core = { path = "../../../substrate/primitives/core" } thiserror = "1.0.48" tokio = "1.24.2" -log = "0.4.17" +log = { workspace = true, default-features = true } parity-scale-codec = { version = "3.6.1", default-features = false } diff --git a/polkadot/node/metrics/Cargo.toml b/polkadot/node/metrics/Cargo.toml index 7d67c0a58d3..c567278f70e 100644 --- a/polkadot/node/metrics/Cargo.toml +++ b/polkadot/node/metrics/Cargo.toml @@ -24,7 +24,7 @@ sc-tracing = { path = "../../../substrate/client/tracing" } codec = { package = "parity-scale-codec", version = "3.6.1" } primitives = { package = "polkadot-primitives", path = "../../primitives" } bs58 = { version = "0.5.0", features = ["alloc"] } -log = "0.4.17" +log = { workspace = true, default-features = true } [dev-dependencies] assert_cmd = "2.0.4" diff --git a/polkadot/node/network/approval-distribution/Cargo.toml b/polkadot/node/network/approval-distribution/Cargo.toml index a1fa803abc2..2bc09c5f42a 100644 --- a/polkadot/node/network/approval-distribution/Cargo.toml +++ b/polkadot/node/network/approval-distribution/Cargo.toml @@ -38,4 +38,4 @@ schnorrkel = { version = "0.11.4", default-features = false } rand_core = "0.6.2" rand_chacha = "0.3.1" env_logger = "0.9.0" -log = "0.4.17" +log = { workspace = true, default-features = true } diff --git a/polkadot/node/network/availability-recovery/Cargo.toml b/polkadot/node/network/availability-recovery/Cargo.toml index 9f1d9052312..e86445730ff 100644 --- a/polkadot/node/network/availability-recovery/Cargo.toml +++ b/polkadot/node/network/availability-recovery/Cargo.toml @@ -32,7 +32,7 @@ sc-network = { path = "../../../../substrate/client/network" } assert_matches = "1.4.0" env_logger = "0.9.0" futures-timer = "3.0.2" -log = "0.4.17" +log = { workspace = true, default-features = true } sp-core = { path = "../../../../substrate/primitives/core" } sp-keyring = { path = "../../../../substrate/primitives/keyring" } diff --git a/polkadot/node/network/bitfield-distribution/Cargo.toml b/polkadot/node/network/bitfield-distribution/Cargo.toml index 8a05bcbd493..0ddb5f643b8 100644 --- a/polkadot/node/network/bitfield-distribution/Cargo.toml +++ b/polkadot/node/network/bitfield-distribution/Cargo.toml @@ -29,7 +29,7 @@ sp-authority-discovery = { path = "../../../../substrate/primitives/authority-di sp-keystore = { path = "../../../../substrate/primitives/keystore" } sp-keyring = { path = "../../../../substrate/primitives/keyring" } maplit = "1.0.2" -log = "0.4.17" +log = { workspace = true, default-features = true } env_logger = "0.9.0" assert_matches = "1.4.0" rand_chacha = "0.3.1" diff --git a/polkadot/node/network/collator-protocol/Cargo.toml b/polkadot/node/network/collator-protocol/Cargo.toml index 9a9ce6fce63..f88e8182ecc 100644 --- a/polkadot/node/network/collator-protocol/Cargo.toml +++ b/polkadot/node/network/collator-protocol/Cargo.toml @@ -29,7 +29,7 @@ thiserror = "1.0.48" tokio-util = "0.7.1" [dev-dependencies] -log = "0.4.17" +log = { workspace = true, default-features = true } env_logger = "0.9.0" assert_matches = "1.4.0" diff --git a/polkadot/node/service/Cargo.toml b/polkadot/node/service/Cargo.toml index 007e3dc595e..5b73d420e3b 100644 --- a/polkadot/node/service/Cargo.toml +++ b/polkadot/node/service/Cargo.toml @@ -83,7 +83,7 @@ futures = "0.3.21" hex-literal = "0.4.1" is_executable = "1.0.1" gum = { package = "tracing-gum", path = "../gum" } -log = "0.4.17" +log = { workspace = true, default-features = true } schnellru = "0.2.1" serde = { version = "1.0.195", features = ["derive"] } serde_json = "1.0.111" diff --git a/polkadot/node/subsystem-bench/Cargo.toml b/polkadot/node/subsystem-bench/Cargo.toml index 136eccbf685..bc038abd648 100644 --- a/polkadot/node/subsystem-bench/Cargo.toml +++ b/polkadot/node/subsystem-bench/Cargo.toml @@ -43,7 +43,7 @@ sha1 = "0.10.6" hex = "0.4.3" gum = { package = "tracing-gum", path = "../gum" } polkadot-erasure-coding = { package = "polkadot-erasure-coding", path = "../../erasure-coding" } -log = "0.4.17" +log = { workspace = true, default-features = true } env_logger = "0.9.0" rand = "0.8.5" # `rand` only supports uniform distribution, we need normal distribution for latency. diff --git a/polkadot/node/subsystem-util/Cargo.toml b/polkadot/node/subsystem-util/Cargo.toml index 5f615e05abd..b2a643d92f6 100644 --- a/polkadot/node/subsystem-util/Cargo.toml +++ b/polkadot/node/subsystem-util/Cargo.toml @@ -46,7 +46,7 @@ parity-db = { version = "0.4.12" } assert_matches = "1.4.0" env_logger = "0.9.0" futures = { version = "0.3.21", features = ["thread-pool"] } -log = "0.4.17" +log = { workspace = true, default-features = true } polkadot-node-subsystem-test-helpers = { path = "../subsystem-test-helpers" } lazy_static = "1.4.0" polkadot-primitives-test-helpers = { path = "../../primitives/test-helpers" } diff --git a/polkadot/parachain/test-parachains/adder/collator/Cargo.toml b/polkadot/parachain/test-parachains/adder/collator/Cargo.toml index 7dd0d9a563c..c2b11aadb50 100644 --- a/polkadot/parachain/test-parachains/adder/collator/Cargo.toml +++ b/polkadot/parachain/test-parachains/adder/collator/Cargo.toml @@ -19,7 +19,7 @@ parity-scale-codec = { version = "3.6.1", default-features = false, features = [ clap = { version = "4.4.18", features = ["derive"] } futures = "0.3.21" futures-timer = "3.0.2" -log = "0.4.17" +log = { workspace = true, default-features = true } test-parachain-adder = { path = ".." } polkadot-primitives = { path = "../../../../primitives" } diff --git a/polkadot/parachain/test-parachains/undying/Cargo.toml b/polkadot/parachain/test-parachains/undying/Cargo.toml index 19e1261db1e..82ceebcf4ee 100644 --- a/polkadot/parachain/test-parachains/undying/Cargo.toml +++ b/polkadot/parachain/test-parachains/undying/Cargo.toml @@ -17,7 +17,7 @@ parity-scale-codec = { version = "3.6.1", default-features = false, features = [ sp-std = { path = "../../../../substrate/primitives/std", default-features = false } tiny-keccak = { version = "2.0.2", features = ["keccak"] } dlmalloc = { version = "0.2.4", features = ["global"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } # We need to make sure the global allocator is disabled until we have support of full substrate externalities sp-io = { path = "../../../../substrate/primitives/io", default-features = false, features = ["disable_allocator"] } diff --git a/polkadot/parachain/test-parachains/undying/collator/Cargo.toml b/polkadot/parachain/test-parachains/undying/collator/Cargo.toml index 001c48476b5..c74b01be084 100644 --- a/polkadot/parachain/test-parachains/undying/collator/Cargo.toml +++ b/polkadot/parachain/test-parachains/undying/collator/Cargo.toml @@ -19,7 +19,7 @@ parity-scale-codec = { version = "3.6.1", default-features = false, features = [ clap = { version = "4.4.18", features = ["derive"] } futures = "0.3.21" futures-timer = "3.0.2" -log = "0.4.17" +log = { workspace = true, default-features = true } test-parachain-undying = { path = ".." } polkadot-primitives = { path = "../../../../primitives" } diff --git a/polkadot/runtime/common/Cargo.toml b/polkadot/runtime/common/Cargo.toml index acbc845c41a..7257ca1d6d6 100644 --- a/polkadot/runtime/common/Cargo.toml +++ b/polkadot/runtime/common/Cargo.toml @@ -13,7 +13,7 @@ workspace = true impl-trait-for-tuples = "0.2.2" bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } rustc-hex = { version = "2.1.0", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } serde = { version = "1.0.195", default-features = false, features = ["alloc"] } diff --git a/polkadot/runtime/parachains/Cargo.toml b/polkadot/runtime/parachains/Cargo.toml index 507df01ac48..12cebe8e79d 100644 --- a/polkadot/runtime/parachains/Cargo.toml +++ b/polkadot/runtime/parachains/Cargo.toml @@ -13,7 +13,7 @@ workspace = true impl-trait-for-tuples = "0.2.2" bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } rustc-hex = { version = "2.1.0", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"] } diff --git a/polkadot/runtime/rococo/Cargo.toml b/polkadot/runtime/rococo/Cargo.toml index 11be7a9ffc4..6b718e824be 100644 --- a/polkadot/runtime/rococo/Cargo.toml +++ b/polkadot/runtime/rococo/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } serde = { version = "1.0.195", default-features = false } serde_derive = { version = "1.0.117", optional = true } static_assertions = "1.1.0" diff --git a/polkadot/runtime/test-runtime/Cargo.toml b/polkadot/runtime/test-runtime/Cargo.toml index 9778ff82439..3eca55a73d7 100644 --- a/polkadot/runtime/test-runtime/Cargo.toml +++ b/polkadot/runtime/test-runtime/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } rustc-hex = { version = "2.1.0", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } serde = { version = "1.0.195", default-features = false } diff --git a/polkadot/runtime/westend/Cargo.toml b/polkadot/runtime/westend/Cargo.toml index 0ea2436b681..77acb1b8565 100644 --- a/polkadot/runtime/westend/Cargo.toml +++ b/polkadot/runtime/westend/Cargo.toml @@ -14,7 +14,7 @@ workspace = true bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } rustc-hex = { version = "2.1.0", default-features = false } serde = { version = "1.0.195", default-features = false } serde_derive = { version = "1.0.117", optional = true } diff --git a/polkadot/utils/remote-ext-tests/bags-list/Cargo.toml b/polkadot/utils/remote-ext-tests/bags-list/Cargo.toml index f8190e6aefa..a300f0fca1c 100644 --- a/polkadot/utils/remote-ext-tests/bags-list/Cargo.toml +++ b/polkadot/utils/remote-ext-tests/bags-list/Cargo.toml @@ -19,5 +19,5 @@ frame-system = { path = "../../../../substrate/frame/system" } sp-core = { path = "../../../../substrate/primitives/core" } clap = { version = "4.4.18", features = ["derive"] } -log = "0.4.17" +log = { workspace = true, default-features = true } tokio = { version = "1.24.2", features = ["macros"] } diff --git a/polkadot/xcm/Cargo.toml b/polkadot/xcm/Cargo.toml index 41b79051bbf..b89a5c5c870 100644 --- a/polkadot/xcm/Cargo.toml +++ b/polkadot/xcm/Cargo.toml @@ -14,7 +14,7 @@ array-bytes = "6.1" bounded-collections = { version = "0.2.0", default-features = false, features = ["serde"] } derivative = { version = "2.2.0", default-features = false, features = ["use_core"] } impl-trait-for-tuples = "0.2.2" -log = { version = "0.4.17", default-features = false } +log = { workspace = true } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } sp-weights = { path = "../../substrate/primitives/weights", default-features = false, features = ["serde"] } diff --git a/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml b/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml index adeacddf90c..80f2d1deedf 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml +++ b/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml @@ -24,7 +24,7 @@ xcm-executor = { package = "staging-xcm-executor", path = "../xcm-executor", def frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false } xcm = { package = "staging-xcm", path = "..", default-features = false } xcm-builder = { package = "staging-xcm-builder", path = "../xcm-builder", default-features = false } -log = "0.4.17" +log = { workspace = true, default-features = true } [dev-dependencies] pallet-balances = { path = "../../../substrate/frame/balances" } diff --git a/polkadot/xcm/pallet-xcm/Cargo.toml b/polkadot/xcm/pallet-xcm/Cargo.toml index dc9b3c0e20d..aa15d5edc82 100644 --- a/polkadot/xcm/pallet-xcm/Cargo.toml +++ b/polkadot/xcm/pallet-xcm/Cargo.toml @@ -14,7 +14,7 @@ bounded-collections = { version = "0.2.0", default-features = false } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } serde = { version = "1.0.195", optional = true, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } frame-support = { path = "../../../substrate/frame/support", default-features = false } frame-system = { path = "../../../substrate/frame/system", default-features = false } diff --git a/polkadot/xcm/xcm-builder/Cargo.toml b/polkadot/xcm/xcm-builder/Cargo.toml index 30010fc2105..10726b0f511 100644 --- a/polkadot/xcm/xcm-builder/Cargo.toml +++ b/polkadot/xcm/xcm-builder/Cargo.toml @@ -23,7 +23,7 @@ sp-weights = { path = "../../../substrate/primitives/weights", default-features frame-support = { path = "../../../substrate/frame/support", default-features = false } frame-system = { path = "../../../substrate/frame/system", default-features = false } pallet-transaction-payment = { path = "../../../substrate/frame/transaction-payment", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } # Polkadot dependencies polkadot-parachain-primitives = { path = "../../parachain", default-features = false } diff --git a/polkadot/xcm/xcm-executor/Cargo.toml b/polkadot/xcm/xcm-executor/Cargo.toml index 7ce4a1cc171..71bd58073db 100644 --- a/polkadot/xcm/xcm-executor/Cargo.toml +++ b/polkadot/xcm/xcm-executor/Cargo.toml @@ -22,7 +22,7 @@ sp-core = { path = "../../../substrate/primitives/core", default-features = fals sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } sp-weights = { path = "../../../substrate/primitives/weights", default-features = false } frame-support = { path = "../../../substrate/frame/support", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true } [features] diff --git a/polkadot/xcm/xcm-simulator/example/Cargo.toml b/polkadot/xcm/xcm-simulator/example/Cargo.toml index 9cb5b6b7eeb..af471df60ab 100644 --- a/polkadot/xcm/xcm-simulator/example/Cargo.toml +++ b/polkadot/xcm/xcm-simulator/example/Cargo.toml @@ -12,7 +12,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } scale-info = { version = "2.10.0", features = ["derive"] } -log = { version = "0.4.14", default-features = false } +log = { workspace = true } frame-system = { path = "../../../../substrate/frame/system" } frame-support = { path = "../../../../substrate/frame/support" } diff --git a/substrate/bin/node/bench/Cargo.toml b/substrate/bin/node/bench/Cargo.toml index 42af802d716..78cdaeb0ceb 100644 --- a/substrate/bin/node/bench/Cargo.toml +++ b/substrate/bin/node/bench/Cargo.toml @@ -17,7 +17,7 @@ workspace = true [dependencies] array-bytes = "6.1" clap = { version = "4.4.18", features = ["derive"] } -log = "0.4.17" +log = { workspace = true, default-features = true } node-primitives = { path = "../primitives" } node-testing = { path = "../testing" } kitchensink-runtime = { path = "../runtime" } diff --git a/substrate/bin/node/cli/Cargo.toml b/substrate/bin/node/cli/Cargo.toml index 5dfe915b789..efdbd0f5b2d 100644 --- a/substrate/bin/node/cli/Cargo.toml +++ b/substrate/bin/node/cli/Cargo.toml @@ -46,7 +46,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1" } serde = { version = "1.0.195", features = ["derive"] } jsonrpsee = { version = "0.20.3", features = ["server"] } futures = "0.3.21" -log = "0.4.17" +log = { workspace = true, default-features = true } rand = "0.8" # primitives diff --git a/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml index 76e32f02f0a..b82358b7c29 100644 --- a/substrate/bin/node/runtime/Cargo.toml +++ b/substrate/bin/node/runtime/Cargo.toml @@ -25,7 +25,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = ] } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } static_assertions = "1.1.0" -log = { version = "0.4.17", default-features = false } +log = { workspace = true } serde_json = { version = "1.0.111", default-features = false, features = ["alloc", "arbitrary_precision"] } # pallet-asset-conversion: turn on "num-traits" feature diff --git a/substrate/bin/node/testing/Cargo.toml b/substrate/bin/node/testing/Cargo.toml index 9ca8b8ef726..31f8689d46c 100644 --- a/substrate/bin/node/testing/Cargo.toml +++ b/substrate/bin/node/testing/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1" } fs_extra = "1" futures = "0.3.21" -log = "0.4.17" +log = { workspace = true, default-features = true } tempfile = "3.1.0" frame-system = { path = "../../../frame/system" } node-cli = { package = "staging-node-cli", path = "../cli" } diff --git a/substrate/bin/utils/chain-spec-builder/Cargo.toml b/substrate/bin/utils/chain-spec-builder/Cargo.toml index e39c9898380..df4ddf33311 100644 --- a/substrate/bin/utils/chain-spec-builder/Cargo.toml +++ b/substrate/bin/utils/chain-spec-builder/Cargo.toml @@ -24,7 +24,7 @@ crate-type = ["rlib"] [dependencies] clap = { version = "4.4.18", features = ["derive"] } -log = "0.4.17" +log = { workspace = true, default-features = true } sc-chain-spec = { path = "../../../client/chain-spec" } serde_json = "1.0.111" sp-tracing = { path = "../../../primitives/tracing" } diff --git a/substrate/client/allocator/Cargo.toml b/substrate/client/allocator/Cargo.toml index f882cda9081..662125814fb 100644 --- a/substrate/client/allocator/Cargo.toml +++ b/substrate/client/allocator/Cargo.toml @@ -17,7 +17,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = "0.4.17" +log = { workspace = true, default-features = true } thiserror = "1.0.48" sp-core = { path = "../../primitives/core" } sp-wasm-interface = { path = "../../primitives/wasm-interface" } diff --git a/substrate/client/api/Cargo.toml b/substrate/client/api/Cargo.toml index 14aca6d9a2e..09f60a47670 100644 --- a/substrate/client/api/Cargo.toml +++ b/substrate/client/api/Cargo.toml @@ -22,7 +22,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = ] } fnv = "1.0.6" futures = "0.3.21" -log = "0.4.17" +log = { workspace = true, default-features = true } parking_lot = "0.12.1" prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } sc-executor = { path = "../executor" } diff --git a/substrate/client/authority-discovery/Cargo.toml b/substrate/client/authority-discovery/Cargo.toml index 4c8370233e9..78f7144f436 100644 --- a/substrate/client/authority-discovery/Cargo.toml +++ b/substrate/client/authority-discovery/Cargo.toml @@ -29,7 +29,7 @@ multihash = { version = "0.18.1", default-features = false, features = [ "sha2", "std", ] } -log = "0.4.17" +log = { workspace = true, default-features = true } prost = "0.12" rand = "0.8.5" thiserror = "1.0" diff --git a/substrate/client/basic-authorship/Cargo.toml b/substrate/client/basic-authorship/Cargo.toml index 370f4a4adf5..51a06464d0d 100644 --- a/substrate/client/basic-authorship/Cargo.toml +++ b/substrate/client/basic-authorship/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" futures-timer = "3.0.1" -log = "0.4.17" +log = { workspace = true, default-features = true } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } sc-block-builder = { path = "../block-builder" } sc-proposer-metrics = { path = "../proposer-metrics" } diff --git a/substrate/client/chain-spec/Cargo.toml b/substrate/client/chain-spec/Cargo.toml index 9ab12dc2ad5..e8ad9e35092 100644 --- a/substrate/client/chain-spec/Cargo.toml +++ b/substrate/client/chain-spec/Cargo.toml @@ -32,7 +32,7 @@ sp-crypto-hashing = { path = "../../primitives/crypto/hashing" } sp-genesis-builder = { path = "../../primitives/genesis-builder" } sp-runtime = { path = "../../primitives/runtime" } sp-state-machine = { path = "../../primitives/state-machine" } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } array-bytes = { version = "6.1" } docify = "0.2.7" diff --git a/substrate/client/cli/Cargo.toml b/substrate/client/cli/Cargo.toml index 2d9c2fa5ffb..50fb57bf1ff 100644 --- a/substrate/client/cli/Cargo.toml +++ b/substrate/client/cli/Cargo.toml @@ -23,7 +23,7 @@ fdlimit = "0.3.0" futures = "0.3.21" itertools = "0.10.3" libp2p-identity = { version = "0.1.3", features = ["ed25519", "peerid"] } -log = "0.4.17" +log = { workspace = true, default-features = true } names = { version = "0.14.0", default-features = false } parity-scale-codec = "3.6.1" rand = "0.8.5" diff --git a/substrate/client/consensus/aura/Cargo.toml b/substrate/client/consensus/aura/Cargo.toml index 33f7d160d81..d1076da35f9 100644 --- a/substrate/client/consensus/aura/Cargo.toml +++ b/substrate/client/consensus/aura/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" -log = "0.4.17" +log = { workspace = true, default-features = true } thiserror = "1.0" prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-block-builder = { path = "../../block-builder" } diff --git a/substrate/client/consensus/babe/Cargo.toml b/substrate/client/consensus/babe/Cargo.toml index 01c5d062d61..9bfed7e541b 100644 --- a/substrate/client/consensus/babe/Cargo.toml +++ b/substrate/client/consensus/babe/Cargo.toml @@ -20,7 +20,7 @@ targets = ["x86_64-unknown-linux-gnu"] async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } futures = "0.3.21" -log = "0.4.17" +log = { workspace = true, default-features = true } num-bigint = "0.4.3" num-rational = "0.4.1" num-traits = "0.2.17" diff --git a/substrate/client/consensus/beefy/Cargo.toml b/substrate/client/consensus/beefy/Cargo.toml index 56c38bf2e47..e76d9ec826e 100644 --- a/substrate/client/consensus/beefy/Cargo.toml +++ b/substrate/client/consensus/beefy/Cargo.toml @@ -18,7 +18,7 @@ async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } fnv = "1.0.6" futures = "0.3" -log = "0.4" +log = { workspace = true, default-features = true } parking_lot = "0.12.1" thiserror = "1.0" wasm-timer = "0.2.5" diff --git a/substrate/client/consensus/beefy/rpc/Cargo.toml b/substrate/client/consensus/beefy/rpc/Cargo.toml index 496aefac113..810959dad31 100644 --- a/substrate/client/consensus/beefy/rpc/Cargo.toml +++ b/substrate/client/consensus/beefy/rpc/Cargo.toml @@ -15,7 +15,7 @@ workspace = true codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } futures = "0.3.21" jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] } -log = "0.4" +log = { workspace = true, default-features = true } parking_lot = "0.12.1" serde = { version = "1.0.195", features = ["derive"] } thiserror = "1.0" diff --git a/substrate/client/consensus/common/Cargo.toml b/substrate/client/consensus/common/Cargo.toml index 16d3a4a1441..496e6c82740 100644 --- a/substrate/client/consensus/common/Cargo.toml +++ b/substrate/client/consensus/common/Cargo.toml @@ -20,7 +20,7 @@ async-trait = "0.1.74" futures = { version = "0.3.21", features = ["thread-pool"] } futures-timer = "3.0.1" libp2p-identity = { version = "0.1.3", features = ["ed25519", "peerid"] } -log = "0.4.17" +log = { workspace = true, default-features = true } mockall = "0.11.3" parking_lot = "0.12.1" serde = { version = "1.0", features = ["derive"] } diff --git a/substrate/client/consensus/grandpa/Cargo.toml b/substrate/client/consensus/grandpa/Cargo.toml index 3f7b48d9f2d..1d02fbb2c88 100644 --- a/substrate/client/consensus/grandpa/Cargo.toml +++ b/substrate/client/consensus/grandpa/Cargo.toml @@ -24,7 +24,7 @@ dyn-clone = "1.0" finality-grandpa = { version = "0.16.2", features = ["derive-codec"] } futures = "0.3.21" futures-timer = "3.0.1" -log = "0.4.17" +log = { workspace = true, default-features = true } parity-scale-codec = { version = "3.6.1", features = ["derive"] } parking_lot = "0.12.1" rand = "0.8.5" diff --git a/substrate/client/consensus/grandpa/rpc/Cargo.toml b/substrate/client/consensus/grandpa/rpc/Cargo.toml index ae9ab5d9d07..e6f884336b5 100644 --- a/substrate/client/consensus/grandpa/rpc/Cargo.toml +++ b/substrate/client/consensus/grandpa/rpc/Cargo.toml @@ -16,7 +16,7 @@ workspace = true finality-grandpa = { version = "0.16.2", features = ["derive-codec"] } futures = "0.3.16" jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] } -log = "0.4.8" +log = { workspace = true, default-features = true } parity-scale-codec = { version = "3.6.1", features = ["derive"] } serde = { version = "1.0.195", features = ["derive"] } thiserror = "1.0" diff --git a/substrate/client/consensus/manual-seal/Cargo.toml b/substrate/client/consensus/manual-seal/Cargo.toml index 0094fb87800..80eeac4dd1d 100644 --- a/substrate/client/consensus/manual-seal/Cargo.toml +++ b/substrate/client/consensus/manual-seal/Cargo.toml @@ -22,7 +22,7 @@ async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" futures-timer = "3.0.1" -log = "0.4.17" +log = { workspace = true, default-features = true } serde = { version = "1.0", features = ["derive"] } thiserror = "1.0" prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } diff --git a/substrate/client/consensus/pow/Cargo.toml b/substrate/client/consensus/pow/Cargo.toml index c59a6a27114..6caccb9879d 100644 --- a/substrate/client/consensus/pow/Cargo.toml +++ b/substrate/client/consensus/pow/Cargo.toml @@ -20,7 +20,7 @@ async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } futures = "0.3.21" futures-timer = "3.0.1" -log = "0.4.17" +log = { workspace = true, default-features = true } parking_lot = "0.12.1" thiserror = "1.0" prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } diff --git a/substrate/client/consensus/slots/Cargo.toml b/substrate/client/consensus/slots/Cargo.toml index 8eed24532c9..75f8b29a2fd 100644 --- a/substrate/client/consensus/slots/Cargo.toml +++ b/substrate/client/consensus/slots/Cargo.toml @@ -21,7 +21,7 @@ async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" futures-timer = "3.0.1" -log = "0.4.17" +log = { workspace = true, default-features = true } sc-client-api = { path = "../../api" } sc-consensus = { path = "../common" } sc-telemetry = { path = "../../telemetry" } diff --git a/substrate/client/db/Cargo.toml b/substrate/client/db/Cargo.toml index ed7b4178f28..57ee1a8ad33 100644 --- a/substrate/client/db/Cargo.toml +++ b/substrate/client/db/Cargo.toml @@ -24,7 +24,7 @@ kvdb = "0.13.0" kvdb-memorydb = "0.13.0" kvdb-rocksdb = { version = "0.19.0", optional = true } linked-hash-map = "0.5.4" -log = "0.4.17" +log = { workspace = true, default-features = true } parity-db = "0.4.12" parking_lot = "0.12.1" sc-client-api = { path = "../api" } diff --git a/substrate/client/executor/wasmtime/Cargo.toml b/substrate/client/executor/wasmtime/Cargo.toml index 12e6647c695..75cc76a2354 100644 --- a/substrate/client/executor/wasmtime/Cargo.toml +++ b/substrate/client/executor/wasmtime/Cargo.toml @@ -16,7 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = "0.4.17" +log = { workspace = true, default-features = true } cfg-if = "1.0" libc = "0.2.152" parking_lot = "0.12.1" diff --git a/substrate/client/informant/Cargo.toml b/substrate/client/informant/Cargo.toml index 0252e9a1157..bd15e94ebaf 100644 --- a/substrate/client/informant/Cargo.toml +++ b/substrate/client/informant/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] ansi_term = "0.12.1" futures = "0.3.21" futures-timer = "3.0.1" -log = "0.4.17" +log = { workspace = true, default-features = true } sc-client-api = { path = "../api" } sc-network-common = { path = "../network/common" } sc-network-sync = { path = "../network/sync" } diff --git a/substrate/client/merkle-mountain-range/Cargo.toml b/substrate/client/merkle-mountain-range/Cargo.toml index 201c179f302..60232bccb0e 100644 --- a/substrate/client/merkle-mountain-range/Cargo.toml +++ b/substrate/client/merkle-mountain-range/Cargo.toml @@ -16,7 +16,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3" -log = "0.4" +log = { workspace = true, default-features = true } sp-api = { path = "../../primitives/api" } sp-blockchain = { path = "../../primitives/blockchain" } sc-client-api = { path = "../api" } diff --git a/substrate/client/mixnet/Cargo.toml b/substrate/client/mixnet/Cargo.toml index 280af81b86e..8bcd963bd56 100644 --- a/substrate/client/mixnet/Cargo.toml +++ b/substrate/client/mixnet/Cargo.toml @@ -24,7 +24,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = futures = "0.3.25" futures-timer = "3.0.2" libp2p-identity = { version = "0.1.3", features = ["peerid"] } -log = "0.4.17" +log = { workspace = true, default-features = true } mixnet = "0.7.0" multiaddr = "0.17.1" parking_lot = "0.12.1" diff --git a/substrate/client/network-gossip/Cargo.toml b/substrate/client/network-gossip/Cargo.toml index baf4def0b8e..a14761c0d6e 100644 --- a/substrate/client/network-gossip/Cargo.toml +++ b/substrate/client/network-gossip/Cargo.toml @@ -21,7 +21,7 @@ ahash = "0.8.2" futures = "0.3.21" futures-timer = "3.0.1" libp2p = "0.51.4" -log = "0.4.17" +log = { workspace = true, default-features = true } schnellru = "0.2.1" tracing = "0.1.29" prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } diff --git a/substrate/client/network/Cargo.toml b/substrate/client/network/Cargo.toml index 167f112705b..79f5a6b4fad 100644 --- a/substrate/client/network/Cargo.toml +++ b/substrate/client/network/Cargo.toml @@ -30,7 +30,7 @@ futures-timer = "3.0.2" ip_network = "0.4.1" libp2p = { version = "0.51.4", features = ["dns", "identify", "kad", "macros", "mdns", "noise", "ping", "request-response", "tcp", "tokio", "websocket", "yamux"] } linked_hash_set = "0.1.3" -log = "0.4.17" +log = { workspace = true, default-features = true } mockall = "0.11.3" parking_lot = "0.12.1" partial_sort = "0.2.0" diff --git a/substrate/client/network/bitswap/Cargo.toml b/substrate/client/network/bitswap/Cargo.toml index 9982ef80cf6..63544b069e5 100644 --- a/substrate/client/network/bitswap/Cargo.toml +++ b/substrate/client/network/bitswap/Cargo.toml @@ -23,7 +23,7 @@ async-channel = "1.8.0" cid = "0.9.0" futures = "0.3.21" libp2p-identity = { version = "0.1.3", features = ["peerid"] } -log = "0.4.17" +log = { workspace = true, default-features = true } prost = "0.12" thiserror = "1.0" unsigned-varint = { version = "0.7.1", features = ["asynchronous_codec", "futures"] } diff --git a/substrate/client/network/light/Cargo.toml b/substrate/client/network/light/Cargo.toml index efefc6f18b6..a83be7538be 100644 --- a/substrate/client/network/light/Cargo.toml +++ b/substrate/client/network/light/Cargo.toml @@ -26,7 +26,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", features = [ ] } futures = "0.3.21" libp2p-identity = { version = "0.1.3", features = ["peerid"] } -log = "0.4.16" +log = { workspace = true, default-features = true } prost = "0.12" sp-blockchain = { path = "../../../primitives/blockchain" } sc-client-api = { path = "../../api" } diff --git a/substrate/client/network/statement/Cargo.toml b/substrate/client/network/statement/Cargo.toml index 0a0ce61527d..b6efee5d9d3 100644 --- a/substrate/client/network/statement/Cargo.toml +++ b/substrate/client/network/statement/Cargo.toml @@ -21,7 +21,7 @@ async-channel = "1.8.0" codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } futures = "0.3.21" libp2p = "0.51.4" -log = "0.4.17" +log = { workspace = true, default-features = true } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-network-common = { path = "../common" } sc-network-sync = { path = "../sync" } diff --git a/substrate/client/network/sync/Cargo.toml b/substrate/client/network/sync/Cargo.toml index f81b4ee77bd..19f86e87ac5 100644 --- a/substrate/client/network/sync/Cargo.toml +++ b/substrate/client/network/sync/Cargo.toml @@ -26,7 +26,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive futures = "0.3.21" futures-timer = "3.0.2" libp2p = "0.51.4" -log = "0.4.17" +log = { workspace = true, default-features = true } mockall = "0.11.3" prost = "0.12" schnellru = "0.2.1" diff --git a/substrate/client/network/test/Cargo.toml b/substrate/client/network/test/Cargo.toml index dced6ed6730..4f57287a39c 100644 --- a/substrate/client/network/test/Cargo.toml +++ b/substrate/client/network/test/Cargo.toml @@ -21,7 +21,7 @@ async-trait = "0.1.74" futures = "0.3.21" futures-timer = "3.0.1" libp2p = "0.51.4" -log = "0.4.17" +log = { workspace = true, default-features = true } parking_lot = "0.12.1" rand = "0.8.5" sc-block-builder = { path = "../../block-builder" } diff --git a/substrate/client/network/transactions/Cargo.toml b/substrate/client/network/transactions/Cargo.toml index 9e021059eb3..01c8ac8814d 100644 --- a/substrate/client/network/transactions/Cargo.toml +++ b/substrate/client/network/transactions/Cargo.toml @@ -20,7 +20,7 @@ array-bytes = "6.1" codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } futures = "0.3.21" libp2p = "0.51.4" -log = "0.4.17" +log = { workspace = true, default-features = true } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-network = { path = ".." } sc-network-common = { path = "../common" } diff --git a/substrate/client/offchain/Cargo.toml b/substrate/client/offchain/Cargo.toml index 52052371286..eef385a6dd5 100644 --- a/substrate/client/offchain/Cargo.toml +++ b/substrate/client/offchain/Cargo.toml @@ -42,7 +42,7 @@ sp-offchain = { path = "../../primitives/offchain" } sp-runtime = { path = "../../primitives/runtime" } sp-keystore = { path = "../../primitives/keystore" } sp-externalities = { path = "../../primitives/externalities" } -log = "0.4.17" +log = { workspace = true, default-features = true } [dev-dependencies] lazy_static = "1.4.0" diff --git a/substrate/client/proposer-metrics/Cargo.toml b/substrate/client/proposer-metrics/Cargo.toml index 40fcc722010..f560ce2d65e 100644 --- a/substrate/client/proposer-metrics/Cargo.toml +++ b/substrate/client/proposer-metrics/Cargo.toml @@ -16,5 +16,5 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = "0.4.17" +log = { workspace = true, default-features = true } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } diff --git a/substrate/client/rpc-servers/Cargo.toml b/substrate/client/rpc-servers/Cargo.toml index b624a14e263..8a0b8f80f43 100644 --- a/substrate/client/rpc-servers/Cargo.toml +++ b/substrate/client/rpc-servers/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] jsonrpsee = { version = "0.20.3", features = ["server"] } -log = "0.4.17" +log = { workspace = true, default-features = true } serde_json = "1.0.111" tokio = { version = "1.22.0", features = ["parking_lot"] } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } diff --git a/substrate/client/rpc-spec-v2/Cargo.toml b/substrate/client/rpc-spec-v2/Cargo.toml index 6d0e7e07848..12a02e0b450 100644 --- a/substrate/client/rpc-spec-v2/Cargo.toml +++ b/substrate/client/rpc-spec-v2/Cargo.toml @@ -39,7 +39,7 @@ parking_lot = "0.12.1" tokio-stream = { version = "0.1.14", features = ["sync"] } tokio = { version = "1.22.0", features = ["sync"] } array-bytes = "6.1" -log = "0.4.17" +log = { workspace = true, default-features = true } futures-util = { version = "0.3.30", default-features = false } [dev-dependencies] diff --git a/substrate/client/rpc/Cargo.toml b/substrate/client/rpc/Cargo.toml index 6917eb0b551..a39d9226b62 100644 --- a/substrate/client/rpc/Cargo.toml +++ b/substrate/client/rpc/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" jsonrpsee = { version = "0.20.3", features = ["server"] } -log = "0.4.17" +log = { workspace = true, default-features = true } parking_lot = "0.12.1" serde_json = "1.0.111" sc-block-builder = { path = "../block-builder" } diff --git a/substrate/client/service/Cargo.toml b/substrate/client/service/Cargo.toml index 1c95112aa6b..41ec22a97c2 100644 --- a/substrate/client/service/Cargo.toml +++ b/substrate/client/service/Cargo.toml @@ -33,7 +33,7 @@ thiserror = "1.0.48" futures = "0.3.21" rand = "0.8.5" parking_lot = "0.12.1" -log = "0.4.17" +log = { workspace = true, default-features = true } futures-timer = "3.0.1" exit-future = "0.2.0" pin-project = "1.0.12" diff --git a/substrate/client/service/test/Cargo.toml b/substrate/client/service/test/Cargo.toml index 625d8286396..ee7e60f6011 100644 --- a/substrate/client/service/test/Cargo.toml +++ b/substrate/client/service/test/Cargo.toml @@ -19,7 +19,7 @@ async-channel = "1.8.0" array-bytes = "6.1" fdlimit = "0.3.0" futures = "0.3.21" -log = "0.4.17" +log = { workspace = true, default-features = true } parity-scale-codec = "3.6.1" parking_lot = "0.12.1" tempfile = "3.1.0" diff --git a/substrate/client/state-db/Cargo.toml b/substrate/client/state-db/Cargo.toml index 3f86a0da88e..400dda20c22 100644 --- a/substrate/client/state-db/Cargo.toml +++ b/substrate/client/state-db/Cargo.toml @@ -17,6 +17,6 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } -log = "0.4.17" +log = { workspace = true, default-features = true } parking_lot = "0.12.1" sp-core = { path = "../../primitives/core" } diff --git a/substrate/client/statement-store/Cargo.toml b/substrate/client/statement-store/Cargo.toml index ed229259379..676f6cb36f6 100644 --- a/substrate/client/statement-store/Cargo.toml +++ b/substrate/client/statement-store/Cargo.toml @@ -16,7 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = "0.4.17" +log = { workspace = true, default-features = true } parking_lot = "0.12.1" parity-db = "0.4.12" tokio = { version = "1.22.0", features = ["time"] } diff --git a/substrate/client/storage-monitor/Cargo.toml b/substrate/client/storage-monitor/Cargo.toml index f4db58d6bb0..6cea3f3894b 100644 --- a/substrate/client/storage-monitor/Cargo.toml +++ b/substrate/client/storage-monitor/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] clap = { version = "4.4.18", features = ["derive", "string"] } -log = "0.4.17" +log = { workspace = true, default-features = true } fs4 = "0.7.0" sp-core = { path = "../../primitives/core" } tokio = { version = "1.22.0", features = ["time"] } diff --git a/substrate/client/sysinfo/Cargo.toml b/substrate/client/sysinfo/Cargo.toml index c09fa41d4df..4c2b0eae161 100644 --- a/substrate/client/sysinfo/Cargo.toml +++ b/substrate/client/sysinfo/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] futures = "0.3.19" libc = "0.2" -log = "0.4.17" +log = { workspace = true, default-features = true } rand = "0.8.5" rand_pcg = "0.3.1" derive_more = "0.99" diff --git a/substrate/client/telemetry/Cargo.toml b/substrate/client/telemetry/Cargo.toml index 3270a2e148d..e0563c44bc4 100644 --- a/substrate/client/telemetry/Cargo.toml +++ b/substrate/client/telemetry/Cargo.toml @@ -20,7 +20,7 @@ targets = ["x86_64-unknown-linux-gnu"] chrono = "0.4.31" futures = "0.3.21" libp2p = { version = "0.51.4", features = ["dns", "tcp", "tokio", "wasm-ext", "websocket"] } -log = "0.4.17" +log = { workspace = true, default-features = true } parking_lot = "0.12.1" pin-project = "1.0.12" sc-utils = { path = "../utils" } diff --git a/substrate/client/tracing/Cargo.toml b/substrate/client/tracing/Cargo.toml index 9f0f5fb6936..1331071d3bd 100644 --- a/substrate/client/tracing/Cargo.toml +++ b/substrate/client/tracing/Cargo.toml @@ -22,7 +22,7 @@ chrono = "0.4.31" codec = { package = "parity-scale-codec", version = "3.6.1" } lazy_static = "1.4.0" libc = "0.2.152" -log = { version = "0.4.17" } +log = { workspace = true, default-features = true } parking_lot = "0.12.1" regex = "1.6.0" rustc-hash = "1.1.0" diff --git a/substrate/client/transaction-pool/Cargo.toml b/substrate/client/transaction-pool/Cargo.toml index b491f7bcafd..23ffebec67c 100644 --- a/substrate/client/transaction-pool/Cargo.toml +++ b/substrate/client/transaction-pool/Cargo.toml @@ -21,7 +21,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" futures-timer = "3.0.2" linked-hash-map = "0.5.4" -log = "0.4.17" +log = { workspace = true, default-features = true } parking_lot = "0.12.1" serde = { version = "1.0.195", features = ["derive"] } thiserror = "1.0.48" diff --git a/substrate/client/transaction-pool/api/Cargo.toml b/substrate/client/transaction-pool/api/Cargo.toml index f5fba65a068..9cf732cb87a 100644 --- a/substrate/client/transaction-pool/api/Cargo.toml +++ b/substrate/client/transaction-pool/api/Cargo.toml @@ -15,7 +15,7 @@ workspace = true async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" -log = "0.4.17" +log = { workspace = true, default-features = true } serde = { version = "1.0.195", features = ["derive"] } thiserror = "1.0.48" sp-blockchain = { path = "../../../primitives/blockchain" } diff --git a/substrate/client/utils/Cargo.toml b/substrate/client/utils/Cargo.toml index ec7b3d0eb0d..7f604219bc0 100644 --- a/substrate/client/utils/Cargo.toml +++ b/substrate/client/utils/Cargo.toml @@ -17,7 +17,7 @@ async-channel = "1.8.0" futures = "0.3.21" futures-timer = "3.0.2" lazy_static = "1.4.0" -log = "0.4" +log = { workspace = true, default-features = true } parking_lot = "0.12.1" prometheus = { version = "0.13.0", default-features = false } sp-arithmetic = { path = "../../primitives/arithmetic", default-features = false } diff --git a/substrate/frame/Cargo.toml b/substrate/frame/Cargo.toml index 9419eb15974..01c1358ecb9 100644 --- a/substrate/frame/Cargo.toml +++ b/substrate/frame/Cargo.toml @@ -49,7 +49,7 @@ frame-system-rpc-runtime-api = { default-features = false, path = "../frame/syst docify = "0.2.7" simple-mermaid = { git = "https://github.com/kianenigma/simple-mermaid.git", rev = "e48b187bcfd5cc75111acd9d241f1bd36604344b", optional = true } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } [dev-dependencies] pallet-examples = { path = "./examples" } diff --git a/substrate/frame/alliance/Cargo.toml b/substrate/frame/alliance/Cargo.toml index 955f9e268c6..bc873ad69c8 100644 --- a/substrate/frame/alliance/Cargo.toml +++ b/substrate/frame/alliance/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] array-bytes = { version = "6.1", optional = true } -log = { version = "0.4.14", default-features = false } +log = { workspace = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } diff --git a/substrate/frame/assets/Cargo.toml b/substrate/frame/assets/Cargo.toml index 6fe3a272360..2efc96348cb 100644 --- a/substrate/frame/assets/Cargo.toml +++ b/substrate/frame/assets/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } sp-std = { path = "../../primitives/std", default-features = false } # Needed for various traits. In our case, `OnFinalize`. diff --git a/substrate/frame/aura/Cargo.toml b/substrate/frame/aura/Cargo.toml index 7620d172ffc..de698487efa 100644 --- a/substrate/frame/aura/Cargo.toml +++ b/substrate/frame/aura/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/babe/Cargo.toml b/substrate/frame/babe/Cargo.toml index 8f49faaa2d6..fc7385efa1f 100644 --- a/substrate/frame/babe/Cargo.toml +++ b/substrate/frame/babe/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/bags-list/Cargo.toml b/substrate/frame/bags-list/Cargo.toml index b8ab099a069..f9ae462e16d 100644 --- a/substrate/frame/bags-list/Cargo.toml +++ b/substrate/frame/bags-list/Cargo.toml @@ -33,7 +33,7 @@ frame-system = { path = "../system", default-features = false } frame-election-provider-support = { path = "../election-provider-support", default-features = false } # third party -log = { version = "0.4.17", default-features = false } +log = { workspace = true } docify = "0.2.7" aquamarine = { version = "0.5.0" } diff --git a/substrate/frame/bags-list/remote-tests/Cargo.toml b/substrate/frame/bags-list/remote-tests/Cargo.toml index fb61a986778..266355f5cab 100644 --- a/substrate/frame/bags-list/remote-tests/Cargo.toml +++ b/substrate/frame/bags-list/remote-tests/Cargo.toml @@ -34,4 +34,4 @@ sp-std = { path = "../../../primitives/std" } remote-externalities = { package = "frame-remote-externalities", path = "../../../utils/frame/remote-externalities" } # others -log = "0.4.17" +log = { workspace = true, default-features = true } diff --git a/substrate/frame/balances/Cargo.toml b/substrate/frame/balances/Cargo.toml index e47e916a274..64ae90c6757 100644 --- a/substrate/frame/balances/Cargo.toml +++ b/substrate/frame/balances/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/beefy-mmr/Cargo.toml b/substrate/frame/beefy-mmr/Cargo.toml index 250a6fb450c..b2ede900f26 100644 --- a/substrate/frame/beefy-mmr/Cargo.toml +++ b/substrate/frame/beefy-mmr/Cargo.toml @@ -14,7 +14,7 @@ workspace = true [dependencies] array-bytes = { version = "6.1", optional = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } serde = { version = "1.0.195", optional = true } binary-merkle-tree = { path = "../../utils/binary-merkle-tree", default-features = false } diff --git a/substrate/frame/beefy/Cargo.toml b/substrate/frame/beefy/Cargo.toml index ba02cf1460d..54b57b44742 100644 --- a/substrate/frame/beefy/Cargo.toml +++ b/substrate/frame/beefy/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } serde = { version = "1.0.195", optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/benchmarking/Cargo.toml b/substrate/frame/benchmarking/Cargo.toml index 38c7bc2b905..cde1aea188b 100644 --- a/substrate/frame/benchmarking/Cargo.toml +++ b/substrate/frame/benchmarking/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } linregress = { version = "0.5.1", optional = true } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } paste = "1.0" scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } serde = { version = "1.0.195", optional = true } diff --git a/substrate/frame/bounties/Cargo.toml b/substrate/frame/bounties/Cargo.toml index df7c3c0cbe5..191a38d20b2 100644 --- a/substrate/frame/bounties/Cargo.toml +++ b/substrate/frame/bounties/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/child-bounties/Cargo.toml b/substrate/frame/child-bounties/Cargo.toml index bcd8426c316..589ca95a751 100644 --- a/substrate/frame/child-bounties/Cargo.toml +++ b/substrate/frame/child-bounties/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/collective/Cargo.toml b/substrate/frame/collective/Cargo.toml index 91bb36bb89e..e19e1496e7b 100644 --- a/substrate/frame/collective/Cargo.toml +++ b/substrate/frame/collective/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/contracts/Cargo.toml b/substrate/frame/contracts/Cargo.toml index de49983a4b3..0b127a5a454 100644 --- a/substrate/frame/contracts/Cargo.toml +++ b/substrate/frame/contracts/Cargo.toml @@ -24,7 +24,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = "max-encoded-len", ] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -log = { version = "0.4", default-features = false } +log = { workspace = true } serde = { version = "1", optional = true, features = ["derive"] } smallvec = { version = "1", default-features = false, features = [ "const_generics", diff --git a/substrate/frame/core-fellowship/Cargo.toml b/substrate/frame/core-fellowship/Cargo.toml index 8e59725d317..3e678d32744 100644 --- a/substrate/frame/core-fellowship/Cargo.toml +++ b/substrate/frame/core-fellowship/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.16", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/democracy/Cargo.toml b/substrate/frame/democracy/Cargo.toml index 0ade0d58a6d..1e824eac2d2 100644 --- a/substrate/frame/democracy/Cargo.toml +++ b/substrate/frame/democracy/Cargo.toml @@ -28,7 +28,7 @@ sp-io = { path = "../../primitives/io", default-features = false } sp-runtime = { path = "../../primitives/runtime", default-features = false } sp-std = { path = "../../primitives/std", default-features = false } sp-core = { path = "../../primitives/core", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } [dev-dependencies] pallet-balances = { path = "../balances" } diff --git a/substrate/frame/election-provider-multi-phase/Cargo.toml b/substrate/frame/election-provider-multi-phase/Cargo.toml index 91bdb3c027f..eadce8c1ff8 100644 --- a/substrate/frame/election-provider-multi-phase/Cargo.toml +++ b/substrate/frame/election-provider-multi-phase/Cargo.toml @@ -21,7 +21,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = scale-info = { version = "2.10.0", default-features = false, features = [ "derive", ] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml b/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml index e9bcc96455b..e6384450a6f 100644 --- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml +++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] parking_lot = "0.12.1" codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } scale-info = { version = "2.10.0", features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } sp-runtime = { path = "../../../primitives/runtime" } sp-io = { path = "../../../primitives/io" } diff --git a/substrate/frame/elections-phragmen/Cargo.toml b/substrate/frame/elections-phragmen/Cargo.toml index 4f8c5638d4b..4dc4a3454aa 100644 --- a/substrate/frame/elections-phragmen/Cargo.toml +++ b/substrate/frame/elections-phragmen/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } -log = { version = "0.4.14", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/examples/basic/Cargo.toml b/substrate/frame/examples/basic/Cargo.toml index 2be5aecb968..e4ab5112201 100644 --- a/substrate/frame/examples/basic/Cargo.toml +++ b/substrate/frame/examples/basic/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../../benchmarking", default-features = false, optional = true } frame-support = { path = "../../support", default-features = false } diff --git a/substrate/frame/examples/default-config/Cargo.toml b/substrate/frame/examples/default-config/Cargo.toml index 83d9ae79510..e40845a425a 100644 --- a/substrate/frame/examples/default-config/Cargo.toml +++ b/substrate/frame/examples/default-config/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-support = { path = "../../support", default-features = false } frame-system = { path = "../../system", default-features = false } diff --git a/substrate/frame/examples/dev-mode/Cargo.toml b/substrate/frame/examples/dev-mode/Cargo.toml index f150f446446..a9c4e3f3b1f 100644 --- a/substrate/frame/examples/dev-mode/Cargo.toml +++ b/substrate/frame/examples/dev-mode/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-support = { path = "../../support", default-features = false } frame-system = { path = "../../system", default-features = false } diff --git a/substrate/frame/examples/kitchensink/Cargo.toml b/substrate/frame/examples/kitchensink/Cargo.toml index f866024b8e9..37384107530 100644 --- a/substrate/frame/examples/kitchensink/Cargo.toml +++ b/substrate/frame/examples/kitchensink/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-support = { path = "../../support", default-features = false, features = ["experimental"] } diff --git a/substrate/frame/examples/offchain-worker/Cargo.toml b/substrate/frame/examples/offchain-worker/Cargo.toml index cc337707a29..fc5151ff292 100644 --- a/substrate/frame/examples/offchain-worker/Cargo.toml +++ b/substrate/frame/examples/offchain-worker/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } lite-json = { version = "0.2.0", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-support = { path = "../../support", default-features = false } frame-system = { path = "../../system", default-features = false } diff --git a/substrate/frame/examples/split/Cargo.toml b/substrate/frame/examples/split/Cargo.toml index 733ca92b820..d140fc3eef4 100644 --- a/substrate/frame/examples/split/Cargo.toml +++ b/substrate/frame/examples/split/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-support = { path = "../../support", default-features = false } diff --git a/substrate/frame/examples/tasks/Cargo.toml b/substrate/frame/examples/tasks/Cargo.toml index f6850b53c03..41521114366 100644 --- a/substrate/frame/examples/tasks/Cargo.toml +++ b/substrate/frame/examples/tasks/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-support = { path = "../../support", default-features = false } diff --git a/substrate/frame/executive/Cargo.toml b/substrate/frame/executive/Cargo.toml index 7c72fc77be9..a4ca265f617 100644 --- a/substrate/frame/executive/Cargo.toml +++ b/substrate/frame/executive/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/fast-unstake/Cargo.toml b/substrate/frame/fast-unstake/Cargo.toml index 5d0a5410f8d..eca8247845e 100644 --- a/substrate/frame/fast-unstake/Cargo.toml +++ b/substrate/frame/fast-unstake/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/glutton/Cargo.toml b/substrate/frame/glutton/Cargo.toml index b9543e7f47c..7de18080b87 100644 --- a/substrate/frame/glutton/Cargo.toml +++ b/substrate/frame/glutton/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] blake2 = { version = "0.10.4", default-features = false } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -log = { version = "0.4.14", default-features = false } +log = { workspace = true } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/grandpa/Cargo.toml b/substrate/frame/grandpa/Cargo.toml index 3775ccdac1d..db540564fbe 100644 --- a/substrate/frame/grandpa/Cargo.toml +++ b/substrate/frame/grandpa/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/identity/Cargo.toml b/substrate/frame/identity/Cargo.toml index ba1fd500f70..912444bf603 100644 --- a/substrate/frame/identity/Cargo.toml +++ b/substrate/frame/identity/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } enumflags2 = { version = "0.7.7" } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/im-online/Cargo.toml b/substrate/frame/im-online/Cargo.toml index 04c35908c53..038cbbcd678 100644 --- a/substrate/frame/im-online/Cargo.toml +++ b/substrate/frame/im-online/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/membership/Cargo.toml b/substrate/frame/membership/Cargo.toml index 658bf67f12f..64214670292 100644 --- a/substrate/frame/membership/Cargo.toml +++ b/substrate/frame/membership/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/merkle-mountain-range/Cargo.toml b/substrate/frame/merkle-mountain-range/Cargo.toml index 607fa340e96..d623e25cec2 100644 --- a/substrate/frame/merkle-mountain-range/Cargo.toml +++ b/substrate/frame/merkle-mountain-range/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/message-queue/Cargo.toml b/substrate/frame/message-queue/Cargo.toml index c2ecf445262..8f3e913ee36 100644 --- a/substrate/frame/message-queue/Cargo.toml +++ b/substrate/frame/message-queue/Cargo.toml @@ -15,7 +15,7 @@ workspace = true codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } serde = { version = "1.0.195", optional = true, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } environmental = { version = "1.1.4", default-features = false } sp-core = { path = "../../primitives/core", default-features = false } diff --git a/substrate/frame/mixnet/Cargo.toml b/substrate/frame/mixnet/Cargo.toml index cb00b38890e..de4c2074e79 100644 --- a/substrate/frame/mixnet/Cargo.toml +++ b/substrate/frame/mixnet/Cargo.toml @@ -20,7 +20,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = frame-benchmarking = { default-features = false, optional = true, path = "../benchmarking" } frame-support = { default-features = false, path = "../support" } frame-system = { default-features = false, path = "../system" } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } serde = { version = "1.0.195", default-features = false, features = ["derive"] } sp-application-crypto = { default-features = false, path = "../../primitives/application-crypto" } diff --git a/substrate/frame/multisig/Cargo.toml b/substrate/frame/multisig/Cargo.toml index aefdbe855a3..1d2a79bdc52 100644 --- a/substrate/frame/multisig/Cargo.toml +++ b/substrate/frame/multisig/Cargo.toml @@ -26,7 +26,7 @@ sp-runtime = { path = "../../primitives/runtime", default-features = false } sp-std = { path = "../../primitives/std", default-features = false } # third party -log = { version = "0.4.17", default-features = false } +log = { workspace = true } [dev-dependencies] pallet-balances = { path = "../balances" } diff --git a/substrate/frame/nft-fractionalization/Cargo.toml b/substrate/frame/nft-fractionalization/Cargo.toml index a55bcab533d..8002b7e1165 100644 --- a/substrate/frame/nft-fractionalization/Cargo.toml +++ b/substrate/frame/nft-fractionalization/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/nfts/Cargo.toml b/substrate/frame/nfts/Cargo.toml index d92a9c0b44e..69e9ea170b1 100644 --- a/substrate/frame/nfts/Cargo.toml +++ b/substrate/frame/nfts/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } enumflags2 = { version = "0.7.7" } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/node-authorization/Cargo.toml b/substrate/frame/node-authorization/Cargo.toml index ac139853cdb..a39b0ec4eff 100644 --- a/substrate/frame/node-authorization/Cargo.toml +++ b/substrate/frame/node-authorization/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/nomination-pools/Cargo.toml b/substrate/frame/nomination-pools/Cargo.toml index cac092c98dc..9830f31d5fa 100644 --- a/substrate/frame/nomination-pools/Cargo.toml +++ b/substrate/frame/nomination-pools/Cargo.toml @@ -31,7 +31,7 @@ sp-std = { path = "../../primitives/std", default-features = false } sp-staking = { path = "../../primitives/staking", default-features = false } sp-core = { path = "../../primitives/core", default-features = false } sp-io = { path = "../../primitives/io", default-features = false } -log = { version = "0.4.0", default-features = false } +log = { workspace = true } # Optional: use for testing and/or fuzzing pallet-balances = { path = "../balances", optional = true } diff --git a/substrate/frame/nomination-pools/fuzzer/Cargo.toml b/substrate/frame/nomination-pools/fuzzer/Cargo.toml index 52f49b28457..c0d63a26859 100644 --- a/substrate/frame/nomination-pools/fuzzer/Cargo.toml +++ b/substrate/frame/nomination-pools/fuzzer/Cargo.toml @@ -29,7 +29,7 @@ sp-io = { path = "../../../primitives/io" } sp-tracing = { path = "../../../primitives/tracing" } rand = { version = "0.8.5", features = ["small_rng"] } -log = "0.4.17" +log = { workspace = true, default-features = true } [[bin]] name = "call" diff --git a/substrate/frame/nomination-pools/test-staking/Cargo.toml b/substrate/frame/nomination-pools/test-staking/Cargo.toml index 845535ae04f..9c7b12e4c63 100644 --- a/substrate/frame/nomination-pools/test-staking/Cargo.toml +++ b/substrate/frame/nomination-pools/test-staking/Cargo.toml @@ -37,4 +37,4 @@ pallet-staking-reward-curve = { path = "../../staking/reward-curve" } pallet-nomination-pools = { path = ".." } sp-tracing = { path = "../../../primitives/tracing" } -log = { version = "0.4.0" } +log = { workspace = true, default-features = true } diff --git a/substrate/frame/offences/Cargo.toml b/substrate/frame/offences/Cargo.toml index 73842c696af..e7f559086a6 100644 --- a/substrate/frame/offences/Cargo.toml +++ b/substrate/frame/offences/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } serde = { version = "1.0.195", optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/offences/benchmarking/Cargo.toml b/substrate/frame/offences/benchmarking/Cargo.toml index fc3fb076f1f..8dcce84d257 100644 --- a/substrate/frame/offences/benchmarking/Cargo.toml +++ b/substrate/frame/offences/benchmarking/Cargo.toml @@ -32,7 +32,7 @@ pallet-staking = { path = "../../staking", default-features = false } sp-runtime = { path = "../../../primitives/runtime", default-features = false } sp-staking = { path = "../../../primitives/staking", default-features = false } sp-std = { path = "../../../primitives/std", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } [dev-dependencies] pallet-staking-reward-curve = { path = "../../staking/reward-curve" } diff --git a/substrate/frame/preimage/Cargo.toml b/substrate/frame/preimage/Cargo.toml index 5951663d291..10a15f97bd5 100644 --- a/substrate/frame/preimage/Cargo.toml +++ b/substrate/frame/preimage/Cargo.toml @@ -21,7 +21,7 @@ sp-core = { path = "../../primitives/core", default-features = false, optional = sp-io = { path = "../../primitives/io", default-features = false } sp-runtime = { path = "../../primitives/runtime", default-features = false } sp-std = { path = "../../primitives/std", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } [dev-dependencies] pallet-balances = { path = "../balances" } diff --git a/substrate/frame/ranked-collective/Cargo.toml b/substrate/frame/ranked-collective/Cargo.toml index 62e01df372b..54e84c0b558 100644 --- a/substrate/frame/ranked-collective/Cargo.toml +++ b/substrate/frame/ranked-collective/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.16", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/referenda/Cargo.toml b/substrate/frame/referenda/Cargo.toml index 0f8a92ff72a..ad0e5993fc9 100644 --- a/substrate/frame/referenda/Cargo.toml +++ b/substrate/frame/referenda/Cargo.toml @@ -29,7 +29,7 @@ frame-system = { path = "../system", default-features = false } sp-io = { path = "../../primitives/io", default-features = false } sp-runtime = { path = "../../primitives/runtime", default-features = false } sp-std = { path = "../../primitives/std", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } [dev-dependencies] assert_matches = { version = "1.5" } diff --git a/substrate/frame/salary/Cargo.toml b/substrate/frame/salary/Cargo.toml index 90a44da1a10..ba57fd46eeb 100644 --- a/substrate/frame/salary/Cargo.toml +++ b/substrate/frame/salary/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.16", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/sassafras/Cargo.toml b/substrate/frame/sassafras/Cargo.toml index 5f4900b5a29..325a39bf597 100644 --- a/substrate/frame/sassafras/Cargo.toml +++ b/substrate/frame/sassafras/Cargo.toml @@ -22,7 +22,7 @@ scale-info = { version = "2.5.0", default-features = false, features = ["derive" frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } sp-consensus-sassafras = { path = "../../primitives/consensus/sassafras", default-features = false, features = ["serde"] } sp-io = { path = "../../primitives/io", default-features = false } sp-runtime = { path = "../../primitives/runtime", default-features = false } diff --git a/substrate/frame/scheduler/Cargo.toml b/substrate/frame/scheduler/Cargo.toml index bca17242d20..f50f6afdc06 100644 --- a/substrate/frame/scheduler/Cargo.toml +++ b/substrate/frame/scheduler/Cargo.toml @@ -14,7 +14,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/session/Cargo.toml b/substrate/frame/session/Cargo.toml index 91ffecbf717..de041307f70 100644 --- a/substrate/frame/session/Cargo.toml +++ b/substrate/frame/session/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } impl-trait-for-tuples = "0.2.2" -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/society/Cargo.toml b/substrate/frame/society/Cargo.toml index 8b24f637f42..3dab082b395 100644 --- a/substrate/frame/society/Cargo.toml +++ b/substrate/frame/society/Cargo.toml @@ -16,7 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = { version = "0.4.17", default-features = false } +log = { workspace = true } rand_chacha = { version = "0.2", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } diff --git a/substrate/frame/staking/Cargo.toml b/substrate/frame/staking/Cargo.toml index 2c3f50beaea..5f82a326145 100644 --- a/substrate/frame/staking/Cargo.toml +++ b/substrate/frame/staking/Cargo.toml @@ -33,7 +33,7 @@ pallet-session = { path = "../session", default-features = false, features = [ pallet-authorship = { path = "../authorship", default-features = false } sp-application-crypto = { path = "../../primitives/application-crypto", default-features = false, features = ["serde"] } frame-election-provider-support = { path = "../election-provider-support", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } # Optional imports for benchmarking frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } diff --git a/substrate/frame/staking/reward-fn/Cargo.toml b/substrate/frame/staking/reward-fn/Cargo.toml index 0b8903f2871..5169db5072e 100644 --- a/substrate/frame/staking/reward-fn/Cargo.toml +++ b/substrate/frame/staking/reward-fn/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [lib] [dependencies] -log = { version = "0.4.17", default-features = false } +log = { workspace = true } sp-arithmetic = { path = "../../../primitives/arithmetic", default-features = false } [features] diff --git a/substrate/frame/state-trie-migration/Cargo.toml b/substrate/frame/state-trie-migration/Cargo.toml index 1fb49e62562..83abf3615f0 100644 --- a/substrate/frame/state-trie-migration/Cargo.toml +++ b/substrate/frame/state-trie-migration/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } serde = { version = "1.0.195", optional = true } thousands = { version = "0.2.0", optional = true } diff --git a/substrate/frame/statement/Cargo.toml b/substrate/frame/statement/Cargo.toml index 58f8b40dc26..6827dbda962 100644 --- a/substrate/frame/statement/Cargo.toml +++ b/substrate/frame/statement/Cargo.toml @@ -25,7 +25,7 @@ sp-runtime = { path = "../../primitives/runtime", default-features = false } sp-std = { path = "../../primitives/std", default-features = false } sp-io = { path = "../../primitives/io", default-features = false } sp-core = { path = "../../primitives/core", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } [dev-dependencies] pallet-balances = { path = "../balances" } diff --git a/substrate/frame/support/Cargo.toml b/substrate/frame/support/Cargo.toml index ad97ad5146e..fd39e8397d0 100644 --- a/substrate/frame/support/Cargo.toml +++ b/substrate/frame/support/Cargo.toml @@ -41,7 +41,7 @@ sp-state-machine = { path = "../../primitives/state-machine", default-features = bitflags = "1.3" impl-trait-for-tuples = "0.2.2" smallvec = "1.11.0" -log = { version = "0.4.17", default-features = false } +log = { workspace = true } sp-crypto-hashing-proc-macro = { path = "../../primitives/crypto/hashing/proc-macro" } k256 = { version = "0.13.1", default-features = false, features = ["ecdsa"] } environmental = { version = "1.1.4", default-features = false } diff --git a/substrate/frame/system/Cargo.toml b/substrate/frame/system/Cargo.toml index d4094601314..d25746dcde8 100644 --- a/substrate/frame/system/Cargo.toml +++ b/substrate/frame/system/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] cfg-if = "1.0" codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"] } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/timestamp/Cargo.toml b/substrate/frame/timestamp/Cargo.toml index cd0737c6bb8..28e57fcab0a 100644 --- a/substrate/frame/timestamp/Cargo.toml +++ b/substrate/frame/timestamp/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/tips/Cargo.toml b/substrate/frame/tips/Cargo.toml index 900cd47e0dc..65931bf2d59 100644 --- a/substrate/frame/tips/Cargo.toml +++ b/substrate/frame/tips/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } serde = { version = "1.0.195", features = ["derive"], optional = true } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } diff --git a/substrate/frame/transaction-storage/Cargo.toml b/substrate/frame/transaction-storage/Cargo.toml index c96aa91d54a..8ae77cae79f 100644 --- a/substrate/frame/transaction-storage/Cargo.toml +++ b/substrate/frame/transaction-storage/Cargo.toml @@ -29,7 +29,7 @@ sp-io = { path = "../../primitives/io", default-features = false } sp-runtime = { path = "../../primitives/runtime", default-features = false } sp-std = { path = "../../primitives/std", default-features = false } sp-transaction-storage-proof = { path = "../../primitives/transaction-storage-proof", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } [dev-dependencies] sp-core = { path = "../../primitives/core", default-features = false } diff --git a/substrate/frame/uniques/Cargo.toml b/substrate/frame/uniques/Cargo.toml index 8a5a180d75f..4e5f21b3d8d 100644 --- a/substrate/frame/uniques/Cargo.toml +++ b/substrate/frame/uniques/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/vesting/Cargo.toml b/substrate/frame/vesting/Cargo.toml index f81b7a122c5..96938b95a2a 100644 --- a/substrate/frame/vesting/Cargo.toml +++ b/substrate/frame/vesting/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/primitives/api/Cargo.toml b/substrate/primitives/api/Cargo.toml index cd882c7a050..3330d2a88de 100644 --- a/substrate/primitives/api/Cargo.toml +++ b/substrate/primitives/api/Cargo.toml @@ -32,7 +32,7 @@ scale-info = { version = "2.10.0", default-features = false, features = [ "derive", ] } sp-metadata-ir = { path = "../metadata-ir", default-features = false, optional = true } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } [dev-dependencies] sp-test-primitives = { path = "../test-primitives" } diff --git a/substrate/primitives/api/test/Cargo.toml b/substrate/primitives/api/test/Cargo.toml index b0975082c44..3a90553bbf0 100644 --- a/substrate/primitives/api/test/Cargo.toml +++ b/substrate/primitives/api/test/Cargo.toml @@ -31,7 +31,7 @@ scale-info = { version = "2.10.0", default-features = false, features = ["derive [dev-dependencies] criterion = "0.4.0" futures = "0.3.21" -log = "0.4.17" +log = { workspace = true, default-features = true } sp-core = { path = "../../core" } static_assertions = "1.1.0" diff --git a/substrate/primitives/blockchain/Cargo.toml b/substrate/primitives/blockchain/Cargo.toml index 176e9ed6dd2..26ba41645e5 100644 --- a/substrate/primitives/blockchain/Cargo.toml +++ b/substrate/primitives/blockchain/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } futures = "0.3.21" -log = "0.4.17" +log = { workspace = true, default-features = true } parking_lot = "0.12.1" schnellru = "0.2.1" thiserror = "1.0.48" diff --git a/substrate/primitives/consensus/common/Cargo.toml b/substrate/primitives/consensus/common/Cargo.toml index 00c2fca5e22..cbca0d94d3e 100644 --- a/substrate/primitives/consensus/common/Cargo.toml +++ b/substrate/primitives/consensus/common/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = "0.1.74" futures = { version = "0.3.21", features = ["thread-pool"] } -log = "0.4.17" +log = { workspace = true, default-features = true } thiserror = "1.0.48" sp-core = { path = "../../core" } sp-inherents = { path = "../../inherents" } diff --git a/substrate/primitives/consensus/grandpa/Cargo.toml b/substrate/primitives/consensus/grandpa/Cargo.toml index de02b189070..1398cb93564 100644 --- a/substrate/primitives/consensus/grandpa/Cargo.toml +++ b/substrate/primitives/consensus/grandpa/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } grandpa = { package = "finality-grandpa", version = "0.16.2", default-features = false, features = ["derive-codec"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } serde = { version = "1.0.195", features = ["alloc", "derive"], default-features = false, optional = true } sp-api = { path = "../../api", default-features = false } diff --git a/substrate/primitives/core/Cargo.toml b/substrate/primitives/core/Cargo.toml index f5914049f41..25c69253354 100644 --- a/substrate/primitives/core/Cargo.toml +++ b/substrate/primitives/core/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } serde = { version = "1.0.195", optional = true, default-features = false, features = ["alloc", "derive"] } bounded-collections = { version = "0.2.0", default-features = false } primitive-types = { version = "0.12.0", default-features = false, features = ["codec", "scale-info"] } diff --git a/substrate/primitives/io/Cargo.toml b/substrate/primitives/io/Cargo.toml index d2d56b83153..c78def9bf44 100644 --- a/substrate/primitives/io/Cargo.toml +++ b/substrate/primitives/io/Cargo.toml @@ -30,7 +30,7 @@ sp-runtime-interface = { path = "../runtime-interface", default-features = false sp-trie = { path = "../trie", default-features = false, optional = true } sp-externalities = { path = "../externalities", default-features = false } sp-tracing = { path = "../tracing", default-features = false } -log = { version = "0.4.17", optional = true } +log = { optional = true, workspace = true, default-features = true } secp256k1 = { version = "0.28.0", features = ["global-context", "recovery"], optional = true } tracing = { version = "0.1.29", default-features = false } tracing-core = { version = "0.1.32", default-features = false } diff --git a/substrate/primitives/merkle-mountain-range/Cargo.toml b/substrate/primitives/merkle-mountain-range/Cargo.toml index 729f458dd8d..66dc762ec28 100644 --- a/substrate/primitives/merkle-mountain-range/Cargo.toml +++ b/substrate/primitives/merkle-mountain-range/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } mmr-lib = { package = "ckb-merkle-mountain-range", version = "0.5.2", default-features = false } serde = { version = "1.0.195", features = ["alloc", "derive"], default-features = false, optional = true } sp-api = { path = "../api", default-features = false } diff --git a/substrate/primitives/runtime/Cargo.toml b/substrate/primitives/runtime/Cargo.toml index f4b11582429..940fca54ab1 100644 --- a/substrate/primitives/runtime/Cargo.toml +++ b/substrate/primitives/runtime/Cargo.toml @@ -21,7 +21,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = either = { version = "1.5", default-features = false } hash256-std-hasher = { version = "0.15.2", default-features = false } impl-trait-for-tuples = "0.2.2" -log = { version = "0.4.17", default-features = false } +log = { workspace = true } paste = "1.0" rand = { version = "0.8.5", optional = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } diff --git a/substrate/primitives/state-machine/Cargo.toml b/substrate/primitives/state-machine/Cargo.toml index b63d5685a33..489209ecc33 100644 --- a/substrate/primitives/state-machine/Cargo.toml +++ b/substrate/primitives/state-machine/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } hash-db = { version = "0.16.0", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } parking_lot = { version = "0.12.1", optional = true } rand = { version = "0.8.5", optional = true } smallvec = "1.11.0" diff --git a/substrate/primitives/wasm-interface/Cargo.toml b/substrate/primitives/wasm-interface/Cargo.toml index ccd2a3043c5..f7d1038903e 100644 --- a/substrate/primitives/wasm-interface/Cargo.toml +++ b/substrate/primitives/wasm-interface/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } impl-trait-for-tuples = "0.2.2" -log = { version = "0.4.17", optional = true } +log = { optional = true, workspace = true, default-features = true } wasmtime = { version = "8.0.1", default-features = false, optional = true } anyhow = { version = "1.0.68", optional = true } sp-std = { path = "../std", default-features = false } diff --git a/substrate/test-utils/runtime/Cargo.toml b/substrate/test-utils/runtime/Cargo.toml index 589686c1229..cdc94782ec5 100644 --- a/substrate/test-utils/runtime/Cargo.toml +++ b/substrate/test-utils/runtime/Cargo.toml @@ -51,7 +51,7 @@ sp-externalities = { path = "../../primitives/externalities", default-features = # 3rd party array-bytes = { version = "6.1", optional = true } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } [dev-dependencies] futures = "0.3.21" diff --git a/substrate/utils/binary-merkle-tree/Cargo.toml b/substrate/utils/binary-merkle-tree/Cargo.toml index b0b870823fb..6ba515afee1 100644 --- a/substrate/utils/binary-merkle-tree/Cargo.toml +++ b/substrate/utils/binary-merkle-tree/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] array-bytes = { version = "6.1", optional = true } -log = { version = "0.4", default-features = false, optional = true } +log = { optional = true, workspace = true } hash-db = { version = "0.16.0", default-features = false } [dev-dependencies] diff --git a/substrate/utils/frame/benchmarking-cli/Cargo.toml b/substrate/utils/frame/benchmarking-cli/Cargo.toml index 0314e3035c0..2af8e055cf2 100644 --- a/substrate/utils/frame/benchmarking-cli/Cargo.toml +++ b/substrate/utils/frame/benchmarking-cli/Cargo.toml @@ -26,7 +26,7 @@ Inflector = "0.11.4" itertools = "0.10.3" lazy_static = "1.4.0" linked-hash-map = "0.5.4" -log = "0.4.17" +log = { workspace = true, default-features = true } rand = { version = "0.8.5", features = ["small_rng"] } rand_pcg = "0.3.1" serde = "1.0.195" diff --git a/substrate/utils/frame/remote-externalities/Cargo.toml b/substrate/utils/frame/remote-externalities/Cargo.toml index ff0b7572d33..0fee718d832 100644 --- a/substrate/utils/frame/remote-externalities/Cargo.toml +++ b/substrate/utils/frame/remote-externalities/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] jsonrpsee = { version = "0.20.3", features = ["http-client"] } codec = { package = "parity-scale-codec", version = "3.6.1" } -log = "0.4.17" +log = { workspace = true, default-features = true } serde = "1.0.195" sp-core = { path = "../../../primitives/core" } sp-crypto-hashing = { path = "../../../primitives/crypto/hashing" } diff --git a/substrate/utils/frame/rpc/client/Cargo.toml b/substrate/utils/frame/rpc/client/Cargo.toml index a97bc77b00f..d1959fe9452 100644 --- a/substrate/utils/frame/rpc/client/Cargo.toml +++ b/substrate/utils/frame/rpc/client/Cargo.toml @@ -20,7 +20,7 @@ sc-rpc-api = { path = "../../../../client/rpc-api" } async-trait = "0.1.74" serde = "1" sp-runtime = { path = "../../../../primitives/runtime" } -log = "0.4" +log = { workspace = true, default-features = true } [dev-dependencies] tokio = { version = "1.22.0", features = ["macros", "rt-multi-thread", "sync"] } diff --git a/substrate/utils/frame/rpc/system/Cargo.toml b/substrate/utils/frame/rpc/system/Cargo.toml index 3f06ffe2bc2..8097a4bf3d3 100644 --- a/substrate/utils/frame/rpc/system/Cargo.toml +++ b/substrate/utils/frame/rpc/system/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1" } jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] } futures = "0.3.21" -log = "0.4.17" +log = { workspace = true, default-features = true } frame-system-rpc-runtime-api = { path = "../../../../frame/system/rpc/runtime-api" } sc-rpc-api = { path = "../../../../client/rpc-api" } sc-transaction-pool-api = { path = "../../../../client/transaction-pool/api" } diff --git a/substrate/utils/frame/try-runtime/cli/Cargo.toml b/substrate/utils/frame/try-runtime/cli/Cargo.toml index 1550a2cec4b..1c8484f9a80 100644 --- a/substrate/utils/frame/try-runtime/cli/Cargo.toml +++ b/substrate/utils/frame/try-runtime/cli/Cargo.toml @@ -40,7 +40,7 @@ substrate-rpc-client = { path = "../../rpc/client" } async-trait = "0.1.74" clap = { version = "4.4.18", features = ["derive"] } hex = { version = "0.4.3", default-features = false } -log = "0.4.17" +log = { workspace = true, default-features = true } parity-scale-codec = "3.6.1" serde = "1.0.195" serde_json = "1.0.111" diff --git a/substrate/utils/prometheus/Cargo.toml b/substrate/utils/prometheus/Cargo.toml index 5ce943fbc59..2a09cb2bb51 100644 --- a/substrate/utils/prometheus/Cargo.toml +++ b/substrate/utils/prometheus/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] hyper = { version = "0.14.16", default-features = false, features = ["http1", "server", "tcp"] } -log = "0.4.17" +log = { workspace = true, default-features = true } prometheus = { version = "0.13.0", default-features = false } thiserror = "1.0" tokio = { version = "1.22.0", features = ["parking_lot"] } -- GitLab From cecdf760740c4e1ed147600e05567632529144a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=B3nal=20Murray?= Date: Mon, 12 Feb 2024 12:01:13 +0000 Subject: [PATCH 044/133] Bump coretime-rococo to get leases fix (#3289) Leases can be force set, but since Leases is a StorageValue, if a lease misses its sale rotation in which it should expire, it can never be cleared. This can happen if a lease is added with an until timeslice that lies in a region whose sale has already started or has passed, even if the timeslice itself hasn't passed. Trappist is currently trapped in a lease that will never end, so this will remove it at the next sale rotation. A fix was introduced in https://github.com/paritytech/polkadot-sdk/pull/3213 but this missed the 1.7 release. This PR bumps the `coretime-rococo` version to get these changes on Rococo. --- cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs index b644640f2c3..f07bac8b2ef 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs @@ -133,7 +133,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("coretime-rococo"), impl_name: create_runtime_str!("coretime-rococo"), authoring_version: 1, - spec_version: 1_007_000, + spec_version: 1_007_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 0, -- GitLab From bde0bbe5017a9c08a1a62e04de71e2add54af5ae Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Mon, 12 Feb 2024 15:23:55 +0200 Subject: [PATCH 045/133] rpc-v2/tx: Implement `transaction_unstable_broadcast` and `transaction_unstable_stop` (#3079) This PR implements the [transaction_unstable_broadcast](https://github.com/paritytech/json-rpc-interface-spec/blob/main/src/api/transaction_unstable_broadcast.md) and [transaction_unstable_stop](https://github.com/paritytech/json-rpc-interface-spec/blob/main/src/api/transaction_unstable_stop.md). The [transaction_unstable_broadcast](https://github.com/paritytech/json-rpc-interface-spec/blob/main/src/api/transaction_unstable_broadcast.md) submits the provided transaction at the best block of the chain. If the transaction is dropped or declared invalid, the API tries to resubmit the transaction at the next available best block. ### Broadcasting The broadcasting operation continues until either: - the user called `transaction_unstable_stop` with the operation ID that identifies the broadcasting operation - the transaction state is one of the following: - Finalized: the transaction is part of the chain - FinalizedTimeout: we have waited for 256 finalized blocks and timedout - Usurped the transaction has been replaced in the tx pool The broadcasting retires to submit the transaction when the transaction state is: - Invalid: the transaction might become valid at a later time - Dropped: the transaction pool's capacity is full at the moment, but might clear when other transactions are finalized/dropped ### Stopping The `transaction_unstable_broadcast` spawns an abortable future and tracks the abort handler. When the [transaction_unstable_stop](https://github.com/paritytech/json-rpc-interface-spec/blob/main/src/api/transaction_unstable_stop.md) is called with a valid operation ID; the abort handler of the corresponding `transaction_unstable_broadcast` future is called. This behavior ensures the broadcast future is finishes on the next polling. When the `transaction_unstable_stop` is called with an invalid operation ID, an invalid jsonrpc specific error object is returned. ### Testing This PR adds the testing harness of the transaction API and validates two basic scenarios: - transaction enters and exits the transaction pool - transaction stop returns appropriate values when called with valid and invalid operation IDs Closes: https://github.com/paritytech/polkadot-sdk/issues/3039 Note that the API should be enabled after: https://github.com/paritytech/polkadot-sdk/issues/3084. cc @paritytech/subxt-team --------- Signed-off-by: Alexandru Vasile Co-authored-by: Sebastian Kunert --- Cargo.lock | 3 + prdoc/pr_3079.prdoc | 15 ++ substrate/client/rpc-spec-v2/Cargo.toml | 3 + .../client/rpc-spec-v2/src/chain_head/mod.rs | 2 +- .../client/rpc-spec-v2/src/transaction/api.rs | 27 +- .../rpc-spec-v2/src/transaction/error.rs | 27 ++ .../client/rpc-spec-v2/src/transaction/mod.rs | 7 +- .../rpc-spec-v2/src/transaction/tests.rs | 238 +++++++++++++++++ .../src/transaction/transaction.rs | 13 +- .../src/transaction/transaction_broadcast.rs | 251 ++++++++++++++++++ 10 files changed, 574 insertions(+), 12 deletions(-) create mode 100644 prdoc/pr_3079.prdoc create mode 100644 substrate/client/rpc-spec-v2/src/transaction/tests.rs create mode 100644 substrate/client/rpc-spec-v2/src/transaction/transaction_broadcast.rs diff --git a/Cargo.lock b/Cargo.lock index f5b272d1175..68d399f78a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16433,11 +16433,13 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.1", "pretty_assertions", + "rand", "sc-block-builder", "sc-chain-spec", "sc-client-api", "sc-rpc", "sc-service", + "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", "serde", @@ -16453,6 +16455,7 @@ dependencies = [ "sp-version", "substrate-test-runtime", "substrate-test-runtime-client", + "substrate-test-runtime-transaction-pool", "thiserror", "tokio", "tokio-stream", diff --git a/prdoc/pr_3079.prdoc b/prdoc/pr_3079.prdoc new file mode 100644 index 00000000000..c745c1ffbfe --- /dev/null +++ b/prdoc/pr_3079.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Implement transaction_unstable_broadcast and transaction_unstable_stop + +doc: + - audience: Node Dev + description: | + A new RPC class is added to handle transactions. The `transaction_unstable_broadcast` broadcasts + the provided transaction to the peers of the node, until the `transaction_unstable_stop` is called. + The APIs are marked as unstable and subject to change in the future. + To know if the transaction was added to the chain, users can decode the bodies of announced finalized blocks. + This is a low-level approach for `transactionWatch_unstable_submitAndWatch`. + +crates: [ ] diff --git a/substrate/client/rpc-spec-v2/Cargo.toml b/substrate/client/rpc-spec-v2/Cargo.toml index 12a02e0b450..1b7870764dc 100644 --- a/substrate/client/rpc-spec-v2/Cargo.toml +++ b/substrate/client/rpc-spec-v2/Cargo.toml @@ -41,12 +41,14 @@ tokio = { version = "1.22.0", features = ["sync"] } array-bytes = "6.1" log = { workspace = true, default-features = true } futures-util = { version = "0.3.30", default-features = false } +rand = "0.8.5" [dev-dependencies] serde_json = "1.0.111" tokio = { version = "1.22.0", features = ["macros"] } substrate-test-runtime-client = { path = "../../test-utils/runtime/client" } substrate-test-runtime = { path = "../../test-utils/runtime" } +substrate-test-runtime-transaction-pool = { path = "../../test-utils/runtime/transaction-pool" } sp-consensus = { path = "../../primitives/consensus/common" } sp-externalities = { path = "../../primitives/externalities" } sp-maybe-compressed-blob = { path = "../../primitives/maybe-compressed-blob" } @@ -54,3 +56,4 @@ sc-block-builder = { path = "../block-builder" } sc-service = { path = "../service", features = ["test-helpers"] } assert_matches = "1.3.0" pretty_assertions = "1.2.1" +sc-transaction-pool = { path = "../transaction-pool" } diff --git a/substrate/client/rpc-spec-v2/src/chain_head/mod.rs b/substrate/client/rpc-spec-v2/src/chain_head/mod.rs index 4cbbd00f64f..c9fe19aca2b 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/mod.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/mod.rs @@ -23,7 +23,7 @@ //! Methods are prefixed by `chainHead`. #[cfg(test)] -mod test_utils; +pub mod test_utils; #[cfg(test)] mod tests; diff --git a/substrate/client/rpc-spec-v2/src/transaction/api.rs b/substrate/client/rpc-spec-v2/src/transaction/api.rs index 53c83b662a3..33af9c95333 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/api.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/api.rs @@ -18,8 +18,8 @@ //! API trait for transactions. -use crate::transaction::event::TransactionEvent; -use jsonrpsee::proc_macros::rpc; +use crate::transaction::{error::ErrorBroadcast, event::TransactionEvent}; +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use sp_core::Bytes; #[rpc(client, server)] @@ -28,6 +28,10 @@ pub trait TransactionApi { /// /// See [`TransactionEvent`](crate::transaction::event::TransactionEvent) for details on /// transaction life cycle. + /// + /// # Unstable + /// + /// This method is unstable and subject to change in the future. #[subscription( name = "transactionWatch_unstable_submitAndWatch" => "transactionWatch_unstable_watchEvent", unsubscribe = "transactionWatch_unstable_unwatch", @@ -35,3 +39,22 @@ pub trait TransactionApi { )] fn submit_and_watch(&self, bytes: Bytes); } + +#[rpc(client, server)] +pub trait TransactionBroadcastApi { + /// Broadcast an extrinsic to the chain. + /// + /// # Unstable + /// + /// This method is unstable and subject to change in the future. + #[method(name = "transaction_unstable_broadcast")] + fn broadcast(&self, bytes: Bytes) -> RpcResult>; + + /// Broadcast an extrinsic to the chain. + /// + /// # Unstable + /// + /// This method is unstable and subject to change in the future. + #[method(name = "transaction_unstable_stop")] + fn stop_broadcast(&self, operation_id: String) -> Result<(), ErrorBroadcast>; +} diff --git a/substrate/client/rpc-spec-v2/src/transaction/error.rs b/substrate/client/rpc-spec-v2/src/transaction/error.rs index d2de07afd59..116977af660 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/error.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/error.rs @@ -21,6 +21,7 @@ //! Errors are interpreted as transaction events for subscriptions. use crate::transaction::event::{TransactionError, TransactionEvent}; +use jsonrpsee::types::error::ErrorObject; use sc_transaction_pool_api::error::Error as PoolError; use sp_runtime::transaction_validity::InvalidTransaction; @@ -98,3 +99,29 @@ impl From for TransactionEvent { } } } + +/// TransactionBroadcast error. +#[derive(Debug, thiserror::Error)] +pub enum ErrorBroadcast { + /// The provided operation ID is invalid. + #[error("Invalid operation id")] + InvalidOperationID, +} + +/// General purpose errors, as defined in +/// . +pub mod json_rpc_spec { + /// Invalid parameter error. + pub const INVALID_PARAM_ERROR: i32 = -32602; +} + +impl From for ErrorObject<'static> { + fn from(e: ErrorBroadcast) -> Self { + let msg = e.to_string(); + + match e { + ErrorBroadcast::InvalidOperationID => + ErrorObject::owned(json_rpc_spec::INVALID_PARAM_ERROR, msg, None::<()>), + } + } +} diff --git a/substrate/client/rpc-spec-v2/src/transaction/mod.rs b/substrate/client/rpc-spec-v2/src/transaction/mod.rs index 212912ba1c7..74268a5372a 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/mod.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/mod.rs @@ -25,14 +25,19 @@ //! //! Methods are prefixed by `transaction`. +#[cfg(test)] +mod tests; + pub mod api; pub mod error; pub mod event; pub mod transaction; +pub mod transaction_broadcast; -pub use api::TransactionApiServer; +pub use api::{TransactionApiServer, TransactionBroadcastApiServer}; pub use event::{ TransactionBlock, TransactionBroadcasted, TransactionDropped, TransactionError, TransactionEvent, }; pub use transaction::Transaction; +pub use transaction_broadcast::TransactionBroadcast; diff --git a/substrate/client/rpc-spec-v2/src/transaction/tests.rs b/substrate/client/rpc-spec-v2/src/transaction/tests.rs new file mode 100644 index 00000000000..45477494768 --- /dev/null +++ b/substrate/client/rpc-spec-v2/src/transaction/tests.rs @@ -0,0 +1,238 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use super::*; +use crate::{ + chain_head::test_utils::ChainHeadMockClient, hex_string, + transaction::TransactionBroadcast as RpcTransactionBroadcast, +}; +use assert_matches::assert_matches; +use codec::Encode; +use futures::Future; +use jsonrpsee::{core::error::Error, rpc_params, RpcModule}; +use sc_transaction_pool::*; +use sc_transaction_pool_api::{ChainEvent, MaintainedTransactionPool, TransactionPool}; +use sp_core::{testing::TaskExecutor, traits::SpawnNamed}; +use std::{pin::Pin, sync::Arc, time::Duration}; +use substrate_test_runtime_client::{prelude::*, AccountKeyring::*, Client}; +use substrate_test_runtime_transaction_pool::{uxt, TestApi}; +use tokio::sync::mpsc; + +type Block = substrate_test_runtime_client::runtime::Block; + +/// Wrap the `TaskExecutor` to know when the broadcast future is dropped. +#[derive(Clone)] +struct TaskExecutorBroadcast { + executor: TaskExecutor, + sender: mpsc::UnboundedSender<()>, +} + +/// The channel that receives events when the broadcast futures are dropped. +type TaskExecutorRecv = mpsc::UnboundedReceiver<()>; + +impl TaskExecutorBroadcast { + /// Construct a new `TaskExecutorBroadcast` and a receiver to know when the broadcast futures + /// are dropped. + fn new() -> (Self, TaskExecutorRecv) { + let (sender, recv) = mpsc::unbounded_channel(); + + (Self { executor: TaskExecutor::new(), sender }, recv) + } +} + +impl SpawnNamed for TaskExecutorBroadcast { + fn spawn( + &self, + name: &'static str, + group: Option<&'static str>, + future: futures::future::BoxFuture<'static, ()>, + ) { + let sender = self.sender.clone(); + let future = Box::pin(async move { + future.await; + let _ = sender.send(()); + }); + + self.executor.spawn(name, group, future) + } + + fn spawn_blocking( + &self, + name: &'static str, + group: Option<&'static str>, + future: futures::future::BoxFuture<'static, ()>, + ) { + let sender = self.sender.clone(); + let future = Box::pin(async move { + future.await; + let _ = sender.send(()); + }); + + self.executor.spawn_blocking(name, group, future) + } +} + +/// Initial Alice account nonce. +const ALICE_NONCE: u64 = 209; + +fn create_basic_pool_with_genesis( + test_api: Arc, +) -> (BasicPool, Pin + Send>>) { + let genesis_hash = { + test_api + .chain() + .read() + .block_by_number + .get(&0) + .map(|blocks| blocks[0].0.header.hash()) + .expect("there is block 0. qed") + }; + BasicPool::new_test(test_api, genesis_hash, genesis_hash) +} + +fn maintained_pool() -> (BasicPool, Arc, futures::executor::ThreadPool) { + let api = Arc::new(TestApi::with_alice_nonce(ALICE_NONCE)); + let (pool, background_task) = create_basic_pool_with_genesis(api.clone()); + + let thread_pool = futures::executor::ThreadPool::new().unwrap(); + thread_pool.spawn_ok(background_task); + (pool, api, thread_pool) +} + +fn setup_api() -> ( + Arc, + Arc>, + Arc>>, + RpcModule< + TransactionBroadcast, ChainHeadMockClient>>, + >, + TaskExecutorRecv, +) { + let (pool, api, _) = maintained_pool(); + let pool = Arc::new(pool); + + let builder = TestClientBuilder::new(); + let client = Arc::new(builder.build()); + let client_mock = Arc::new(ChainHeadMockClient::new(client.clone())); + + let (task_executor, executor_recv) = TaskExecutorBroadcast::new(); + + let tx_api = + RpcTransactionBroadcast::new(client_mock.clone(), pool.clone(), Arc::new(task_executor)) + .into_rpc(); + + (api, pool, client_mock, tx_api, executor_recv) +} + +#[tokio::test] +async fn tx_broadcast_enters_pool() { + let (api, pool, client_mock, tx_api, _) = setup_api(); + + // Start at block 1. + let block_1_header = api.push_block(1, vec![], true); + + let uxt = uxt(Alice, ALICE_NONCE); + let xt = hex_string(&uxt.encode()); + + let operation_id: String = + tx_api.call("transaction_unstable_broadcast", rpc_params![&xt]).await.unwrap(); + + // Announce block 1 to `transaction_unstable_broadcast`. + client_mock.trigger_import_stream(block_1_header).await; + + // Ensure the tx propagated from `transaction_unstable_broadcast` to the transaction pool. + + // TODO: Improve testability by extending the `transaction_unstable_broadcast` with + // a middleware trait that intercepts the transaction status for testing. + let mut num_retries = 12; + while num_retries > 0 && pool.status().ready != 1 { + tokio::time::sleep(Duration::from_secs(5)).await; + num_retries -= 1; + } + assert_eq!(1, pool.status().ready); + assert_eq!(uxt.encode().len(), pool.status().ready_bytes); + + // Import block 2 with the transaction included. + let block_2_header = api.push_block(2, vec![uxt.clone()], true); + let block_2 = block_2_header.hash(); + + // Announce block 2 to the pool. + let event = ChainEvent::NewBestBlock { hash: block_2, tree_route: None }; + pool.maintain(event).await; + + assert_eq!(0, pool.status().ready); + + // Stop call can still be made. + let _: () = tx_api + .call("transaction_unstable_stop", rpc_params![&operation_id]) + .await + .unwrap(); +} + +#[tokio::test] +async fn tx_broadcast_invalid_tx() { + let (_, pool, _, tx_api, mut exec_recv) = setup_api(); + + // Invalid parameters. + let err = tx_api + .call::<_, serde_json::Value>("transaction_unstable_broadcast", [1u8]) + .await + .unwrap_err(); + assert_matches!(err, + Error::Call(err) if err.code() == super::error::json_rpc_spec::INVALID_PARAM_ERROR && err.message() == "Invalid params" + ); + + assert_eq!(0, pool.status().ready); + + // Invalid transaction that cannot be decoded. The broadcast silently exits. + let xt = "0xdeadbeef"; + let operation_id: String = + tx_api.call("transaction_unstable_broadcast", rpc_params![&xt]).await.unwrap(); + + assert_eq!(0, pool.status().ready); + + // Await the broadcast future to exit. + // Without this we'd be subject to races, where we try to call the stop before the tx is + // dropped. + exec_recv.recv().await.unwrap(); + + // The broadcast future was dropped, and the operation is no longer active. + // When the operation is not active, either from the tx being finalized or a + // terminal error; the stop method should return an error. + let err = tx_api + .call::<_, serde_json::Value>("transaction_unstable_stop", rpc_params![&operation_id]) + .await + .unwrap_err(); + assert_matches!(err, + Error::Call(err) if err.code() == super::error::json_rpc_spec::INVALID_PARAM_ERROR && err.message() == "Invalid operation id" + ); +} + +#[tokio::test] +async fn tx_invalid_stop() { + let (_, _, _, tx_api, _) = setup_api(); + + // Make an invalid stop call. + let err = tx_api + .call::<_, serde_json::Value>("transaction_unstable_stop", ["invalid_operation_id"]) + .await + .unwrap_err(); + assert_matches!(err, + Error::Call(err) if err.code() == super::error::json_rpc_spec::INVALID_PARAM_ERROR && err.message() == "Invalid operation id" + ); +} diff --git a/substrate/client/rpc-spec-v2/src/transaction/transaction.rs b/substrate/client/rpc-spec-v2/src/transaction/transaction.rs index b2cfa36c9c9..17889b3bad2 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/transaction.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/transaction.rs @@ -29,21 +29,18 @@ use crate::{ }, SubscriptionTaskExecutor, }; +use codec::Decode; +use futures::{StreamExt, TryFutureExt}; use jsonrpsee::{core::async_trait, types::error::ErrorObject, PendingSubscriptionSink}; +use sc_rpc::utils::pipe_from_stream; use sc_transaction_pool_api::{ error::IntoPoolError, BlockHash, TransactionFor, TransactionPool, TransactionSource, TransactionStatus, }; -use std::sync::Arc; - -use sc_rpc::utils::pipe_from_stream; -use sp_api::ProvideRuntimeApi; use sp_blockchain::HeaderBackend; use sp_core::Bytes; use sp_runtime::traits::Block as BlockT; - -use codec::Decode; -use futures::{StreamExt, TryFutureExt}; +use std::sync::Arc; /// An API for transaction RPC calls. pub struct Transaction { @@ -82,7 +79,7 @@ where Pool: TransactionPool + Sync + Send + 'static, Pool::Hash: Unpin, ::Hash: Unpin, - Client: HeaderBackend + ProvideRuntimeApi + Send + Sync + 'static, + Client: HeaderBackend + Send + Sync + 'static, { fn submit_and_watch(&self, pending: PendingSubscriptionSink, xt: Bytes) { let client = self.client.clone(); diff --git a/substrate/client/rpc-spec-v2/src/transaction/transaction_broadcast.rs b/substrate/client/rpc-spec-v2/src/transaction/transaction_broadcast.rs new file mode 100644 index 00000000000..92c83826187 --- /dev/null +++ b/substrate/client/rpc-spec-v2/src/transaction/transaction_broadcast.rs @@ -0,0 +1,251 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! API implementation for broadcasting transactions. + +use crate::{transaction::api::TransactionBroadcastApiServer, SubscriptionTaskExecutor}; +use codec::Decode; +use futures::{FutureExt, Stream, StreamExt}; +use futures_util::stream::AbortHandle; +use jsonrpsee::core::{async_trait, RpcResult}; +use parking_lot::RwLock; +use rand::{distributions::Alphanumeric, Rng}; +use sc_client_api::BlockchainEvents; +use sc_transaction_pool_api::{ + error::IntoPoolError, TransactionFor, TransactionPool, TransactionSource, +}; +use sp_blockchain::HeaderBackend; +use sp_core::Bytes; +use sp_runtime::traits::Block as BlockT; +use std::{collections::HashMap, sync::Arc}; + +use super::error::ErrorBroadcast; + +/// An API for transaction RPC calls. +pub struct TransactionBroadcast { + /// Substrate client. + client: Arc, + /// Transactions pool. + pool: Arc, + /// Executor to spawn subscriptions. + executor: SubscriptionTaskExecutor, + /// The brodcast operation IDs. + broadcast_ids: Arc>>, +} + +/// The state of a broadcast operation. +struct BroadcastState { + /// Handle to abort the running future that broadcasts the transaction. + handle: AbortHandle, +} + +impl TransactionBroadcast { + /// Creates a new [`TransactionBroadcast`]. + pub fn new(client: Arc, pool: Arc, executor: SubscriptionTaskExecutor) -> Self { + TransactionBroadcast { client, pool, executor, broadcast_ids: Default::default() } + } + + /// Generate an unique operation ID for the `transaction_broadcast` RPC method. + pub fn generate_unique_id(&self) -> String { + let generate_operation_id = || { + // The length of the operation ID. + const OPERATION_ID_LEN: usize = 16; + + rand::thread_rng() + .sample_iter(Alphanumeric) + .take(OPERATION_ID_LEN) + .map(char::from) + .collect::() + }; + + let mut id = generate_operation_id(); + + let broadcast_ids = self.broadcast_ids.read(); + + while broadcast_ids.contains_key(&id) { + id = generate_operation_id(); + } + + id + } +} + +/// Currently we treat all RPC transactions as externals. +/// +/// Possibly in the future we could allow opt-in for special treatment +/// of such transactions, so that the block authors can inject +/// some unique transactions via RPC and have them included in the pool. +const TX_SOURCE: TransactionSource = TransactionSource::External; + +#[async_trait] +impl TransactionBroadcastApiServer for TransactionBroadcast +where + Pool: TransactionPool + Sync + Send + 'static, + Pool::Error: IntoPoolError, + ::Hash: Unpin, + Client: HeaderBackend + BlockchainEvents + Send + Sync + 'static, +{ + fn broadcast(&self, bytes: Bytes) -> RpcResult> { + let pool = self.pool.clone(); + + // The unique ID of this operation. + let id = self.generate_unique_id(); + + let mut best_block_import_stream = + Box::pin(self.client.import_notification_stream().filter_map( + |notification| async move { notification.is_new_best.then_some(notification.hash) }, + )); + + let broadcast_transaction_fut = async move { + // There is nothing we could do with an extrinsic of invalid format. + let Ok(decoded_extrinsic) = TransactionFor::::decode(&mut &bytes[..]) else { + return; + }; + + // Flag to determine if the we should broadcast the transaction again. + let mut is_done = false; + + while !is_done { + // Wait for the last block to become available. + let Some(best_block_hash) = + last_stream_element(&mut best_block_import_stream).await + else { + return; + }; + + let mut stream = match pool + .submit_and_watch(best_block_hash, TX_SOURCE, decoded_extrinsic.clone()) + .await + { + Ok(stream) => stream, + // The transaction was not included to the pool. + Err(e) => { + let Ok(pool_err) = e.into_pool_error() else { return }; + + if pool_err.is_retriable() { + // Try to resubmit the transaction at a later block for + // recoverable errors. + continue + } else { + return; + } + }, + }; + + while let Some(event) = stream.next().await { + // Check if the transaction could be submitted again + // at a later time. + if event.is_retriable() { + break; + } + + // Stop if this is the final event of the transaction stream + // and the event is not retriable. + if event.is_final() { + is_done = true; + break; + } + } + } + }; + + // Convert the future into an abortable future, for easily terminating it from the + // `transaction_stop` method. + let (fut, handle) = futures::future::abortable(broadcast_transaction_fut); + let broadcast_ids = self.broadcast_ids.clone(); + let drop_id = id.clone(); + // The future expected by the executor must be `Future` instead of + // `Future>`. + let fut = fut.map(move |_| { + // Remove the entry from the broadcast IDs map. + broadcast_ids.write().remove(&drop_id); + }); + + // Keep track of this entry and the abortable handle. + { + let mut broadcast_ids = self.broadcast_ids.write(); + broadcast_ids.insert(id.clone(), BroadcastState { handle }); + } + + sc_rpc::utils::spawn_subscription_task(&self.executor, fut); + + Ok(Some(id)) + } + + fn stop_broadcast(&self, operation_id: String) -> Result<(), ErrorBroadcast> { + let mut broadcast_ids = self.broadcast_ids.write(); + + let Some(broadcast_state) = broadcast_ids.remove(&operation_id) else { + return Err(ErrorBroadcast::InvalidOperationID) + }; + + broadcast_state.handle.abort(); + + Ok(()) + } +} + +/// Returns the last element of the providided stream, or `None` if the stream is closed. +async fn last_stream_element(stream: &mut S) -> Option +where + S: Stream + Unpin, +{ + let Some(mut element) = stream.next().await else { return None }; + + // We are effectively polling the stream for the last available item at this time. + // The `now_or_never` returns `None` if the stream is `Pending`. + // + // If the stream contains `Hash0x1 Hash0x2 Hash0x3 Hash0x4`, we want only `Hash0x4`. + while let Some(next) = stream.next().now_or_never() { + let Some(next) = next else { + // Nothing to do if the stream terminated. + return None + }; + element = next; + } + + Some(element) +} + +#[cfg(test)] +mod tests { + use super::*; + use tokio_stream::wrappers::ReceiverStream; + + #[tokio::test] + async fn check_last_stream_element() { + let (tx, rx) = tokio::sync::mpsc::channel(16); + + let mut stream = ReceiverStream::new(rx); + // Check the stream with one element queued. + tx.send(1).await.unwrap(); + assert_eq!(last_stream_element(&mut stream).await, Some(1)); + + // Check the stream with multiple elements. + tx.send(1).await.unwrap(); + tx.send(2).await.unwrap(); + tx.send(3).await.unwrap(); + assert_eq!(last_stream_element(&mut stream).await, Some(3)); + + // Drop the stream with some elements + tx.send(1).await.unwrap(); + tx.send(2).await.unwrap(); + drop(tx); + assert_eq!(last_stream_element(&mut stream).await, None); + } +} -- GitLab From e661dc0b961af32cdc81167530fe9d8ad5b7fa09 Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Mon, 12 Feb 2024 18:20:09 +0100 Subject: [PATCH 046/133] Add bridge zombienet test back to the CI (#3264) Related to https://github.com/paritytech/polkadot-sdk/issues/3176 This PR only adds the first bridge zombienet test back to the CI after fixing it, reverting https://github.com/paritytech/polkadot-sdk/pull/3071 Credits to @svyatonik for building all the CI infrastructure around this. --- .gitlab/pipeline/build.yml | 18 ++++++ .gitlab/pipeline/publish.yml | 22 +++++++ .gitlab/pipeline/zombienet.yml | 2 + .gitlab/pipeline/zombienet/bridges.yml | 55 ++++++++++++++++++ ...ridges_zombienet_tests_injected.Dockerfile | 58 +++++++++++++++++++ 5 files changed, 155 insertions(+) create mode 100644 .gitlab/pipeline/zombienet/bridges.yml create mode 100644 docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile diff --git a/.gitlab/pipeline/build.yml b/.gitlab/pipeline/build.yml index 482fc222aa0..42aabc2635d 100644 --- a/.gitlab/pipeline/build.yml +++ b/.gitlab/pipeline/build.yml @@ -395,3 +395,21 @@ build-subkey-linux: # after_script: [""] # tags: # - osx + +# bridges + +# we need some non-binary artifacts in our bridges+zombienet image +prepare-bridges-zombienet-artifacts: + stage: build + extends: + - .docker-env + - .common-refs + - .run-immediately + - .collect-artifacts + before_script: + - mkdir -p ./artifacts/bridges-polkadot-sdk/bridges + - mkdir -p ./artifacts/bridges-polkadot-sdk/cumulus/zombienet + script: + - cp -r bridges/zombienet ./artifacts/bridges-polkadot-sdk/bridges/zombienet + - cp -r cumulus/scripts ./artifacts/bridges-polkadot-sdk/cumulus/scripts + - cp -r cumulus/zombienet/bridge-hubs ./artifacts/bridges-polkadot-sdk/cumulus/zombienet/bridge-hubs diff --git a/.gitlab/pipeline/publish.yml b/.gitlab/pipeline/publish.yml index 3b77f5363f6..b73acb560f6 100644 --- a/.gitlab/pipeline/publish.yml +++ b/.gitlab/pipeline/publish.yml @@ -66,6 +66,8 @@ publish-rustdoc: # note: images are used not only in zombienet but also in rococo, wococo and versi .build-push-image: image: $BUILDAH_IMAGE + extends: + - .zombienet-refs variables: DOCKERFILE: "" # docker/path-to.Dockerfile IMAGE_NAME: "" # docker.io/paritypr/image_name @@ -77,6 +79,7 @@ publish-rustdoc: --build-arg VCS_REF="${CI_COMMIT_SHA}" --build-arg BUILD_DATE="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" --build-arg IMAGE_NAME="${IMAGE_NAME}" + --build-arg ZOMBIENET_IMAGE="${ZOMBIENET_IMAGE}" --tag "$IMAGE_NAME:${DOCKER_IMAGES_VERSION}" --file ${DOCKERFILE} . - echo "$PARITYPR_PASS" | @@ -163,3 +166,22 @@ build-push-image-substrate-pr: variables: DOCKERFILE: "docker/dockerfiles/substrate_injected.Dockerfile" IMAGE_NAME: "docker.io/paritypr/substrate" + +# unlike other images, bridges+zombienet image is based on Zombienet image that pulls required binaries +# from other fresh images (polkadot and cumulus) +build-push-image-bridges-zombienet-tests: + stage: publish + extends: + - .kubernetes-env + - .common-refs + - .build-push-image + needs: + - job: build-linux-stable + artifacts: true + - job: build-linux-stable-cumulus + artifacts: true + - job: prepare-bridges-zombienet-artifacts + artifacts: true + variables: + DOCKERFILE: "docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile" + IMAGE_NAME: "docker.io/paritypr/bridges-zombienet-tests" diff --git a/.gitlab/pipeline/zombienet.yml b/.gitlab/pipeline/zombienet.yml index a1d4db580cc..55120e66d0e 100644 --- a/.gitlab/pipeline/zombienet.yml +++ b/.gitlab/pipeline/zombienet.yml @@ -10,3 +10,5 @@ include: - .gitlab/pipeline/zombienet/cumulus.yml # polkadot tests - .gitlab/pipeline/zombienet/polkadot.yml + # bridges tests + - .gitlab/pipeline/zombienet/bridges.yml diff --git a/.gitlab/pipeline/zombienet/bridges.yml b/.gitlab/pipeline/zombienet/bridges.yml new file mode 100644 index 00000000000..f2de526c870 --- /dev/null +++ b/.gitlab/pipeline/zombienet/bridges.yml @@ -0,0 +1,55 @@ +# This file is part of .gitlab-ci.yml +# Here are all jobs that are executed during "zombienet" stage for bridges + +# common settings for all zombienet jobs +.zombienet-bridges-common: + extends: + - .kubernetes-env + - .zombienet-refs + rules: + - !reference [.build-refs, rules] + - if: $CI_COMMIT_REF_NAME =~ /^gh-readonly-queue.*$/ + variables: + DOCKER_IMAGES_VERSION: ${CI_COMMIT_SHORT_SHA} + before_script: + - echo "Zombienet Tests Config" + - echo "${ZOMBIENET_IMAGE}" + - echo "${GH_DIR}" + - echo "${LOCAL_DIR}" + - ls "${LOCAL_DIR}" + - export DEBUG=zombie,zombie::network-node + - export ZOMBIENET_INTEGRATION_TEST_IMAGE="${BRIDGES_ZOMBIENET_TESTS_IMAGE}":${BRIDGES_ZOMBIENET_TESTS_IMAGE_TAG} + - echo "${ZOMBIENET_INTEGRATION_TEST_IMAGE}" + stage: zombienet + image: "${BRIDGES_ZOMBIENET_TESTS_IMAGE}:${BRIDGES_ZOMBIENET_TESTS_IMAGE_TAG}" + needs: + - job: build-push-image-bridges-zombienet-tests + artifacts: true + variables: + BRIDGES_ZOMBIENET_TESTS_IMAGE_TAG: ${DOCKER_IMAGES_VERSION} + BRIDGES_ZOMBIENET_TESTS_IMAGE: "docker.io/paritypr/bridges-zombienet-tests" + GH_DIR: "https://github.com/paritytech/polkadot-sdk/tree/${CI_COMMIT_SHA}/bridges/zombienet" + LOCAL_DIR: "/builds/parity/mirrors/polkadot-sdk/bridges/zombienet" + FF_DISABLE_UMASK_FOR_DOCKER_EXECUTOR: 1 + RUN_IN_CONTAINER: "1" + artifacts: + name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}_zombienet_bridge_tests" + when: always + expire_in: 2 days + paths: + - ./zombienet-logs + after_script: + - mkdir -p ./zombienet-logs + # copy general logs + - cp -r /tmp/bridges-tests-run-*/logs/* ./zombienet-logs/ + # copy logs of rococo nodes + - cp -r /tmp/bridges-tests-run-*/bridge_hub_rococo_local_network/*.log ./zombienet-logs/ + # copy logs of westend nodes + - cp -r /tmp/bridges-tests-run-*/bridge_hub_westend_local_network/*.log ./zombienet-logs/ + +zombienet-bridges-0001-asset-transfer-works: + extends: + - .zombienet-bridges-common + script: + - /home/nonroot/bridges-polkadot-sdk/bridges/zombienet/run-new-test.sh 0001-asset-transfer --docker + - echo "Done" diff --git a/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile b/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile new file mode 100644 index 00000000000..ec8881c5a33 --- /dev/null +++ b/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile @@ -0,0 +1,58 @@ +# this image is built on top of existing Zombienet image +ARG ZOMBIENET_IMAGE +# this image uses substrate-relay image built elsewhere +ARG SUBSTRATE_RELAY_IMAGE=docker.io/paritytech/substrate-relay:v2023-11-07-rococo-westend-initial-relayer + +# metadata +ARG VCS_REF +ARG BUILD_DATE +ARG IMAGE_NAME + +# we need `substrate-relay` binary, built elsewhere +FROM ${SUBSTRATE_RELAY_IMAGE} as relay-builder + +# the base image is the zombienet image - we are planning to run zombienet tests using native +# provider here +FROM ${ZOMBIENET_IMAGE} + +LABEL io.parity.image.authors="devops-team@parity.io" \ + io.parity.image.vendor="Parity Technologies" \ + io.parity.image.title="${IMAGE_NAME}" \ + io.parity.image.description="Bridges Zombienet tests." \ + io.parity.image.source="https://github.com/paritytech/polkadot-sdk/blob/${VCS_REF}/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile" \ + io.parity.image.revision="${VCS_REF}" \ + io.parity.image.created="${BUILD_DATE}" \ + io.parity.image.documentation="https://github.com/paritytech/polkadot-sdk/bridges/zombienet" + +# show backtraces +ENV RUST_BACKTRACE 1 +USER root + +# for native provider to work (TODO: fix in zn docker?) +RUN apt-get update && apt-get install -y procps sudo +RUN yarn global add @polkadot/api-cli + +# add polkadot binary to the docker image +COPY ./artifacts/polkadot /usr/local/bin/ +COPY ./artifacts/polkadot-execute-worker /usr/local/bin/ +COPY ./artifacts/polkadot-prepare-worker /usr/local/bin/ +# add polkadot-parachain binary to the docker image +COPY ./artifacts/polkadot-parachain /usr/local/bin +# copy substrate-relay to the docker image +COPY --from=relay-builder /home/user/substrate-relay /usr/local/bin/ +# we need bridges zombienet runner and tests +RUN mkdir -p /home/nonroot/bridges-polkadot-sdk +COPY ./artifacts/bridges-polkadot-sdk /home/nonroot/bridges-polkadot-sdk +# also prepare `generate_hex_encoded_call` for running +RUN set -eux; \ + cd /home/nonroot/bridges-polkadot-sdk/cumulus/scripts/generate_hex_encoded_call; \ + npm install + +# check if executable works in this container +USER nonroot +RUN /usr/local/bin/polkadot --version +RUN /usr/local/bin/polkadot-parachain --version +RUN /usr/local/bin/substrate-relay --version + +# https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:{PORT}#/explorer +EXPOSE 9942 9910 8943 9945 9010 8945 -- GitLab From 396fffa5a7af13976239774beeb4563716764ad8 Mon Sep 17 00:00:00 2001 From: Radha <86818441+DrW3RK@users.noreply.github.com> Date: Mon, 12 Feb 2024 21:07:09 +0100 Subject: [PATCH 047/133] AlreadyVoting Error - Remove ambiguity (#3280) This error suggests using either `unvote` or `reap_vote` calls which are unavailable in the pallet. The only available call for this is `remove_vote`. EDIT: Please ignore my earlier write-up. I was able to delegate with conviction after calling `remove_vote` on all decided proposals --------- Co-authored-by: command-bot <> --- substrate/frame/conviction-voting/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/substrate/frame/conviction-voting/src/lib.rs b/substrate/frame/conviction-voting/src/lib.rs index 1d6fbaa3869..466fc70a619 100644 --- a/substrate/frame/conviction-voting/src/lib.rs +++ b/substrate/frame/conviction-voting/src/lib.rs @@ -185,7 +185,7 @@ pub mod pallet { /// The account is already delegating. AlreadyDelegating, /// The account currently has votes attached to it and the operation cannot succeed until - /// these are removed, either through `unvote` or `reap_vote`. + /// these are removed through `remove_vote`. AlreadyVoting, /// Too high a balance was provided that the account cannot afford. InsufficientFunds, @@ -231,8 +231,8 @@ pub mod pallet { /// /// The dispatch origin of this call must be _Signed_, and the signing account must either: /// - be delegating already; or - /// - have no voting activity (if there is, then it will need to be removed/consolidated - /// through `reap_vote` or `unvote`). + /// - have no voting activity (if there is, then it will need to be removed through + /// `remove_vote`). /// /// - `to`: The account whose voting the `target` account's voting power will follow. /// - `class`: The class of polls to delegate. To delegate multiple classes, multiple calls -- GitLab From 044d914c30e6dd16eb1231c6d9631bd53ebd3635 Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Mon, 12 Feb 2024 18:08:34 -0300 Subject: [PATCH 048/133] Fix(zombienet): allow finality_lag be to be 1 or less and increase timeout (#3299) Change `parityDb` test assertions after a quick check with @alexggh in order to resolve failures in `zombienet-polkadot-misc-0001-parachains-paritydb`(e.g https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/jobs/5186277). Thx! --- .../zombienet_tests/misc/0001-paritydb.zndsl | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/polkadot/zombienet_tests/misc/0001-paritydb.zndsl b/polkadot/zombienet_tests/misc/0001-paritydb.zndsl index 4a22311de76..e0260cb9fdd 100644 --- a/polkadot/zombienet_tests/misc/0001-paritydb.zndsl +++ b/polkadot/zombienet_tests/misc/0001-paritydb.zndsl @@ -31,28 +31,28 @@ validator-0: parachain 2008 is registered validator-0: parachain 2009 is registered # Ensure parachains made some progress. -validator-0: parachain 2000 block height is at least 3 within 30 seconds -validator-0: parachain 2001 block height is at least 3 within 30 seconds -validator-0: parachain 2002 block height is at least 3 within 30 seconds -validator-0: parachain 2003 block height is at least 3 within 30 seconds -validator-0: parachain 2004 block height is at least 3 within 30 seconds -validator-0: parachain 2005 block height is at least 3 within 30 seconds -validator-0: parachain 2006 block height is at least 3 within 30 seconds -validator-0: parachain 2007 block height is at least 3 within 30 seconds -validator-0: parachain 2008 block height is at least 3 within 30 seconds -validator-0: parachain 2009 block height is at least 3 within 30 seconds +validator-0: parachain 2000 block height is at least 3 within 60 seconds +validator-0: parachain 2001 block height is at least 3 within 60 seconds +validator-0: parachain 2002 block height is at least 3 within 60 seconds +validator-0: parachain 2003 block height is at least 3 within 60 seconds +validator-0: parachain 2004 block height is at least 3 within 60 seconds +validator-0: parachain 2005 block height is at least 3 within 60 seconds +validator-0: parachain 2006 block height is at least 3 within 60 seconds +validator-0: parachain 2007 block height is at least 3 within 60 seconds +validator-0: parachain 2008 block height is at least 3 within 60 seconds +validator-0: parachain 2009 block height is at least 3 within 60 seconds # Check lag - approval -validator-0: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-1: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-2: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-3: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-4: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-5: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-6: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-7: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-8: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-9: reports polkadot_parachain_approval_checking_finality_lag is 0 +validator-0: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-1: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-2: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-3: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-4: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-5: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-6: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-7: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-8: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-9: reports polkadot_parachain_approval_checking_finality_lag <= 1 # Check lag - dispute conclusion validator-0: reports polkadot_parachain_candidate_disputes_total is 0 -- GitLab From b2a62a56c98d57f67bb29a5065047b9a9dc83774 Mon Sep 17 00:00:00 2001 From: Bruno Galvao Date: Tue, 13 Feb 2024 16:23:22 +0700 Subject: [PATCH 049/133] Use `TEST_WS` in all remote-externalities tests (#3284) Refactor in accordance with https://github.com/paritytech/polkadot-sdk/issues/2245#issuecomment-1937025951 Prior to this PR, the `remote_tests` test module would either use `TEST_WS` or `DEFAULT_HTTP_ENDPOINT`. With the PR, `TEST_WS` is the default for the `remote_tests` test module and the fallback is `DEFAULT_HTTP_ENDPOINT`. The only downside I see to this PR is that for particular tests in the `remote_tests` module, one would want to use a different http endpoint. If that is the case, they would have to manually hardcode the http endpoint for that particular test. Note: The `TEST_WS` node should fulfill the role for all test cases e.g. include child tries. Give it a _try_: ``` TEST_WS=wss://rococo-try-runtime-node.parity-chains.parity.io:443 cargo test --features=remote-test -p frame-remote-externalities -- --nocapture ``` --------- Co-authored-by: Oliver Tale-Yazdi --- .../frame/remote-externalities/src/lib.rs | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/substrate/utils/frame/remote-externalities/src/lib.rs b/substrate/utils/frame/remote-externalities/src/lib.rs index 47c0508485c..c7399468da9 100644 --- a/substrate/utils/frame/remote-externalities/src/lib.rs +++ b/substrate/utils/frame/remote-externalities/src/lib.rs @@ -1263,7 +1263,11 @@ mod tests { #[cfg(all(test, feature = "remote-test"))] mod remote_tests { use super::test_prelude::*; - use std::os::unix::fs::MetadataExt; + use std::{env, os::unix::fs::MetadataExt}; + + fn endpoint() -> String { + env::var("TEST_WS").unwrap_or_else(|_| DEFAULT_HTTP_ENDPOINT.to_string()) + } #[tokio::test] async fn state_version_is_kept_and_can_be_altered() { @@ -1273,6 +1277,7 @@ mod remote_tests { // first, build a snapshot. let ext = Builder::::new() .mode(Mode::Online(OnlineConfig { + transport: endpoint().clone().into(), pallets: vec!["Proxy".to_owned()], child_trie: false, state_snapshot: Some(SnapshotConfig::new(CACHE)), @@ -1314,6 +1319,7 @@ mod remote_tests { // first, build a snapshot. let ext = Builder::::new() .mode(Mode::Online(OnlineConfig { + transport: endpoint().clone().into(), pallets: vec!["Proxy".to_owned()], child_trie: false, state_snapshot: Some(SnapshotConfig::new(CACHE)), @@ -1341,6 +1347,7 @@ mod remote_tests { // create an ext with children keys let child_ext = Builder::::new() .mode(Mode::Online(OnlineConfig { + transport: endpoint().clone().into(), pallets: vec!["Proxy".to_owned()], child_trie: true, state_snapshot: Some(SnapshotConfig::new(CACHE)), @@ -1353,6 +1360,7 @@ mod remote_tests { // create an ext without children keys let ext = Builder::::new() .mode(Mode::Online(OnlineConfig { + transport: endpoint().clone().into(), pallets: vec!["Proxy".to_owned()], child_trie: false, state_snapshot: Some(SnapshotConfig::new(CACHE)), @@ -1378,6 +1386,7 @@ mod remote_tests { .mode(Mode::OfflineOrElseOnline( OfflineConfig { state_snapshot: SnapshotConfig::new(CACHE) }, OnlineConfig { + transport: endpoint().clone().into(), pallets: vec!["Proxy".to_owned()], child_trie: false, state_snapshot: Some(SnapshotConfig::new(CACHE)), @@ -1419,6 +1428,7 @@ mod remote_tests { init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { + transport: endpoint().clone().into(), pallets: vec!["Proxy".to_owned()], child_trie: false, ..Default::default() @@ -1434,6 +1444,7 @@ mod remote_tests { init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { + transport: endpoint().clone().into(), pallets: vec!["Proxy".to_owned(), "Multisig".to_owned()], child_trie: false, ..Default::default() @@ -1451,6 +1462,7 @@ mod remote_tests { Builder::::new() .mode(Mode::Online(OnlineConfig { + transport: endpoint().clone().into(), state_snapshot: Some(SnapshotConfig::new(CACHE)), pallets: vec!["Proxy".to_owned()], child_trie: false, @@ -1480,6 +1492,7 @@ mod remote_tests { init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { + transport: endpoint().clone().into(), state_snapshot: Some(SnapshotConfig::new(CACHE)), pallets: vec!["Crowdloan".to_owned()], child_trie: true, @@ -1511,7 +1524,7 @@ mod remote_tests { init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { - transport: std::option_env!("TEST_WS").unwrap().to_owned().into(), + transport: endpoint().clone().into(), pallets: vec!["Staking".to_owned()], child_trie: false, ..Default::default() @@ -1530,7 +1543,7 @@ mod remote_tests { init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { - transport: std::option_env!("TEST_WS").unwrap().to_owned().into(), + transport: endpoint().clone().into(), ..Default::default() })) .build() @@ -1543,9 +1556,10 @@ mod remote_tests { async fn can_fetch_in_parallel() { init_logger(); - let uri = String::from("wss://kusama-bridge-hub-rpc.polkadot.io:443"); - let mut builder = Builder::::new() - .mode(Mode::Online(OnlineConfig { transport: uri.into(), ..Default::default() })); + let mut builder = Builder::::new().mode(Mode::Online(OnlineConfig { + transport: endpoint().clone().into(), + ..Default::default() + })); builder.init_remote_client().await.unwrap(); let at = builder.as_online().at.unwrap(); -- GitLab From 4c96dca661654ca7511a11f7626d0b677256d3a7 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Tue, 13 Feb 2024 11:01:44 +0100 Subject: [PATCH 050/133] [ci] Fix gitspiegel trigger (#3297) PR removes `pull_request_target` from gitspiegel trigger because it breaks the logic. With `pull_request_target` the action runs in any case even for first-time contributors. cc @mutantcornholio --- .github/workflows/gitspiegel-trigger.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/gitspiegel-trigger.yml b/.github/workflows/gitspiegel-trigger.yml index b338f7a3f62..01058ad74d0 100644 --- a/.github/workflows/gitspiegel-trigger.yml +++ b/.github/workflows/gitspiegel-trigger.yml @@ -13,14 +13,15 @@ on: - unlocked - ready_for_review - reopened + # doesn't work as intended, triggers "workflow_run" webhook in any case # the job doesn't check out any code, so it is relatively safe to run it on any event - pull_request_target: - types: - - opened - - synchronize - - unlocked - - ready_for_review - - reopened + # pull_request_target: + # types: + # - opened + # - synchronize + # - unlocked + # - ready_for_review + # - reopened merge_group: # drop all permissions for GITHUB_TOKEN -- GitLab From 349132f08a0e5cd169934af1fd488feee3251d34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 13 Feb 2024 14:30:28 +0100 Subject: [PATCH 051/133] Fix compilation on latest nightly because of `stdsimd` (#3296) We need to bump `ahash` to make it compile again. Closes: https://github.com/paritytech/polkadot-sdk/issues/3269 --- Cargo.lock | 40 +++++++++---------- .../bin/node-template/{ => env-setup}/.envrc | 0 .../bin/node-template/env-setup/README.md | 9 +++++ .../node-template/{ => env-setup}/flake.lock | 0 .../node-template/{ => env-setup}/flake.nix | 0 .../{ => env-setup}/rust-toolchain.toml | 2 +- .../ci/node-template-release/src/main.rs | 28 ++++++++++++- 7 files changed, 56 insertions(+), 23 deletions(-) rename substrate/bin/node-template/{ => env-setup}/.envrc (100%) create mode 100644 substrate/bin/node-template/env-setup/README.md rename substrate/bin/node-template/{ => env-setup}/flake.lock (100%) rename substrate/bin/node-template/{ => env-setup}/flake.nix (100%) rename substrate/bin/node-template/{ => env-setup}/rust-toolchain.toml (90%) diff --git a/Cargo.lock b/Cargo.lock index 68d399f78a7..67eca4e3f9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -114,9 +114,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom 0.2.10", "once_cell", @@ -125,9 +125,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +checksum = "42cd52102d3df161c77a887b608d7a4897d7cc112886a9537b738a887a03aaff" dependencies = [ "cfg-if", "getrandom 0.2.10", @@ -4375,9 +4375,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.1.1" +version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" +checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" dependencies = [ "cfg-if", "cpufeatures", @@ -4811,7 +4811,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" dependencies = [ - "curve25519-dalek 4.1.1", + "curve25519-dalek 4.1.2", "ed25519", "rand_core 0.6.4", "serde", @@ -4840,7 +4840,7 @@ version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9" dependencies = [ - "curve25519-dalek 4.1.1", + "curve25519-dalek 4.1.2", "ed25519", "hashbrown 0.14.3", "hex", @@ -6229,7 +6229,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.6", + "ahash 0.7.8", ] [[package]] @@ -6238,7 +6238,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.7", + "ahash 0.8.8", ] [[package]] @@ -6247,7 +6247,7 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ - "ahash 0.8.7", + "ahash 0.8.8", "allocator-api2", "serde", ] @@ -8127,7 +8127,7 @@ dependencies = [ "bitflags 1.3.2", "blake2 0.10.6", "c2-chacha", - "curve25519-dalek 4.1.1", + "curve25519-dalek 4.1.2", "either", "hashlink", "lioness", @@ -15764,7 +15764,7 @@ dependencies = [ name = "sc-consensus-grandpa" version = "0.19.0" dependencies = [ - "ahash 0.8.7", + "ahash 0.8.8", "array-bytes 6.1.0", "assert_matches", "async-trait", @@ -16150,7 +16150,7 @@ dependencies = [ name = "sc-network-gossip" version = "0.34.0" dependencies = [ - "ahash 0.8.7", + "ahash 0.8.8", "async-trait", "futures", "futures-timer", @@ -16842,7 +16842,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "772575a524feeb803e5b0fcbc6dd9f367e579488197c94c6e4023aad2305774d" dependencies = [ - "ahash 0.8.7", + "ahash 0.8.8", "cfg-if", "hashbrown 0.13.2", ] @@ -16888,7 +16888,7 @@ dependencies = [ "aead 0.5.2", "arrayref", "arrayvec 0.7.4", - "curve25519-dalek 4.1.1", + "curve25519-dalek 4.1.2", "getrandom_or_panic", "merlin 3.0.0", "rand_core 0.6.4", @@ -17577,7 +17577,7 @@ dependencies = [ "aes-gcm 0.9.4", "blake2 0.10.6", "chacha20poly1305", - "curve25519-dalek 4.1.1", + "curve25519-dalek 4.1.2", "rand_core 0.6.4", "ring 0.16.20", "rustc_version 0.4.0", @@ -18858,7 +18858,7 @@ name = "sp-statement-store" version = "10.0.0" dependencies = [ "aes-gcm 0.10.3", - "curve25519-dalek 4.1.1", + "curve25519-dalek 4.1.2", "ed25519-dalek", "hkdf", "parity-scale-codec", @@ -18985,7 +18985,7 @@ dependencies = [ name = "sp-trie" version = "29.0.0" dependencies = [ - "ahash 0.8.7", + "ahash 0.8.8", "array-bytes 6.1.0", "criterion 0.4.0", "hash-db", @@ -22070,7 +22070,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" dependencies = [ - "curve25519-dalek 4.1.1", + "curve25519-dalek 4.1.2", "rand_core 0.6.4", "serde", "zeroize", diff --git a/substrate/bin/node-template/.envrc b/substrate/bin/node-template/env-setup/.envrc similarity index 100% rename from substrate/bin/node-template/.envrc rename to substrate/bin/node-template/env-setup/.envrc diff --git a/substrate/bin/node-template/env-setup/README.md b/substrate/bin/node-template/env-setup/README.md new file mode 100644 index 00000000000..a7955872d3f --- /dev/null +++ b/substrate/bin/node-template/env-setup/README.md @@ -0,0 +1,9 @@ +# Env setup + +Special files for setting up an environment to work with the template: + +- `rust-toolchain.toml` when working with `rustup`. +- `flake.nix` when working with `nix`. + +These files will be copied by the installer script to the main directory. They are +put into this special directory to not interfere with the normal CI. diff --git a/substrate/bin/node-template/flake.lock b/substrate/bin/node-template/env-setup/flake.lock similarity index 100% rename from substrate/bin/node-template/flake.lock rename to substrate/bin/node-template/env-setup/flake.lock diff --git a/substrate/bin/node-template/flake.nix b/substrate/bin/node-template/env-setup/flake.nix similarity index 100% rename from substrate/bin/node-template/flake.nix rename to substrate/bin/node-template/env-setup/flake.nix diff --git a/substrate/bin/node-template/rust-toolchain.toml b/substrate/bin/node-template/env-setup/rust-toolchain.toml similarity index 90% rename from substrate/bin/node-template/rust-toolchain.toml rename to substrate/bin/node-template/env-setup/rust-toolchain.toml index 2a35c6ed07c..f81199a2249 100644 --- a/substrate/bin/node-template/rust-toolchain.toml +++ b/substrate/bin/node-template/env-setup/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "nightly" +channel = "stable" components = [ "cargo", "clippy", diff --git a/substrate/scripts/ci/node-template-release/src/main.rs b/substrate/scripts/ci/node-template-release/src/main.rs index fc8089f3051..2bcda2944fc 100644 --- a/substrate/scripts/ci/node-template-release/src/main.rs +++ b/substrate/scripts/ci/node-template-release/src/main.rs @@ -18,6 +18,7 @@ use std::{ collections::HashMap, + ffi::OsString, fs::{self, File, OpenOptions}, path::{Path, PathBuf}, process::Command, @@ -56,9 +57,24 @@ struct Options { } /// Copy the `node-template` to the given path. -fn copy_node_template(node_template: &Path, dest_path: &Path) { +fn copy_node_template(node_template: &Path, node_template_folder: &OsString, dest_path: &Path) { let options = CopyOptions::new(); dir::copy(node_template, dest_path, &options).expect("Copies node-template to tmp dir"); + + let dest_path = dest_path.join(node_template_folder); + + dir::get_dir_content(dest_path.join("env-setup")) + .expect("`env-setup` directory should exist") + .files + .iter() + .for_each(|f| { + fs::copy( + f, + dest_path.join(PathBuf::from(f).file_name().expect("File has a file name.")), + ) + .expect("Copying from `env-setup` directory works"); + }); + dir::remove(dest_path.join("env-setup")).expect("Deleting `env-setup works`"); } /// Find all `Cargo.toml` files in the given path. @@ -195,6 +211,9 @@ fn update_root_cargo_toml( commit_id: &str, ) { let mut workspace = Table::new(); + + workspace.insert("resolver", value("2")); + workspace.insert("members", value(Array::from_iter(members.iter()))); let mut workspace_dependencies = Table::new(); deps.values() @@ -216,6 +235,8 @@ fn update_root_cargo_toml( workspace.insert("package", Item::Table(package)); workspace.insert("dependencies", Item::Table(workspace_dependencies)); + + workspace.insert("lints", Item::Table(Table::new())); cargo_toml.insert("workspace", Item::Table(workspace)); let mut panic_unwind = Table::new(); @@ -294,7 +315,7 @@ fn main() { .file_name() .expect("Node template folder is last element of path") .to_owned(); - copy_node_template(&options.node_template, build_dir.path()); + copy_node_template(&options.node_template, &node_template_folder, build_dir.path()); // The path to the node-template in the build dir. let node_template_path = build_dir.path().join(node_template_folder); @@ -429,6 +450,7 @@ frame-system = { workspace = true } ); let expected_toml = r#"[workspace] +resolver = "2" members = ["node", "pallets/template", "runtime"] [workspace.package] @@ -438,6 +460,8 @@ edition = "2021" frame-system = { version = "4.0.0-dev", default-features = true, git = "https://github.com/paritytech/polkadot-sdk.git", rev = "commit_id" } sp-io = { version = "7.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", rev = "commit_id" } +[workspace.lints] + [profile] [profile.release] -- GitLab From ec6bf5d0a52161bda0eccd2d797dffab4213900a Mon Sep 17 00:00:00 2001 From: s0me0ne-unkn0wn <48632512+s0me0ne-unkn0wn@users.noreply.github.com> Date: Tue, 13 Feb 2024 15:31:33 +0100 Subject: [PATCH 052/133] Use dynamic aura slot duration in lookahead collator (#3211) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It's a follow-up of #2949. It enables the lookahead collator to dynamically adjust the aura slot size, which may change during the runtime upgrade. It also addressed a couple of issues with time constants I missed in the original PR. Good news: it works. The parachain successfully switches from sync backing with 12s slots to async backing with 6s slots. Bad news: during the transitional period of a single block in which the actual runtime upgrade is performed, it still gets the old slot duration of 12s (as it gets it from the best block), resulting in a runtime panic (logs follow). That doesn't affect the following block production of the parachain. Ideas on how to improve the situation are appreciated.
``` 2024-02-05 12:59:36.373 INFO tokio-runtime-worker sc_basic_authorship::basic_authorship: [Parachain] 🙌 Starting consensus session on top of parent 0x6fd2d8f904f12c22531bfabf77b16dc84a6a29e45d9ae358aa6547fbf3f0438b 2024-02-05 12:59:36.373 ERROR tokio-runtime-worker runtime: [Parachain] panicked at /home/s0me0ne/wrk/parity/polkadot-sdk/cumulus/pallets/aura-ext/src/consensus_hook.rs:69:9: assertion `left == right` failed: slot number mismatch left: Slot(142261198) right: Slot(284522396) 2024-02-05 12:59:36.373 WARN tokio-runtime-worker sp_state_machine::overlayed_changes::changeset: [Parachain] 1 storage transactions are left open by the runtime. Those will be rolled back. 2024-02-05 12:59:36.373 WARN tokio-runtime-worker sp_state_machine::overlayed_changes::changeset: [Parachain] 1 storage transactions are left open by the runtime. Those will be rolled back. 2024-02-05 12:59:36.373 WARN tokio-runtime-worker basic-authorship: [Parachain] ❗ Inherent extrinsic returned unexpected error: Error at calling runtime api: Execution failed: Execution aborted due to trap: wasm trap: wasm `unreachable` instruction executed WASM backtrace: error while executing at wasm backtrace: 0: 0x4e4a3b - !rust_begin_unwind 1: 0x46cf57 - !core::panicking::panic_fmt::h3c280dba88683724 2: 0x46d238 - !core::panicking::assert_failed_inner::hebac5970933beb4d 3: 0x3d00fc - !core::panicking::assert_failed::h640a47e2fb5dfb4b 4: 0xd0db3 - !frame_support::storage::transactional::with_transaction::hcbc31515f81b2ee1 5: 0x34d654 - ! as frame_support::traits::dispatch::UnfilteredDispatchable>::dispatch_bypass_filter::{{closure}}::hb7c2c9a11fa88301 6: 0x3547db - !environmental::local_key::LocalKey::with::h783f2605ae27d6d3 7: 0x7f454 - !::dispatch_bypass_filter::h5e11a01ab97c06c7 8: 0x7f237 - !::dispatch::h7f8ae4a8fede71ca 9: 0x26a0f3 - !frame_executive::Executive::apply_extrinsic::h75e524ff34738391 10: 0x282211 - !BlockBuilder_apply_extrinsic. Dropping. 2024-02-05 12:59:36.374 ERROR tokio-runtime-worker runtime: [Parachain] panicked at /home/s0me0ne/wrk/parity/polkadot-sdk/substrate/frame/aura/src/lib.rs:416:9: assertion `left == right` failed: Timestamp slot must match `CurrentSlot` left: Slot(142261198) right: Slot(284522396) 2024-02-05 12:59:36.374 WARN tokio-runtime-worker sp_state_machine::overlayed_changes::changeset: [Parachain] 1 storage transactions are left open by the runtime. Those will be rolled back. 2024-02-05 12:59:36.374 WARN tokio-runtime-worker sp_state_machine::overlayed_changes::changeset: [Parachain] 1 storage transactions are left open by the runtime. Those will be rolled back. 2024-02-05 12:59:36.374 WARN tokio-runtime-worker basic-authorship: [Parachain] ❗ Inherent extrinsic returned unexpected error: Error at calling runtime api: Execution failed: Execution aborted due to trap: wasm trap: wasm `unreachable` instruction executed WASM backtrace: error while executing at wasm backtrace: 0: 0x4e4a3b - !rust_begin_unwind 1: 0x46cf57 - !core::panicking::panic_fmt::h3c280dba88683724 2: 0x46d238 - !core::panicking::assert_failed_inner::hebac5970933beb4d 3: 0x3d00fc - !core::panicking::assert_failed::h640a47e2fb5dfb4b 4: 0x9ece6 - !frame_support::storage::transactional::with_transaction::h26f75cb9f9462088 5: 0x356d7e - !environmental::local_key::LocalKey::with::hbcf2d4e17b48fdb5 6: 0x7f507 - !::dispatch_bypass_filter::h5e11a01ab97c06c7 7: 0x7f237 - !::dispatch::h7f8ae4a8fede71ca 8: 0x26a0f3 - !frame_executive::Executive::apply_extrinsic::h75e524ff34738391 9: 0x282211 - !BlockBuilder_apply_extrinsic. Dropping. 2024-02-05 12:59:36.374 DEBUG tokio-runtime-worker runtime::xcmp-queue-migration: [Parachain] Lazy migration finished: item gone 2024-02-05 12:59:36.374 ERROR tokio-runtime-worker runtime: [Parachain] panicked at /home/s0me0ne/wrk/parity/polkadot-sdk/cumulus/pallets/parachain-system/src/lib.rs:265:18: set_validation_data inherent needs to be present in every block! 2024-02-05 12:59:36.374 ERROR tokio-runtime-worker aura::cumulus: [Parachain] err=Error { inner: Proposing Caused by: 0: Error at calling runtime api: Execution failed: Execution aborted due to trap: wasm trap: wasm `unreachable` instruction executed WASM backtrace: error while executing at wasm backtrace: 0: 0x4e4a3b - !rust_begin_unwind 1: 0x46cf57 - !core::panicking::panic_fmt::h3c280dba88683724 2: 0x46da8b - !core::option::expect_failed::hdf18d99c3adabca7 3: 0x2134cb - ! as frame_support::traits::hooks::OnFinalize<<<::Block as sp_runtime::traits::HeaderProvider>::HeaderT as sp_runtime::traits::Header>::Number>>::on_finalize::hf98aac39802896ba 4: 0x26a9d6 - !frame_executive::Executive::idle_and_finalize_hook::h32775c0df0749d92 5: 0x26ad9f - !frame_executive::Executive::finalize_block::h15e5a1a6b9ca8032 6: 0x2822bd - !BlockBuilder_finalize_block 1: Execution failed: Execution aborted due to trap: wasm trap: wasm `unreachable` instruction executed WASM backtrace: error while executing at wasm backtrace: 0: 0x4e4a3b - !rust_begin_unwind 1: 0x46cf57 - !core::panicking::panic_fmt::h3c280dba88683724 2: 0x46da8b - !core::option::expect_failed::hdf18d99c3adabca7 3: 0x2134cb - ! as frame_support::traits::hooks::OnFinalize<<<::Block as sp_runtime::traits::HeaderProvider>::HeaderT as sp_runtime::traits::Header>::Number>>::on_finalize::hf98aac39802896ba 4: 0x26a9d6 - !frame_executive::Executive::idle_and_finalize_hook::h32775c0df0749d92 5: 0x26ad9f - !frame_executive::Executive::finalize_block::h15e5a1a6b9ca8032 6: 0x2822bd - !BlockBuilder_finalize_block } ```
--------- Co-authored-by: Bastian Köcher --- .../consensus/aura/src/collators/lookahead.rs | 60 ++++++++++--------- .../contracts/contracts-rococo/src/lib.rs | 4 +- .../runtimes/people/people-rococo/src/lib.rs | 4 +- cumulus/polkadot-parachain/src/service.rs | 21 ------- 4 files changed, 37 insertions(+), 52 deletions(-) diff --git a/cumulus/client/consensus/aura/src/collators/lookahead.rs b/cumulus/client/consensus/aura/src/collators/lookahead.rs index e24b7f6f1c9..a9f33173d83 100644 --- a/cumulus/client/consensus/aura/src/collators/lookahead.rs +++ b/cumulus/client/consensus/aura/src/collators/lookahead.rs @@ -59,7 +59,7 @@ use sp_api::ProvideRuntimeApi; use sp_application_crypto::AppPublic; use sp_blockchain::HeaderBackend; use sp_consensus::SyncOracle; -use sp_consensus_aura::{AuraApi, Slot, SlotDuration}; +use sp_consensus_aura::{AuraApi, Slot}; use sp_core::crypto::Pair; use sp_inherents::CreateInherentDataProviders; use sp_keystore::KeystorePtr; @@ -95,8 +95,6 @@ pub struct Params { pub para_id: ParaId, /// A handle to the relay-chain client's "Overseer" or task orchestrator. pub overseer_handle: OverseerHandle, - /// The length of slots in this chain. - pub slot_duration: SlotDuration, /// The length of slots in the relay chain. pub relay_chain_slot_duration: Duration, /// The underlying block proposer this should call into. @@ -214,26 +212,6 @@ where }, }; - let (slot_now, timestamp) = match consensus_common::relay_slot_and_timestamp( - &relay_parent_header, - params.relay_chain_slot_duration, - ) { - None => continue, - Some((r_s, t)) => { - let our_slot = Slot::from_timestamp(t, params.slot_duration); - tracing::debug!( - target: crate::LOG_TARGET, - relay_slot = ?r_s, - para_slot = ?our_slot, - timestamp = ?t, - slot_duration = ?params.slot_duration, - relay_chain_slot_duration = ?params.relay_chain_slot_duration, - "Adjusted relay-chain slot to parachain slot" - ); - (our_slot, t) - }, - }; - let parent_search_params = ParentSearchParams { relay_parent, para_id: params.para_id, @@ -272,14 +250,39 @@ where let para_client = &*params.para_client; let keystore = ¶ms.keystore; let can_build_upon = |block_hash| { - can_build_upon::<_, _, P>( + let slot_duration = match sc_consensus_aura::standalone::slot_duration_at( + &*params.para_client, + block_hash, + ) { + Ok(sd) => sd, + Err(err) => { + tracing::error!(target: crate::LOG_TARGET, ?err, "Failed to acquire parachain slot duration"); + return None + }, + }; + tracing::debug!(target: crate::LOG_TARGET, ?slot_duration, ?block_hash, "Parachain slot duration acquired"); + let (relay_slot, timestamp) = consensus_common::relay_slot_and_timestamp( + &relay_parent_header, + params.relay_chain_slot_duration, + )?; + let slot_now = Slot::from_timestamp(timestamp, slot_duration); + tracing::debug!( + target: crate::LOG_TARGET, + ?relay_slot, + para_slot = ?slot_now, + ?timestamp, + ?slot_duration, + relay_chain_slot_duration = ?params.relay_chain_slot_duration, + "Adjusted relay-chain slot to parachain slot" + ); + Some(can_build_upon::<_, _, P>( slot_now, timestamp, block_hash, included_block, para_client, &keystore, - ) + )) }; // Sort by depth, ascending, to choose the longest chain. @@ -301,9 +304,12 @@ where // This needs to change to support elastic scaling, but for continuously // scheduled chains this ensures that the backlog will grow steadily. for n_built in 0..2 { - let slot_claim = match can_build_upon(parent_hash).await { + let slot_claim = match can_build_upon(parent_hash) { + Some(fut) => match fut.await { + None => break, + Some(c) => c, + }, None => break, - Some(c) => c, }; tracing::debug!( diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs index a0eb74657bd..15a5d9b04a9 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs @@ -58,10 +58,10 @@ use frame_system::limits::{BlockLength, BlockWeights}; pub use parachains_common as common; use parachains_common::{ impls::DealWithFees, message_queue::*, AccountId, BlockNumber, Hash, Header, Nonce, Signature, - AVERAGE_ON_INITIALIZE_RATIO, MINUTES, NORMAL_DISPATCH_RATIO, + AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, }; pub use parachains_common::{AuraId, Balance}; -use testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee}; +use testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee, time::*}; use xcm_config::CollatorSelectionUpdateOrigin; #[cfg(any(feature = "std", test))] diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs index 5e96395532e..9e7dd6c884b 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -44,7 +44,7 @@ use parachains_common::{ impls::DealWithFees, message_queue::{NarrowOriginToSibling, ParaIdToSibling}, AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature, AVERAGE_ON_INITIALIZE_RATIO, - HOURS, NORMAL_DISPATCH_RATIO, + NORMAL_DISPATCH_RATIO, }; use polkadot_runtime_common::{identity_migrator, BlockHashCount, SlowAdjustingFeeUpdate}; use sp_api::impl_runtime_apis; @@ -63,7 +63,7 @@ use sp_std::prelude::*; #[cfg(feature = "std")] use sp_version::NativeVersion; use sp_version::RuntimeVersion; -use testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee}; +use testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee, time::*}; use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; use xcm::latest::prelude::BodyId; use xcm_config::{ diff --git a/cumulus/polkadot-parachain/src/service.rs b/cumulus/polkadot-parachain/src/service.rs index daeeadd5ece..553975b01a8 100644 --- a/cumulus/polkadot-parachain/src/service.rs +++ b/cumulus/polkadot-parachain/src/service.rs @@ -939,8 +939,6 @@ pub async fn start_rococo_parachain_node( overseer_handle, announce_block, backend| { - let slot_duration = cumulus_client_consensus_aura::slot_duration(&*client)?; - let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( task_manager.spawn_handle(), client.clone(), @@ -971,7 +969,6 @@ pub async fn start_rococo_parachain_node( collator_key, para_id, overseer_handle, - slot_duration, relay_chain_slot_duration, proposer, collator_service, @@ -1435,8 +1432,6 @@ where overseer_handle, announce_block, backend| { - let slot_duration = cumulus_client_consensus_aura::slot_duration(&*client)?; - let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( task_manager.spawn_handle(), client.clone(), @@ -1467,7 +1462,6 @@ where collator_key, para_id, overseer_handle, - slot_duration, relay_chain_slot_duration, proposer, collator_service, @@ -1737,14 +1731,6 @@ where } // Move to Aura consensus. - let slot_duration = match cumulus_client_consensus_aura::slot_duration(&*client) { - Ok(d) => d, - Err(e) => { - log::error!("Could not get Aura slot duration: {e}"); - return - }, - }; - let proposer = Proposer::new(proposer_factory); let params = AuraParams { @@ -1761,7 +1747,6 @@ where collator_key, para_id, overseer_handle, - slot_duration, relay_chain_slot_duration, proposer, collator_service, @@ -1832,8 +1817,6 @@ where overseer_handle, announce_block, backend| { - let slot_duration = cumulus_client_consensus_aura::slot_duration(&*client)?; - let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( task_manager.spawn_handle(), client.clone(), @@ -1864,7 +1847,6 @@ where collator_key, para_id, overseer_handle, - slot_duration, relay_chain_slot_duration, proposer, collator_service, @@ -2141,8 +2123,6 @@ pub async fn start_contracts_rococo_node( overseer_handle, announce_block, backend| { - let slot_duration = cumulus_client_consensus_aura::slot_duration(&*client)?; - let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( task_manager.spawn_handle(), client.clone(), @@ -2173,7 +2153,6 @@ pub async fn start_contracts_rococo_node( collator_key, para_id, overseer_handle, - slot_duration, relay_chain_slot_duration, proposer, collator_service, -- GitLab From b79bf4fb1fec1f7a7483f9a2baa0a1e7a4fcb9c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Feb 2024 15:41:44 +0100 Subject: [PATCH 053/133] Bump once_cell from 1.18.0 to 1.19.0 (#3217) Bumps [once_cell](https://github.com/matklad/once_cell) from 1.18.0 to 1.19.0.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=once_cell&package-manager=cargo&previous-version=1.18.0&new-version=1.19.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- substrate/client/offchain/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 67eca4e3f9a..66b319b034b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8901,9 +8901,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oorandom" diff --git a/substrate/client/offchain/Cargo.toml b/substrate/client/offchain/Cargo.toml index eef385a6dd5..caa4bb03f40 100644 --- a/substrate/client/offchain/Cargo.toml +++ b/substrate/client/offchain/Cargo.toml @@ -26,7 +26,7 @@ hyper = { version = "0.14.16", features = ["http2", "stream"] } hyper-rustls = { version = "0.24.0", features = ["http2"] } libp2p = "0.51.4" num_cpus = "1.13" -once_cell = "1.8" +once_cell = "1.19" parking_lot = "0.12.1" rand = "0.8.5" threadpool = "1.7" -- GitLab From e0c902e3dfac2cb53350c0e38135a4157e99185d Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Tue, 13 Feb 2024 20:22:53 +0100 Subject: [PATCH 054/133] [xcm-builder] Replaced deprecated CurrencyAdapter with FungibleAdapter (#3287) I found out during the cleanup of this deprecation message in the `polkadot-fellows` repository that we deprecated `CurrencyAdapter` without making the recommended changes. ## TODO - [ ] fix `polkadot-fellows` bump to 1.6.0 https://github.com/polkadot-fellows/runtimes/pull/159 --------- Co-authored-by: Francisco Aguirre --- .../snowbridge/runtime/test-common/src/lib.rs | 1 + cumulus/pallets/xcmp-queue/src/mock.rs | 8 +++---- .../runtime/src/xcm_config.rs | 13 +++++------ .../emulated/common/src/macros.rs | 4 ++-- .../src/tests/reserve_transfer.rs | 18 +++++++-------- .../asset-hub-rococo/src/tests/teleport.rs | 20 ++++++++--------- .../src/tests/reserve_transfer.rs | 18 +++++++-------- .../asset-hub-westend/src/tests/teleport.rs | 20 ++++++++--------- .../src/tests/asset_transfers.rs | 4 ++-- .../bridge-hub-rococo/src/tests/mod.rs | 2 +- .../bridge-hub-rococo/src/tests/snowbridge.rs | 4 ++-- .../src/tests/asset_transfers.rs | 4 ++-- .../bridge-hub-westend/src/tests/mod.rs | 2 +- .../people-rococo/src/tests/reap_identity.rs | 4 ++-- .../people-rococo/src/tests/teleport.rs | 12 +++++----- .../people-westend/src/tests/reap_identity.rs | 4 ++-- .../people-westend/src/tests/teleport.rs | 12 +++++----- .../assets/asset-hub-rococo/src/xcm_config.rs | 22 +++++++++---------- .../asset-hub-westend/src/xcm_config.rs | 22 +++++++++---------- .../bridge-hub-rococo/src/xcm_config.rs | 17 ++++++-------- .../bridge-hub-rococo/tests/snowbridge.rs | 2 +- .../bridge-hub-westend/src/xcm_config.rs | 18 +++++++-------- .../collectives-westend/src/xcm_config.rs | 13 +++++------ .../contracts-rococo/src/xcm_config.rs | 16 ++++++-------- .../coretime-rococo/src/xcm_config.rs | 17 ++++++-------- .../coretime-westend/src/xcm_config.rs | 16 ++++++-------- .../people/people-rococo/src/xcm_config.rs | 19 +++++++--------- .../people/people-westend/src/xcm_config.rs | 19 +++++++--------- .../runtimes/testing/penpal/src/xcm_config.rs | 8 +++---- .../testing/rococo-parachain/src/lib.rs | 13 +++++------ polkadot/runtime/rococo/src/xcm_config.rs | 15 +++++-------- polkadot/runtime/westend/src/xcm_config.rs | 11 ++++------ .../src/fungible/benchmarking.rs | 12 ++++++---- .../src/fungible/mock.rs | 6 ++--- polkadot/xcm/pallet-xcm/src/mock.rs | 7 ++---- .../xcm/xcm-builder/src/fungible_adapter.rs | 2 +- polkadot/xcm/xcm-builder/tests/mock/mod.rs | 18 +++++---------- .../xcm/xcm-simulator/fuzzer/src/parachain.rs | 9 +++----- .../xcm-simulator/fuzzer/src/relay_chain.rs | 9 +++----- .../contracts/mock-network/src/parachain.rs | 9 +++----- .../contracts/mock-network/src/relay_chain.rs | 7 ++---- 41 files changed, 198 insertions(+), 259 deletions(-) diff --git a/bridges/snowbridge/runtime/test-common/src/lib.rs b/bridges/snowbridge/runtime/test-common/src/lib.rs index c9bbce98e57..18ba2aa1559 100644 --- a/bridges/snowbridge/runtime/test-common/src/lib.rs +++ b/bridges/snowbridge/runtime/test-common/src/lib.rs @@ -188,6 +188,7 @@ pub fn send_transfer_token_message_success( let channel_id: ChannelId = origin.into(); let nonce = snowbridge_pallet_outbound_queue::Nonce::::try_get(channel_id); + dbg!(&nonce); assert_ok!(nonce); assert_eq!(nonce.unwrap(), 1); diff --git a/cumulus/pallets/xcmp-queue/src/mock.rs b/cumulus/pallets/xcmp-queue/src/mock.rs index 2bf1e3c6425..08ab58ce816 100644 --- a/cumulus/pallets/xcmp-queue/src/mock.rs +++ b/cumulus/pallets/xcmp-queue/src/mock.rs @@ -30,10 +30,9 @@ use sp_runtime::{ BuildStorage, }; use xcm::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter; use xcm_builder::{ - FixedWeightBounds, FrameTransactionalProcessor, IsConcrete, NativeAsset, ParentIsPreset, + FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, IsConcrete, NativeAsset, + ParentIsPreset, }; use xcm_executor::traits::ConvertOrigin; @@ -133,8 +132,7 @@ parameter_types! { } /// Means for transacting assets on this chain. -#[allow(deprecated)] -pub type LocalAssetTransactor = CurrencyAdapter< +pub type LocalAssetTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: diff --git a/cumulus/parachain-template/runtime/src/xcm_config.rs b/cumulus/parachain-template/runtime/src/xcm_config.rs index 9dd08dc7f3e..b1230ba1e5d 100644 --- a/cumulus/parachain-template/runtime/src/xcm_config.rs +++ b/cumulus/parachain-template/runtime/src/xcm_config.rs @@ -12,15 +12,13 @@ use pallet_xcm::XcmPassthrough; use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::impls::ToAuthor; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FixedWeightBounds, - FrameTransactionalProcessor, IsConcrete, NativeAsset, ParentIsPreset, RelayChainAsNative, - SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, - UsingComponents, WithComputedOrigin, WithUniqueTopic, + FrameTransactionalProcessor, FungibleAdapter, IsConcrete, NativeAsset, ParentIsPreset, + RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, WithUniqueTopic, }; use xcm_executor::XcmExecutor; @@ -44,8 +42,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting assets on this chain. -#[allow(deprecated)] -pub type LocalAssetTransactor = CurrencyAdapter< +pub type LocalAssetTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: diff --git a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs index 01a376e4dbf..d3bb3238a3b 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs @@ -76,7 +76,7 @@ macro_rules! test_parachain_is_trusted_teleporter { $crate::macros::cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } ) => {}, RuntimeEvent::Balances( - $crate::macros::pallet_balances::Event::Withdraw { who: sender, amount } + $crate::macros::pallet_balances::Event::Burned { who: sender, amount } ) => {}, ] ); @@ -90,7 +90,7 @@ macro_rules! test_parachain_is_trusted_teleporter { $receiver_para, vec![ RuntimeEvent::Balances( - $crate::macros::pallet_balances::Event::Deposit { who: receiver, .. } + $crate::macros::pallet_balances::Event::Minted { who: receiver, .. } ) => {}, RuntimeEvent::MessageQueue( $crate::macros::pallet_message_queue::Event::Processed { success: true, .. } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs index 50f47ab970b..d2c3a323256 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs @@ -66,7 +66,7 @@ fn para_receiver_assertions(_: Test) { assert_expected_events!( PenpalA, vec![ - RuntimeEvent::Balances(pallet_balances::Event::Deposit { .. }) => {}, + RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {}, RuntimeEvent::MessageQueue( pallet_message_queue::Event::Processed { success: true, .. } ) => {}, @@ -82,7 +82,7 @@ fn para_to_system_para_sender_assertions(t: ParaToSystemParaTest) { vec![ // Amount to reserve transfer is transferred to Parachain's Sovereign account RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, @@ -101,12 +101,12 @@ fn para_to_system_para_receiver_assertions(t: ParaToSystemParaTest) { vec![ // Amount to reserve transfer is withdrawn from Parachain's Sovereign account RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == sov_penpal_on_ahr.clone().into(), amount: *amount == t.args.amount, }, - RuntimeEvent::Balances(pallet_balances::Event::Deposit { .. }) => {}, + RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {}, RuntimeEvent::MessageQueue( pallet_message_queue::Event::Processed { success: true, .. } ) => {}, @@ -143,7 +143,7 @@ fn system_para_to_para_assets_receiver_assertions(_: Test) { assert_expected_events!( PenpalA, vec![ - RuntimeEvent::Balances(pallet_balances::Event::Deposit { .. }) => {}, + RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {}, RuntimeEvent::Assets(pallet_assets::Event::Issued { .. }) => {}, RuntimeEvent::MessageQueue( pallet_message_queue::Event::Processed { success: true, .. } @@ -160,7 +160,7 @@ fn para_to_para_sender_assertions(t: ParaToParaTest) { vec![ // Amount to reserve transfer is transferred to Parachain's Sovereign account RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, @@ -184,14 +184,14 @@ fn para_to_para_relay_hop_assertions(t: ParaToParaTest) { vec![ // Withdrawn from sender parachain SA RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == sov_penpal_a_on_rococo, amount: *amount == t.args.amount, }, // Deposited to receiver parachain SA RuntimeEvent::Balances( - pallet_balances::Event::Deposit { who, .. } + pallet_balances::Event::Minted { who, .. } ) => { who: *who == sov_penpal_b_on_rococo, }, @@ -207,7 +207,7 @@ fn para_to_para_receiver_assertions(_: ParaToParaTest) { assert_expected_events!( PenpalB, vec![ - RuntimeEvent::Balances(pallet_balances::Event::Deposit { .. }) => {}, + RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {}, RuntimeEvent::MessageQueue( pallet_message_queue::Event::Processed { success: true, .. } ) => {}, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs index 218234cc78e..dfb5061b55f 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs @@ -28,12 +28,12 @@ fn relay_origin_assertions(t: RelayToSystemParaTest) { Rococo, vec![ // Amount to teleport is withdrawn from Sender - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, }, // Amount to teleport is deposited in Relay's `CheckAccount` - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) => { who: *who == ::XcmPallet::check_account(), amount: *amount == t.args.amount, }, @@ -54,12 +54,12 @@ fn relay_dest_assertions(t: SystemParaToRelayTest) { Rococo, vec![ // Amount is withdrawn from Relay Chain's `CheckAccount` - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { who: *who == ::XcmPallet::check_account(), amount: *amount == t.args.amount, }, // Amount minus fees are deposited in Receiver's account - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == t.receiver.account_id, }, ] @@ -88,7 +88,7 @@ fn para_origin_assertions(t: SystemParaToRelayTest) { AssetHubRococo, vec![ // Amount is withdrawn from Sender's account - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, }, @@ -105,7 +105,7 @@ fn para_dest_assertions(t: RelayToSystemParaTest) { AssetHubRococo, vec![ // Amount minus fees are deposited in Receiver's account - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == t.receiver.account_id, }, ] @@ -122,7 +122,7 @@ fn penpal_to_ah_foreign_assets_sender_assertions(t: ParaToSystemParaTest) { PenpalA, vec![ RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, @@ -149,12 +149,12 @@ fn penpal_to_ah_foreign_assets_receiver_assertions(t: ParaToSystemParaTest) { vec![ // native asset reserve transfer for paying fees, withdrawn from Penpal's sov account RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == sov_penpal_on_ahr.clone().into(), amount: *amount == t.args.amount, }, - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == t.receiver.account_id, }, RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, amount }) => { @@ -221,7 +221,7 @@ fn ah_to_penpal_foreign_assets_receiver_assertions(t: SystemParaToParaTest) { amount: *amount == expected_asset_amount, }, // native asset for fee is deposited to receiver - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == t.receiver.account_id, }, RuntimeEvent::MessageQueue( diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs index 851d7be6ec9..a29cd10ba83 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs @@ -70,7 +70,7 @@ fn para_receiver_assertions(_: Test) { assert_expected_events!( PenpalB, vec![ - RuntimeEvent::Balances(pallet_balances::Event::Deposit { .. }) => {}, + RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {}, RuntimeEvent::MessageQueue( pallet_message_queue::Event::Processed { success: true, .. } ) => {}, @@ -88,7 +88,7 @@ fn para_to_system_para_sender_assertions(t: ParaToSystemParaTest) { vec![ // Amount to reserve transfer is transferred to Parachain's Sovereign account RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, @@ -109,12 +109,12 @@ fn para_to_system_para_receiver_assertions(t: ParaToSystemParaTest) { vec![ // Amount to reserve transfer is transferred to Parachain's Sovereign account RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == sov_penpal_on_ahw.clone().into(), amount: *amount == t.args.amount, }, - RuntimeEvent::Balances(pallet_balances::Event::Deposit { .. }) => {}, + RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {}, RuntimeEvent::MessageQueue( pallet_message_queue::Event::Processed { success: true, .. } ) => {}, @@ -153,7 +153,7 @@ fn system_para_to_para_assets_receiver_assertions(_: Test) { assert_expected_events!( PenpalB, vec![ - RuntimeEvent::Balances(pallet_balances::Event::Deposit { .. }) => {}, + RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {}, RuntimeEvent::Assets(pallet_assets::Event::Issued { .. }) => {}, RuntimeEvent::MessageQueue( pallet_message_queue::Event::Processed { success: true, .. } @@ -170,7 +170,7 @@ fn para_to_para_sender_assertions(t: ParaToParaTest) { vec![ // Amount to reserve transfer is transferred to Parachain's Sovereign account RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, @@ -194,14 +194,14 @@ fn para_to_para_relay_hop_assertions(t: ParaToParaTest) { vec![ // Withdrawn from sender parachain SA RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == sov_penpal_b_on_westend, amount: *amount == t.args.amount, }, // Deposited to receiver parachain SA RuntimeEvent::Balances( - pallet_balances::Event::Deposit { who, .. } + pallet_balances::Event::Minted { who, .. } ) => { who: *who == sov_penpal_a_on_westend, }, @@ -217,7 +217,7 @@ fn para_to_para_receiver_assertions(_: ParaToParaTest) { assert_expected_events!( PenpalA, vec![ - RuntimeEvent::Balances(pallet_balances::Event::Deposit { .. }) => {}, + RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {}, RuntimeEvent::MessageQueue( pallet_message_queue::Event::Processed { success: true, .. } ) => {}, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs index 01498f7bb4e..0dd1a1533b5 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs @@ -28,12 +28,12 @@ fn relay_origin_assertions(t: RelayToSystemParaTest) { Westend, vec![ // Amount to teleport is withdrawn from Sender - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, }, // Amount to teleport is deposited in Relay's `CheckAccount` - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) => { who: *who == ::XcmPallet::check_account(), amount: *amount == t.args.amount, }, @@ -54,12 +54,12 @@ fn relay_dest_assertions(t: SystemParaToRelayTest) { Westend, vec![ // Amount is withdrawn from Relay Chain's `CheckAccount` - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { who: *who == ::XcmPallet::check_account(), amount: *amount == t.args.amount, }, // Amount minus fees are deposited in Receiver's account - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == t.receiver.account_id, }, ] @@ -88,7 +88,7 @@ fn para_origin_assertions(t: SystemParaToRelayTest) { AssetHubWestend, vec![ // Amount is withdrawn from Sender's account - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, }, @@ -105,7 +105,7 @@ fn para_dest_assertions(t: RelayToSystemParaTest) { AssetHubWestend, vec![ // Amount minus fees are deposited in Receiver's account - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == t.receiver.account_id, }, ] @@ -122,7 +122,7 @@ fn penpal_to_ah_foreign_assets_sender_assertions(t: ParaToSystemParaTest) { PenpalB, vec![ RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, @@ -149,12 +149,12 @@ fn penpal_to_ah_foreign_assets_receiver_assertions(t: ParaToSystemParaTest) { vec![ // native asset reserve transfer for paying fees, withdrawn from Penpal's sov account RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == sov_penpal_on_ahr.clone().into(), amount: *amount == t.args.amount, }, - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == t.receiver.account_id, }, RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, amount }) => { @@ -221,7 +221,7 @@ fn ah_to_penpal_foreign_assets_receiver_assertions(t: SystemParaToParaTest) { amount: *amount == expected_asset_amount, }, // native asset for fee is deposited to receiver - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == t.receiver.account_id, }, RuntimeEvent::MessageQueue( diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs index a203de0f8c9..787a82ed32f 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs @@ -184,13 +184,13 @@ fn send_wnds_from_asset_hub_rococo_to_asset_hub_westend() { vec![ // WND is withdrawn from AHR's SA on AHW RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == sov_ahr_on_ahw, amount: *amount == amount_to_send, }, // WNDs deposited to beneficiary - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == AssetHubWestendReceiver::get(), }, // message processed successfully diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs index a33d2fab753..6d7b53c8fdf 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs @@ -68,7 +68,7 @@ pub(crate) fn assert_bridge_hub_rococo_message_accepted(expected_processed: bool BridgeHubRococo, vec![ // pay for bridge fees - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { .. }) => {}, + RuntimeEvent::Balances(pallet_balances::Event::Burned { .. }) => {}, // message exported RuntimeEvent::BridgeWestendMessages( pallet_bridge_messages::Event::MessageAccepted { .. } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs index 5e1a2af660b..a5957ee3393 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs @@ -478,7 +478,7 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { assert!( events.iter().any(|event| matches!( event, - RuntimeEvent::Balances(pallet_balances::Event::Deposit{ who, amount }) + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) if *who == TREASURY_ACCOUNT.into() && *amount == 16903333 )), "Snowbridge sovereign takes local fee." @@ -487,7 +487,7 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { assert!( events.iter().any(|event| matches!( event, - RuntimeEvent::Balances(pallet_balances::Event::Deposit{ who, amount }) + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) if *who == assethub_sovereign && *amount == 2680000000000, )), "AssetHub sovereign takes remote fee." diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs index c2a9c008902..5b0990973d2 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs @@ -182,13 +182,13 @@ fn send_rocs_from_asset_hub_westend_to_asset_hub_rococo() { vec![ // ROC is withdrawn from AHW's SA on AHR RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == sov_ahw_on_ahr, amount: *amount == amount_to_send, }, // ROCs deposited to beneficiary - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == AssetHubRococoReceiver::get(), }, // message processed successfully diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs index 186b96b3976..3074435e8e4 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs @@ -67,7 +67,7 @@ pub(crate) fn assert_bridge_hub_westend_message_accepted(expected_processed: boo BridgeHubWestend, vec![ // pay for bridge fees - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { .. }) => {}, + RuntimeEvent::Balances(pallet_balances::Event::Burned { .. }) => {}, // message exported RuntimeEvent::BridgeRococoMessages( pallet_bridge_messages::Event::MessageAccepted { .. } diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/reap_identity.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/reap_identity.rs index 58bb9504dbd..87adb363e02 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/reap_identity.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/reap_identity.rs @@ -366,7 +366,7 @@ fn assert_reap_events(id_deposit: Balance, id: &Identity) { PeopleRococo, vec![ // Deposit and Endowed from teleport - RuntimeEvent::Balances(BalancesEvent::Deposit { .. }) => {}, + RuntimeEvent::Balances(BalancesEvent::Minted { .. }) => {}, RuntimeEvent::Balances(BalancesEvent::Endowed { .. }) => {}, // Amount reserved for identity info RuntimeEvent::Balances(BalancesEvent::Reserved { who, amount }) => { @@ -392,7 +392,7 @@ fn assert_reap_events(id_deposit: Balance, id: &Identity) { PeopleRococo, vec![ // Deposit and Endowed from teleport - RuntimeEvent::Balances(BalancesEvent::Deposit { .. }) => {}, + RuntimeEvent::Balances(BalancesEvent::Minted { .. }) => {}, RuntimeEvent::Balances(BalancesEvent::Endowed { .. }) => {}, // Amount reserved for identity info RuntimeEvent::Balances(BalancesEvent::Reserved { who, amount }) => { diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/teleport.rs index 42c7e310b26..0a12277395d 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/teleport.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/teleport.rs @@ -25,12 +25,12 @@ fn relay_origin_assertions(t: RelayToSystemParaTest) { Rococo, vec![ // Amount to teleport is withdrawn from Sender - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, }, // Amount to teleport is deposited in Relay's `CheckAccount` - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) => { who: *who == ::XcmPallet::check_account(), amount: *amount == t.args.amount, }, @@ -51,12 +51,12 @@ fn relay_dest_assertions(t: SystemParaToRelayTest) { Rococo, vec![ // Amount is withdrawn from Relay Chain's `CheckAccount` - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { who: *who == ::XcmPallet::check_account(), amount: *amount == t.args.amount, }, // Amount minus fees are deposited in Receiver's account - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == t.receiver.account_id, }, ] @@ -85,7 +85,7 @@ fn para_origin_assertions(t: SystemParaToRelayTest) { PeopleRococo, vec![ // Amount is withdrawn from Sender's account - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, }, @@ -102,7 +102,7 @@ fn para_dest_assertions(t: RelayToSystemParaTest) { PeopleRococo, vec![ // Amount minus fees are deposited in Receiver's account - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == t.receiver.account_id, }, ] diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/reap_identity.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/reap_identity.rs index 246c612a681..8d63c8ceff6 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/reap_identity.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/reap_identity.rs @@ -368,7 +368,7 @@ fn assert_reap_events(id_deposit: Balance, id: &Identity) { PeopleWestend, vec![ // Deposit and Endowed from teleport - RuntimeEvent::Balances(BalancesEvent::Deposit { .. }) => {}, + RuntimeEvent::Balances(BalancesEvent::Minted { .. }) => {}, RuntimeEvent::Balances(BalancesEvent::Endowed { .. }) => {}, // Amount reserved for identity info RuntimeEvent::Balances(BalancesEvent::Reserved { who, amount }) => { @@ -394,7 +394,7 @@ fn assert_reap_events(id_deposit: Balance, id: &Identity) { PeopleWestend, vec![ // Deposit and Endowed from teleport - RuntimeEvent::Balances(BalancesEvent::Deposit { .. }) => {}, + RuntimeEvent::Balances(BalancesEvent::Minted { .. }) => {}, RuntimeEvent::Balances(BalancesEvent::Endowed { .. }) => {}, // Amount reserved for identity info RuntimeEvent::Balances(BalancesEvent::Reserved { who, amount }) => { diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/teleport.rs index e9f0158efbf..345663be99b 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/teleport.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/teleport.rs @@ -25,12 +25,12 @@ fn relay_origin_assertions(t: RelayToSystemParaTest) { Westend, vec![ // Amount to teleport is withdrawn from Sender - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, }, // Amount to teleport is deposited in Relay's `CheckAccount` - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) => { who: *who == ::XcmPallet::check_account(), amount: *amount == t.args.amount, }, @@ -51,12 +51,12 @@ fn relay_dest_assertions(t: SystemParaToRelayTest) { Westend, vec![ // Amount is withdrawn from Relay Chain's `CheckAccount` - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { who: *who == ::XcmPallet::check_account(), amount: *amount == t.args.amount, }, // Amount minus fees are deposited in Receiver's account - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == t.receiver.account_id, }, ] @@ -85,7 +85,7 @@ fn para_origin_assertions(t: SystemParaToRelayTest) { PeopleWestend, vec![ // Amount is withdrawn from Sender's account - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, }, @@ -102,7 +102,7 @@ fn para_dest_assertions(t: RelayToSystemParaTest) { PeopleWestend, vec![ // Amount minus fees are deposited in Receiver's account - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == t.receiver.account_id, }, ] diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs index 693fd7c3fc7..2584dbdf310 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs @@ -49,19 +49,18 @@ use testnet_parachains_constants::rococo::snowbridge::{ EthereumNetwork, INBOUND_QUEUE_PALLET_INDEX, }; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, EnsureXcmOrigin, FrameTransactionalProcessor, - FungiblesAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, - LocalMint, NetworkExportTableItem, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, - ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignPaidRemoteExporter, - SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, - XcmFeeManagerFromComponents, XcmFeeToAccount, + FungibleAdapter, FungiblesAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, + IsConcrete, LocalMint, NetworkExportTableItem, NoChecking, NonFungiblesAdapter, + ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignPaidRemoteExporter, SovereignSignedViaLocation, StartsWith, + StartsWithExplicitGlobalConsensus, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, + WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, + XcmFeeToAccount, }; use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; @@ -114,8 +113,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. -#[allow(deprecated)] -pub type CurrencyTransactor = CurrencyAdapter< +pub type FungibleTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: @@ -223,7 +221,7 @@ pub type PoolFungiblesTransactor = FungiblesAdapter< /// Means for transacting assets on this chain. pub type AssetTransactors = ( - CurrencyTransactor, + FungibleTransactor, FungiblesTransactor, ForeignFungiblesTransactor, PoolFungiblesTransactor, diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs index 01437534c1f..50865c00061 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs @@ -45,19 +45,18 @@ use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::xcm_sender::ExponentialPrice; use sp_runtime::traits::{AccountIdConversion, ConvertInto}; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, DenyThenTry, DescribeFamily, DescribePalletTerminal, EnsureXcmOrigin, - FrameTransactionalProcessor, FungiblesAdapter, GlobalConsensusParachainConvertsFor, - HashedDescription, IsConcrete, LocalMint, NetworkExportTableItem, NoChecking, - NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, - SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, - TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, - WithUniqueTopic, XcmFeeManagerFromComponents, XcmFeeToAccount, + FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, + GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, LocalMint, + NetworkExportTableItem, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, + RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, StartsWith, + StartsWithExplicitGlobalConsensus, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, + WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, + XcmFeeToAccount, }; use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; @@ -107,8 +106,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. -#[allow(deprecated)] -pub type CurrencyTransactor = CurrencyAdapter< +pub type FungibleTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: @@ -216,7 +214,7 @@ pub type PoolFungiblesTransactor = FungiblesAdapter< /// Means for transacting assets on this chain. pub type AssetTransactors = ( - CurrencyTransactor, + FungibleTransactor, FungiblesTransactor, ForeignFungiblesTransactor, PoolFungiblesTransactor, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs index b7d170707cf..55c78477b56 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs @@ -54,16 +54,14 @@ use sp_runtime::traits::AccountIdConversion; use sp_std::marker::PhantomData; use testnet_parachains_constants::rococo::snowbridge::EthereumNetwork; use xcm::latest::prelude::*; -#[allow(deprecated)] use xcm_builder::{ deposit_or_burn_fee, AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, - CurrencyAdapter, DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, - FrameTransactionalProcessor, HandleFee, IsConcrete, ParentAsSuperuser, ParentIsPreset, - RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, - XcmFeeToAccount, + DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, + FungibleAdapter, HandleFee, IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeToAccount, }; use xcm_executor::{ traits::{FeeManager, FeeReason, FeeReason::Export, TransactAsset, WithOriginFilter}, @@ -96,8 +94,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. -#[allow(deprecated)] -pub type CurrencyTransactor = CurrencyAdapter< +pub type FungibleTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: @@ -285,7 +282,7 @@ pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; type XcmSender = XcmRouter; - type AssetTransactor = CurrencyTransactor; + type AssetTransactor = FungibleTransactor; type OriginConverter = XcmOriginToTransactDispatchOrigin; // BridgeHub does not recognize a reserve location for any asset. Users must teleport Native // token where allowed (e.g. with the Relay Chain). diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs index e14755c89d4..b2e5cc9ef06 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs @@ -102,7 +102,7 @@ pub fn transfer_token_to_ethereum_insufficient_fund() { H160::random(), H160::random(), DefaultBridgeHubEthereumBaseFee::get(), - FailedToTransactAsset("InsufficientBalance"), + FailedToTransactAsset("Funds are unavailable"), ) } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs index d2f0f187a20..e18df6feda8 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs @@ -38,16 +38,15 @@ use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::xcm_sender::ExponentialPrice; use sp_runtime::traits::AccountIdConversion; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, - DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, IsConcrete, ParentAsSuperuser, - ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, - XcmFeeManagerFromComponents, XcmFeeToAccount, + DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, IsConcrete, + ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, + WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, + XcmFeeToAccount, }; use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; @@ -76,8 +75,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. -#[allow(deprecated)] -pub type CurrencyTransactor = CurrencyAdapter< +pub type FungibleTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: @@ -233,7 +231,7 @@ pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; type XcmSender = XcmRouter; - type AssetTransactor = CurrencyTransactor; + type AssetTransactor = FungibleTransactor; type OriginConverter = XcmOriginToTransactDispatchOrigin; // BridgeHub does not recognize a reserve location for any asset. Users must teleport Native // token where allowed (e.g. with the Relay Chain). diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs index ad19521898d..cc25cbda0a4 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs @@ -36,13 +36,11 @@ use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::xcm_sender::ExponentialPrice; use westend_runtime_constants::xcm as xcm_constants; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, - DenyThenTry, EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, IsConcrete, - LocatableAssetId, OriginToPluralityVoice, ParentAsSuperuser, ParentIsPreset, + DenyThenTry, EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, + IsConcrete, LocatableAssetId, OriginToPluralityVoice, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, WithUniqueTopic, @@ -85,9 +83,8 @@ pub type LocationToAccountId = ( AccountId32Aliases, ); -/// Means for transacting the native currency on this chain. -#[allow(deprecated)] -pub type CurrencyTransactor = CurrencyAdapter< +/// Means for transacting the native currency on this chain.#[allow(deprecated)] +pub type FungibleTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: @@ -262,7 +259,7 @@ pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; type XcmSender = XcmRouter; - type AssetTransactor = CurrencyTransactor; + type AssetTransactor = FungibleTransactor; type OriginConverter = XcmOriginToTransactDispatchOrigin; // Collectives does not recognize a reserve location for any asset. Users must teleport WND // where allowed (e.g. with the Relay Chain). diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs index 7919faf400f..e8f3209eb67 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs @@ -37,16 +37,15 @@ use polkadot_runtime_common::xcm_sender::ExponentialPrice; use sp_runtime::traits::AccountIdConversion; use testnet_parachains_constants::rococo::currency::CENTS; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, - DenyThenTry, EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, IsConcrete, - NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, XcmFeeToAccount, + DenyThenTry, EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, + IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, + XcmFeeToAccount, }; use xcm_executor::XcmExecutor; @@ -79,8 +78,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. -#[allow(deprecated)] -pub type CurrencyTransactor = CurrencyAdapter< +pub type CurrencyTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/xcm_config.rs index 13816f8ce71..37bb8809dab 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/xcm_config.rs @@ -38,17 +38,15 @@ use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::xcm_sender::ExponentialPrice; use sp_runtime::traits::AccountIdConversion; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, - DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, IsConcrete, NonFungibleAdapter, - ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, - XcmFeeToAccount, + DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, IsConcrete, + NonFungibleAdapter, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; @@ -79,8 +77,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. -#[allow(deprecated)] -pub type CurrencyTransactor = CurrencyAdapter< +pub type CurrencyTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs index e57a4687776..3adef5fb7cf 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs @@ -37,16 +37,15 @@ use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::xcm_sender::ExponentialPrice; use sp_runtime::traits::AccountIdConversion; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, - DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, IsConcrete, ParentAsSuperuser, - ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, - XcmFeeManagerFromComponents, XcmFeeToAccount, + DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, IsConcrete, + ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, + WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, + XcmFeeToAccount, }; use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; @@ -75,8 +74,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. -#[allow(deprecated)] -pub type CurrencyTransactor = CurrencyAdapter< +pub type CurrencyTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs index c7408b3fa84..e75b048b3b3 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs @@ -34,17 +34,15 @@ use parachains_common::{ use polkadot_parachain_primitives::primitives::Sibling; use sp_runtime::traits::AccountIdConversion; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, - DenyThenTry, DescribeTerminus, EnsureXcmOrigin, FrameTransactionalProcessor, HashedDescription, - IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, - XcmFeeToAccount, + DenyThenTry, DescribeTerminus, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, + HashedDescription, IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; @@ -97,8 +95,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. -#[allow(deprecated)] -pub type CurrencyTransactor = CurrencyAdapter< +pub type FungibleTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: @@ -244,7 +241,7 @@ pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; type XcmSender = XcmRouter; - type AssetTransactor = CurrencyTransactor; + type AssetTransactor = FungibleTransactor; type OriginConverter = XcmOriginToTransactDispatchOrigin; // People chain does not recognize a reserve location for any asset. Users must teleport ROC // where allowed (e.g. with the Relay Chain). diff --git a/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs index baead523419..ee0691fd996 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs @@ -34,17 +34,15 @@ use parachains_common::{ use polkadot_parachain_primitives::primitives::Sibling; use sp_runtime::traits::AccountIdConversion; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, - DenyThenTry, DescribeTerminus, EnsureXcmOrigin, FrameTransactionalProcessor, HashedDescription, - IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, - XcmFeeToAccount, + DenyThenTry, DescribeTerminus, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, + HashedDescription, IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; @@ -97,8 +95,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. -#[allow(deprecated)] -pub type CurrencyTransactor = CurrencyAdapter< +pub type FungibleTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: @@ -252,7 +249,7 @@ pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; type XcmSender = XcmRouter; - type AssetTransactor = CurrencyTransactor; + type AssetTransactor = FungibleTransactor; type OriginConverter = XcmOriginToTransactDispatchOrigin; // People does not recognize a reserve location for any asset. Users must teleport WND // where allowed (e.g. with the Relay Chain). diff --git a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs index 3fad47576fd..c0bcd787962 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -45,12 +45,11 @@ use polkadot_runtime_common::impls::ToAuthor; use sp_runtime::traits::Zero; use testnet_parachains_constants::rococo::snowbridge::EthereumNetwork; use xcm::latest::prelude::*; -#[allow(deprecated)] use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AsPrefixedGeneralIndex, - ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, - FrameTransactionalProcessor, FungiblesAdapter, IsConcrete, LocalMint, NativeAsset, NoChecking, + ConvertedConcreteId, EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, + FungibleAdapter, FungiblesAdapter, IsConcrete, LocalMint, NativeAsset, NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, StartsWith, TakeWeightCredit, TrailingSetTopicAsId, @@ -78,8 +77,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting assets on this chain. -#[allow(deprecated)] -pub type CurrencyTransactor = CurrencyAdapter< +pub type CurrencyTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs index 11a0f8b9570..57969d9a4f1 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs @@ -84,14 +84,12 @@ use xcm_executor::traits::JustTry; use pallet_xcm::{EnsureXcm, IsMajorityOfBody, XcmPassthrough}; use polkadot_parachain_primitives::primitives::Sibling; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom, - EnsureXcmOrigin, FixedWeightBounds, IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, - RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - UsingComponents, + EnsureXcmOrigin, FixedWeightBounds, FungibleAdapter, IsConcrete, NativeAsset, + ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, }; use xcm_executor::XcmExecutor; @@ -350,8 +348,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting assets on this chain. -#[allow(deprecated)] -pub type CurrencyTransactor = CurrencyAdapter< +pub type CurrencyTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: diff --git a/polkadot/runtime/rococo/src/xcm_config.rs b/polkadot/runtime/rococo/src/xcm_config.rs index 5fddd749dad..af8981ddcc1 100644 --- a/polkadot/runtime/rococo/src/xcm_config.rs +++ b/polkadot/runtime/rococo/src/xcm_config.rs @@ -36,17 +36,15 @@ use runtime_common::{ }; use sp_core::ConstU32; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter as XcmCurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, ChildParachainAsNative, ChildParachainConvertsVia, DescribeBodyTerminal, DescribeFamily, FixedWeightBounds, - FrameTransactionalProcessor, HashedDescription, IsChildSystemParachain, IsConcrete, - MintLocation, OriginToPluralityVoice, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, - XcmFeeToAccount, + FrameTransactionalProcessor, FungibleAdapter, HashedDescription, IsChildSystemParachain, + IsConcrete, MintLocation, OriginToPluralityVoice, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::XcmExecutor; @@ -73,8 +71,7 @@ pub type LocationConverter = ( /// point of view of XCM-only concepts like `Location` and `Asset`. /// /// Ours is only aware of the Balances pallet, which is mapped to `RocLocation`. -#[allow(deprecated)] -pub type LocalAssetTransactor = XcmCurrencyAdapter< +pub type LocalAssetTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: diff --git a/polkadot/runtime/westend/src/xcm_config.rs b/polkadot/runtime/westend/src/xcm_config.rs index 8c2fea69006..a11c52156c8 100644 --- a/polkadot/runtime/westend/src/xcm_config.rs +++ b/polkadot/runtime/westend/src/xcm_config.rs @@ -39,15 +39,13 @@ use westend_runtime_constants::{ xcm::body::{FELLOWSHIP_ADMIN_INDEX, TREASURER_INDEX}, }; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter as XcmCurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, ChildParachainAsNative, ChildParachainConvertsVia, DescribeBodyTerminal, DescribeFamily, FrameTransactionalProcessor, - HashedDescription, IsConcrete, MintLocation, OriginToPluralityVoice, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, - UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + FungibleAdapter, HashedDescription, IsConcrete, MintLocation, OriginToPluralityVoice, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::XcmExecutor; @@ -75,8 +73,7 @@ pub type LocationConverter = ( HashedDescription>, ); -#[allow(deprecated)] -pub type LocalAssetTransactor = XcmCurrencyAdapter< +pub type LocalAssetTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs index e96ec48fcba..4b77199069d 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs @@ -64,13 +64,16 @@ benchmarks_instance_pallet! { transfer_asset { let (sender_account, sender_location) = account_and_location::(1); let asset = T::get_asset(); - let assets: Assets = vec![ asset.clone() ].into(); + let assets: Assets = vec![asset.clone()].into(); // this xcm doesn't use holding let dest_location = T::valid_destination()?; let dest_account = T::AccountIdConverter::convert_location(&dest_location).unwrap(); >::deposit_asset(&asset, &sender_location, None).unwrap(); + // We deposit the asset twice so we have enough for ED after transferring + >::deposit_asset(&asset, &sender_location, None).unwrap(); + let sender_account_balance_before = T::TransactAsset::balance(&sender_account); assert!(T::TransactAsset::balance(&dest_account).is_zero()); let mut executor = new_executor::(sender_location); @@ -79,7 +82,7 @@ benchmarks_instance_pallet! { }: { executor.bench_process(xcm)?; } verify { - assert!(T::TransactAsset::balance(&sender_account).is_zero()); + assert!(T::TransactAsset::balance(&sender_account) < sender_account_balance_before); assert!(!T::TransactAsset::balance(&dest_account).is_zero()); } @@ -93,11 +96,12 @@ benchmarks_instance_pallet! { &dest_location, FeeReason::TransferReserveAsset ); - let sender_account_balance_before = T::TransactAsset::balance(&sender_account); let asset = T::get_asset(); >::deposit_asset(&asset, &sender_location, None).unwrap(); - assert!(T::TransactAsset::balance(&sender_account) > sender_account_balance_before); + // We deposit the asset twice so we have enough for ED after transferring + >::deposit_asset(&asset, &sender_location, None).unwrap(); + let sender_account_balance_before = T::TransactAsset::balance(&sender_account); let assets: Assets = vec![asset].into(); assert!(T::TransactAsset::balance(&dest_account).is_zero()); diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs index fe3ee81f9d4..637446832fd 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs @@ -103,8 +103,7 @@ impl xcm_executor::traits::MatchesFungible for MatchAnyFungible { } // Use balances as the asset transactor. -#[allow(deprecated)] -pub type AssetTransactor = xcm_builder::CurrencyAdapter< +pub type AssetTransactor = xcm_builder::FungibleAdapter< Balances, MatchAnyFungible, AccountIdConverter, @@ -192,8 +191,7 @@ impl xcm_balances_benchmark::Config for Test { type TrustedReserve = TrustedReserve; fn get_asset() -> Asset { - let amount = - >::minimum_balance() as u128; + let amount = 1_000_000_000_000; Asset { id: AssetId(Here.into()), fun: Fungible(amount) } } } diff --git a/polkadot/xcm/pallet-xcm/src/mock.rs b/polkadot/xcm/pallet-xcm/src/mock.rs index 434dac1659b..004a73ca6ab 100644 --- a/polkadot/xcm/pallet-xcm/src/mock.rs +++ b/polkadot/xcm/pallet-xcm/src/mock.rs @@ -30,13 +30,11 @@ use sp_core::H256; use sp_runtime::{traits::IdentityLookup, AccountId32, BuildStorage}; pub use sp_std::cell::RefCell; use xcm::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter as XcmCurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, Case, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, DescribeAllTerminal, FixedRateOfFungible, FixedWeightBounds, - FrameTransactionalProcessor, FungiblesAdapter, HashedDescription, IsConcrete, + FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, HashedDescription, IsConcrete, MatchedConvertedConcreteId, NoChecking, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, XcmFeeManagerFromComponents, XcmFeeToAccount, }; @@ -424,9 +422,8 @@ pub type ForeignAssetsConvertedConcreteId = MatchedConvertedConcreteId< JustTry, >; -#[allow(deprecated)] pub type AssetTransactors = ( - XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>, + FungibleAdapter, SovereignAccountOf, AccountId, ()>, FungiblesAdapter< AssetsPallet, ForeignAssetsConvertedConcreteId, diff --git a/polkadot/xcm/xcm-builder/src/fungible_adapter.rs b/polkadot/xcm/xcm-builder/src/fungible_adapter.rs index 7bea8cdf957..33b766c8560 100644 --- a/polkadot/xcm/xcm-builder/src/fungible_adapter.rs +++ b/polkadot/xcm/xcm-builder/src/fungible_adapter.rs @@ -204,7 +204,7 @@ impl< ) -> result::Result { log::trace!( target: "xcm::fungible_adapter", - "deposit_asset what: {:?}, who: {:?}", + "withdraw_asset what: {:?}, who: {:?}", what, who, ); let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?; diff --git a/polkadot/xcm/xcm-builder/tests/mock/mod.rs b/polkadot/xcm/xcm-builder/tests/mock/mod.rs index 34508d7622c..06cedb9c357 100644 --- a/polkadot/xcm/xcm-builder/tests/mock/mod.rs +++ b/polkadot/xcm/xcm-builder/tests/mock/mod.rs @@ -32,14 +32,12 @@ use xcm_executor::XcmExecutor; use staging_xcm_builder as xcm_builder; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter as XcmCurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, - FixedRateOfFungible, FixedWeightBounds, IsChildSystemParachain, IsConcrete, MintLocation, - RespectSuspension, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, - TakeWeightCredit, + FixedRateOfFungible, FixedWeightBounds, FungibleAdapter, IsChildSystemParachain, IsConcrete, + MintLocation, RespectSuspension, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, TakeWeightCredit, }; pub type AccountId = AccountId32; @@ -146,14 +144,8 @@ parameter_types! { pub type SovereignAccountOf = (ChildParachainConvertsVia, AccountId32Aliases); -#[allow(deprecated)] -pub type LocalCurrencyAdapter = XcmCurrencyAdapter< - Balances, - IsConcrete, - SovereignAccountOf, - AccountId, - CheckAccount, ->; +pub type LocalCurrencyAdapter = + FungibleAdapter, SovereignAccountOf, AccountId, CheckAccount>; pub type LocalAssetTransactor = (LocalCurrencyAdapter,); diff --git a/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs b/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs index fbb60a25f44..a20390b64f9 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs +++ b/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs @@ -38,12 +38,10 @@ use polkadot_parachain_primitives::primitives::{ DmpMessageHandler, Id as ParaId, Sibling, XcmpMessageFormat, XcmpMessageHandler, }; use xcm::{latest::prelude::*, VersionedXcm}; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter as XcmCurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowUnpaidExecutionFrom, EnsureXcmOrigin, FixedRateOfFungible, - FixedWeightBounds, FrameTransactionalProcessor, IsConcrete, NativeAsset, ParentIsPreset, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, IsConcrete, NativeAsset, + ParentIsPreset, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, }; use xcm_executor::{Config, XcmExecutor}; @@ -125,9 +123,8 @@ parameter_types! { pub const MaxAssetsIntoHolding: u32 = 64; } -#[allow(deprecated)] pub type LocalAssetTransactor = - XcmCurrencyAdapter, LocationToAccountId, AccountId, ()>; + FungibleAdapter, LocationToAccountId, AccountId, ()>; pub type XcmRouter = super::ParachainXcmRouter; pub type Barrier = AllowUnpaidExecutionFrom; diff --git a/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs b/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs index e8294560dfc..5bf65fa9f9a 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs +++ b/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs @@ -37,13 +37,11 @@ use polkadot_runtime_parachains::{ origin, shared, }; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter as XcmCurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowUnpaidExecutionFrom, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, FixedRateOfFungible, - FixedWeightBounds, FrameTransactionalProcessor, IsConcrete, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, + FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, IsConcrete, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, }; use xcm_executor::{Config, XcmExecutor}; @@ -112,9 +110,8 @@ parameter_types! { pub type SovereignAccountOf = (ChildParachainConvertsVia, AccountId32Aliases); -#[allow(deprecated)] pub type LocalAssetTransactor = - XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>; + FungibleAdapter, SovereignAccountOf, AccountId, ()>; type LocalOriginConverter = ( SovereignSignedViaLocation, diff --git a/substrate/frame/contracts/mock-network/src/parachain.rs b/substrate/frame/contracts/mock-network/src/parachain.rs index 53839f1fca0..7a60a66b314 100644 --- a/substrate/frame/contracts/mock-network/src/parachain.rs +++ b/substrate/frame/contracts/mock-network/src/parachain.rs @@ -37,13 +37,11 @@ use sp_runtime::traits::{Get, IdentityLookup, MaybeEquivalence}; use sp_std::prelude::*; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter as XcmCurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom, ConvertedConcreteId, EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, - FrameTransactionalProcessor, FungiblesAdapter, IsConcrete, NativeAsset, NoChecking, - ParentAsSuperuser, ParentIsPreset, SignedAccountId32AsNative, SignedToAccountId32, + FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, IsConcrete, NativeAsset, + NoChecking, ParentAsSuperuser, ParentIsPreset, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, WithComputedOrigin, }; use xcm_executor::{traits::JustTry, Config, XcmExecutor}; @@ -184,9 +182,8 @@ pub fn estimate_fee_for_weight(weight: Weight) -> u128 { units_per_mb * (weight.proof_size() as u128) / (WEIGHT_PROOF_SIZE_PER_MB as u128) } -#[allow(deprecated)] pub type LocalBalancesTransactor = - XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>; + FungibleAdapter, SovereignAccountOf, AccountId, ()>; pub struct FromLocationToAsset(PhantomData<(Location, AssetId)>); impl MaybeEquivalence diff --git a/substrate/frame/contracts/mock-network/src/relay_chain.rs b/substrate/frame/contracts/mock-network/src/relay_chain.rs index ce7e22dce2f..6eb9b4e5385 100644 --- a/substrate/frame/contracts/mock-network/src/relay_chain.rs +++ b/substrate/frame/contracts/mock-network/src/relay_chain.rs @@ -29,13 +29,11 @@ use sp_runtime::traits::IdentityLookup; use polkadot_parachain_primitives::primitives::Id as ParaId; use polkadot_runtime_parachains::{configuration, origin, shared}; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter as XcmCurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, DescribeAllTerminal, DescribeFamily, FixedRateOfFungible, - FixedWeightBounds, FrameTransactionalProcessor, HashedDescription, IsConcrete, + FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, HashedDescription, IsConcrete, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, WithComputedOrigin, }; use xcm_executor::{Config, XcmExecutor}; @@ -119,9 +117,8 @@ pub type SovereignAccountOf = ( ChildParachainConvertsVia, ); -#[allow(deprecated)] pub type LocalBalancesTransactor = - XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>; + FungibleAdapter, SovereignAccountOf, AccountId, ()>; pub type AssetTransactors = LocalBalancesTransactor; -- GitLab From 96ebb305edb202b8f707d3a02d6bfbc5dca82994 Mon Sep 17 00:00:00 2001 From: Dmitry Markin Date: Tue, 13 Feb 2024 21:59:44 +0200 Subject: [PATCH 055/133] Prepare syncing for parallel sync strategies (#3224) This PR should supersede https://github.com/paritytech/polkadot-sdk/pull/2814 and accomplish the same with less changes. It's needed to run sync strategies in parallel, like running `ChainSync` and `GapSync` as independent strategies, and running `ChainSync` and Sync 2.0 alongside each other. The difference with https://github.com/paritytech/polkadot-sdk/pull/2814 is that we allow simultaneous requests to remote peers initiated by different strategies, as this is not tracked on the remote node in any way. Therefore, `PeerPool` is not needed. CC @skunert --------- Co-authored-by: Sebastian Kunert --- substrate/client/network/sync/src/engine.rs | 143 +++-- ..._requests.rs => justification_requests.rs} | 4 + substrate/client/network/sync/src/lib.rs | 2 +- .../network/sync/src/pending_responses.rs | 31 +- substrate/client/network/sync/src/strategy.rs | 602 ++++++++++-------- .../network/sync/src/strategy/chain_sync.rs | 66 +- .../sync/src/strategy/chain_sync/test.rs | 72 ++- .../client/network/sync/src/strategy/state.rs | 5 - 8 files changed, 542 insertions(+), 383 deletions(-) rename substrate/client/network/sync/src/{extra_requests.rs => justification_requests.rs} (98%) diff --git a/substrate/client/network/sync/src/engine.rs b/substrate/client/network/sync/src/engine.rs index 7486c091ebf..c1a7009eeb0 100644 --- a/substrate/client/network/sync/src/engine.rs +++ b/substrate/client/network/sync/src/engine.rs @@ -33,7 +33,7 @@ use crate::{ }, strategy::{ warp::{EncodedProof, WarpProofRequest, WarpSyncParams}, - SyncingAction, SyncingConfig, SyncingStrategy, + StrategyKey, SyncingAction, SyncingConfig, SyncingStrategy, }, types::{ BadPeer, ExtendedPeerInfo, OpaqueStateRequest, OpaqueStateResponse, PeerRequest, SyncEvent, @@ -48,7 +48,7 @@ use futures::{ FutureExt, StreamExt, }; use libp2p::{request_response::OutboundFailure, PeerId}; -use log::{debug, error, trace}; +use log::{debug, error, trace, warn}; use prometheus_endpoint::{ register, Counter, Gauge, MetricSource, Opts, PrometheusError, Registry, SourcedGauge, U64, }; @@ -214,9 +214,6 @@ pub struct SyncingEngine { /// Syncing strategy. strategy: SyncingStrategy, - /// Syncing configuration for startegies. - syncing_config: SyncingConfig, - /// Blockchain client. client: Arc, @@ -441,8 +438,7 @@ where .map_or(futures::future::pending().boxed().fuse(), |rx| rx.boxed().fuse()); // Initialize syncing strategy. - let strategy = - SyncingStrategy::new(syncing_config.clone(), client.clone(), warp_sync_config)?; + let strategy = SyncingStrategy::new(syncing_config, client.clone(), warp_sync_config)?; let block_announce_protocol_name = block_announce_config.protocol_name().clone(); let (tx, service_rx) = tracing_unbounded("mpsc_chain_sync", 100_000); @@ -471,7 +467,6 @@ where roles, client, strategy, - syncing_config, network_service, peers: HashMap::new(), block_announce_data_cache: LruMap::new(ByLength::new(cache_capacity)), @@ -661,8 +656,15 @@ where Some(event) => self.process_notification_event(event), None => return, }, - warp_target_block_header = &mut self.warp_sync_target_block_header_rx_fused => - self.pass_warp_sync_target_block_header(warp_target_block_header), + warp_target_block_header = &mut self.warp_sync_target_block_header_rx_fused => { + if let Err(_) = self.pass_warp_sync_target_block_header(warp_target_block_header) { + error!( + target: LOG_TARGET, + "Failed to set warp sync target block header, terminating `SyncingEngine`.", + ); + return + } + }, response_event = self.pending_responses.select_next_some() => self.process_response_event(response_event), validation_result = self.block_announce_validator.select_next_some() => @@ -675,48 +677,61 @@ where // Process actions requested by a syncing strategy. if let Err(e) = self.process_strategy_actions() { - error!("Terminating `SyncingEngine` due to fatal error: {e:?}"); + error!( + target: LOG_TARGET, + "Terminating `SyncingEngine` due to fatal error: {e:?}.", + ); return } } } fn process_strategy_actions(&mut self) -> Result<(), ClientError> { - for action in self.strategy.actions() { + for action in self.strategy.actions()? { match action { - SyncingAction::SendBlockRequest { peer_id, request } => { + SyncingAction::SendBlockRequest { peer_id, key, request } => { // Sending block request implies dropping obsolete pending response as we are // not interested in it anymore (see [`SyncingAction::SendBlockRequest`]). - // Furthermore, only one request at a time is allowed to any peer. - let removed = self.pending_responses.remove(&peer_id); - self.send_block_request(peer_id, request.clone()); - - trace!( - target: LOG_TARGET, - "Processed `ChainSyncAction::SendBlockRequest` to {} with {:?}, stale response removed: {}.", - peer_id, - request, - removed, - ) + let removed = self.pending_responses.remove(peer_id, key); + self.send_block_request(peer_id, key, request.clone()); + + if removed { + warn!( + target: LOG_TARGET, + "Processed `ChainSyncAction::SendBlockRequest` to {} from {:?} with {:?}. \ + Stale response removed!", + peer_id, + key, + request, + ) + } else { + trace!( + target: LOG_TARGET, + "Processed `ChainSyncAction::SendBlockRequest` to {} from {:?} with {:?}.", + peer_id, + key, + request, + ) + } }, - SyncingAction::CancelBlockRequest { peer_id } => { - let removed = self.pending_responses.remove(&peer_id); + SyncingAction::CancelRequest { peer_id, key } => { + let removed = self.pending_responses.remove(peer_id, key); trace!( target: LOG_TARGET, "Processed {action:?}, response removed: {removed}.", ); }, - SyncingAction::SendStateRequest { peer_id, request } => { - self.send_state_request(peer_id, request); + SyncingAction::SendStateRequest { peer_id, key, request } => { + self.send_state_request(peer_id, key, request); trace!( target: LOG_TARGET, - "Processed `ChainSyncAction::SendBlockRequest` to {peer_id}.", + "Processed `ChainSyncAction::SendStateRequest` to {peer_id}.", ); }, - SyncingAction::SendWarpProofRequest { peer_id, request } => { - self.send_warp_proof_request(peer_id, request.clone()); + SyncingAction::SendWarpProofRequest { peer_id, key, request } => { + self.send_warp_proof_request(peer_id, key, request.clone()); trace!( target: LOG_TARGET, @@ -726,7 +741,7 @@ where ); }, SyncingAction::DropPeer(BadPeer(peer_id, rep)) => { - self.pending_responses.remove(&peer_id); + self.pending_responses.remove_all(&peer_id); self.network_service .disconnect_peer(peer_id, self.block_announce_protocol_name.clone()); self.network_service.report_peer(peer_id, rep); @@ -753,20 +768,8 @@ where number, ) }, - SyncingAction::Finished => { - let connected_peers = self.peers.iter().filter_map(|(peer_id, peer)| { - peer.info.roles.is_full().then_some(( - *peer_id, - peer.info.best_hash, - peer.info.best_number, - )) - }); - self.strategy.switch_to_next( - self.syncing_config.clone(), - self.client.clone(), - connected_peers, - )?; - }, + // Nothing to do, this is handled internally by `SyncingStrategy`. + SyncingAction::Finished => {}, } } @@ -948,23 +951,18 @@ where } } - fn pass_warp_sync_target_block_header(&mut self, header: Result) { + fn pass_warp_sync_target_block_header( + &mut self, + header: Result, + ) -> Result<(), ()> { match header { - Ok(header) => - if let SyncingStrategy::WarpSyncStrategy(warp_sync) = &mut self.strategy { - warp_sync.set_target_block(header); - } else { - error!( - target: LOG_TARGET, - "Cannot set warp sync target block: no warp sync strategy is active." - ); - debug_assert!(false); - }, + Ok(header) => self.strategy.set_warp_sync_target_block_header(header), Err(err) => { error!( target: LOG_TARGET, "Failed to get target block for warp sync. Error: {err:?}", ); + Err(()) }, } } @@ -1002,7 +1000,7 @@ where } self.strategy.remove_peer(&peer_id); - self.pending_responses.remove(&peer_id); + self.pending_responses.remove_all(&peer_id); self.event_streams .retain(|stream| stream.unbounded_send(SyncEvent::PeerDisconnected(peer_id)).is_ok()); } @@ -1167,7 +1165,7 @@ where Ok(()) } - fn send_block_request(&mut self, peer_id: PeerId, request: BlockRequest) { + fn send_block_request(&mut self, peer_id: PeerId, key: StrategyKey, request: BlockRequest) { if !self.peers.contains_key(&peer_id) { trace!(target: LOG_TARGET, "Cannot send block request to unknown peer {peer_id}"); debug_assert!(false); @@ -1178,12 +1176,18 @@ where self.pending_responses.insert( peer_id, + key, PeerRequest::Block(request.clone()), async move { downloader.download_blocks(peer_id, request).await }.boxed(), ); } - fn send_state_request(&mut self, peer_id: PeerId, request: OpaqueStateRequest) { + fn send_state_request( + &mut self, + peer_id: PeerId, + key: StrategyKey, + request: OpaqueStateRequest, + ) { if !self.peers.contains_key(&peer_id) { trace!(target: LOG_TARGET, "Cannot send state request to unknown peer {peer_id}"); debug_assert!(false); @@ -1192,7 +1196,7 @@ where let (tx, rx) = oneshot::channel(); - self.pending_responses.insert(peer_id, PeerRequest::State, rx.boxed()); + self.pending_responses.insert(peer_id, key, PeerRequest::State, rx.boxed()); match Self::encode_state_request(&request) { Ok(data) => { @@ -1213,7 +1217,12 @@ where } } - fn send_warp_proof_request(&mut self, peer_id: PeerId, request: WarpProofRequest) { + fn send_warp_proof_request( + &mut self, + peer_id: PeerId, + key: StrategyKey, + request: WarpProofRequest, + ) { if !self.peers.contains_key(&peer_id) { trace!(target: LOG_TARGET, "Cannot send warp proof request to unknown peer {peer_id}"); debug_assert!(false); @@ -1222,7 +1231,7 @@ where let (tx, rx) = oneshot::channel(); - self.pending_responses.insert(peer_id, PeerRequest::WarpProof, rx.boxed()); + self.pending_responses.insert(peer_id, key, PeerRequest::WarpProof, rx.boxed()); match &self.warp_sync_protocol_name { Some(name) => self.network_service.start_request( @@ -1259,14 +1268,14 @@ where } fn process_response_event(&mut self, response_event: ResponseEvent) { - let ResponseEvent { peer_id, request, response } = response_event; + let ResponseEvent { peer_id, key, request, response } = response_event; match response { Ok(Ok((resp, _))) => match request { PeerRequest::Block(req) => { match self.block_downloader.block_response_into_blocks(&req, resp) { Ok(blocks) => { - self.strategy.on_block_response(peer_id, req, blocks); + self.strategy.on_block_response(peer_id, key, req, blocks); }, Err(BlockResponseError::DecodeFailed(e)) => { debug!( @@ -1311,10 +1320,10 @@ where }, }; - self.strategy.on_state_response(peer_id, response); + self.strategy.on_state_response(peer_id, key, response); }, PeerRequest::WarpProof => { - self.strategy.on_warp_proof_response(&peer_id, EncodedProof(resp)); + self.strategy.on_warp_proof_response(&peer_id, key, EncodedProof(resp)); }, }, Ok(Err(e)) => { diff --git a/substrate/client/network/sync/src/extra_requests.rs b/substrate/client/network/sync/src/justification_requests.rs similarity index 98% rename from substrate/client/network/sync/src/extra_requests.rs rename to substrate/client/network/sync/src/justification_requests.rs index cd3008d270b..799b6df5831 100644 --- a/substrate/client/network/sync/src/extra_requests.rs +++ b/substrate/client/network/sync/src/justification_requests.rs @@ -16,6 +16,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +//! Justification requests scheduling. [`ExtraRequests`] manages requesting justifications +//! from peers taking into account forks and their finalization (dropping pending requests +//! that don't make sense after one of the forks is finalized). + use crate::{ request_metrics::Metrics, strategy::chain_sync::{PeerSync, PeerSyncState}, diff --git a/substrate/client/network/sync/src/lib.rs b/substrate/client/network/sync/src/lib.rs index 494e3b87aa9..e23a23e735d 100644 --- a/substrate/client/network/sync/src/lib.rs +++ b/substrate/client/network/sync/src/lib.rs @@ -23,8 +23,8 @@ pub use strategy::warp::{WarpSyncParams, WarpSyncPhase, WarpSyncProgress}; pub use types::{SyncEvent, SyncEventStream, SyncState, SyncStatus, SyncStatusProvider}; mod block_announce_validator; -mod extra_requests; mod futures_stream; +mod justification_requests; mod pending_responses; mod request_metrics; mod schema; diff --git a/substrate/client/network/sync/src/pending_responses.rs b/substrate/client/network/sync/src/pending_responses.rs index 21e409eb847..602c69df7ff 100644 --- a/substrate/client/network/sync/src/pending_responses.rs +++ b/substrate/client/network/sync/src/pending_responses.rs @@ -19,7 +19,7 @@ //! [`PendingResponses`] is responsible for keeping track of pending responses and //! polling them. [`Stream`] implemented by [`PendingResponses`] never terminates. -use crate::{types::PeerRequest, LOG_TARGET}; +use crate::{strategy::StrategyKey, types::PeerRequest, LOG_TARGET}; use futures::{ channel::oneshot, future::BoxFuture, @@ -42,6 +42,7 @@ type ResponseFuture = BoxFuture<'static, ResponseResult>; /// An event we receive once a pending response future resolves. pub(crate) struct ResponseEvent { pub peer_id: PeerId, + pub key: StrategyKey, pub request: PeerRequest, pub response: ResponseResult, } @@ -49,7 +50,8 @@ pub(crate) struct ResponseEvent { /// Stream taking care of polling pending responses. pub(crate) struct PendingResponses { /// Pending responses - pending_responses: StreamMap, ResponseResult)>>, + pending_responses: + StreamMap<(PeerId, StrategyKey), BoxStream<'static, (PeerRequest, ResponseResult)>>, /// Waker to implement never terminating stream waker: Option, } @@ -62,6 +64,7 @@ impl PendingResponses { pub fn insert( &mut self, peer_id: PeerId, + key: StrategyKey, request: PeerRequest, response_future: ResponseFuture, ) { @@ -70,7 +73,7 @@ impl PendingResponses { if self .pending_responses .insert( - peer_id, + (peer_id, key), Box::pin(async move { (request, response_future.await) }.into_stream()), ) .is_some() @@ -87,8 +90,20 @@ impl PendingResponses { } } - pub fn remove(&mut self, peer_id: &PeerId) -> bool { - self.pending_responses.remove(peer_id).is_some() + pub fn remove(&mut self, peer_id: PeerId, key: StrategyKey) -> bool { + self.pending_responses.remove(&(peer_id, key)).is_some() + } + + pub fn remove_all(&mut self, peer_id: &PeerId) { + let to_remove = self + .pending_responses + .keys() + .filter(|(peer, _key)| peer == peer_id) + .cloned() + .collect::>(); + to_remove.iter().for_each(|k| { + self.pending_responses.remove(k); + }); } pub fn len(&self) -> usize { @@ -104,13 +119,13 @@ impl Stream for PendingResponses { cx: &mut Context<'_>, ) -> Poll> { match self.pending_responses.poll_next_unpin(cx) { - Poll::Ready(Some((peer_id, (request, response)))) => { + Poll::Ready(Some(((peer_id, key), (request, response)))) => { // We need to manually remove the stream, because `StreamMap` doesn't know yet that // it's going to yield `None`, so may not remove it before the next request is made // to the same peer. - self.pending_responses.remove(&peer_id); + self.pending_responses.remove(&(peer_id, key)); - Poll::Ready(Some(ResponseEvent { peer_id, request, response })) + Poll::Ready(Some(ResponseEvent { peer_id, key, request, response })) }, Poll::Ready(None) | Poll::Pending => { self.waker = Some(cx.waker().clone()); diff --git a/substrate/client/network/sync/src/strategy.rs b/substrate/client/network/sync/src/strategy.rs index dbfb4188ec3..7d6e6a8d3b8 100644 --- a/substrate/client/network/sync/src/strategy.rs +++ b/substrate/client/network/sync/src/strategy.rs @@ -30,7 +30,7 @@ use crate::{ }; use chain_sync::{ChainSync, ChainSyncAction, ChainSyncMode}; use libp2p::PeerId; -use log::{error, info}; +use log::{debug, error, info}; use prometheus_endpoint::Registry; use sc_client_api::{BlockBackend, ProofProvider}; use sc_consensus::{BlockImportError, BlockImportStatus, IncomingBlock}; @@ -41,11 +41,11 @@ use sc_network_common::sync::{ use sp_blockchain::{Error as ClientError, HeaderBackend, HeaderMetadata}; use sp_consensus::BlockOrigin; use sp_runtime::{ - traits::{Block as BlockT, NumberFor}, + traits::{Block as BlockT, Header, NumberFor}, Justifications, }; use state::{StateStrategy, StateStrategyAction}; -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; use warp::{EncodedProof, WarpProofRequest, WarpSync, WarpSyncAction, WarpSyncConfig}; /// Corresponding `ChainSync` mode. @@ -71,16 +71,27 @@ pub struct SyncingConfig { pub metrics_registry: Option, } +/// The key identifying a specific strategy for responses routing. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum StrategyKey { + /// Warp sync initiated this request. + Warp, + /// State sync initiated this request. + State, + /// `ChainSync` initiated this request. + ChainSync, +} + #[derive(Debug)] pub enum SyncingAction { /// Send block request to peer. Always implies dropping a stale block request to the same peer. - SendBlockRequest { peer_id: PeerId, request: BlockRequest }, - /// Drop stale block request. - CancelBlockRequest { peer_id: PeerId }, + SendBlockRequest { peer_id: PeerId, key: StrategyKey, request: BlockRequest }, /// Send state request to peer. - SendStateRequest { peer_id: PeerId, request: OpaqueStateRequest }, + SendStateRequest { peer_id: PeerId, key: StrategyKey, request: OpaqueStateRequest }, /// Send warp proof request to peer. - SendWarpProofRequest { peer_id: PeerId, request: WarpProofRequest }, + SendWarpProofRequest { peer_id: PeerId, key: StrategyKey, request: WarpProofRequest }, + /// Drop stale request. + CancelRequest { peer_id: PeerId, key: StrategyKey }, /// Peer misbehaved. Disconnect, report it and cancel any requests to it. DropPeer(BadPeer), /// Import blocks. @@ -92,15 +103,75 @@ pub enum SyncingAction { number: NumberFor, justifications: Justifications, }, - /// Syncing strategy has finished. + /// Strategy finished. Nothing to do, this is handled by `SyncingStrategy`. Finished, } +impl SyncingAction { + fn is_finished(&self) -> bool { + matches!(self, SyncingAction::Finished) + } +} + +impl From> for SyncingAction { + fn from(action: WarpSyncAction) -> Self { + match action { + WarpSyncAction::SendWarpProofRequest { peer_id, request } => + SyncingAction::SendWarpProofRequest { peer_id, key: StrategyKey::Warp, request }, + WarpSyncAction::SendBlockRequest { peer_id, request } => + SyncingAction::SendBlockRequest { peer_id, key: StrategyKey::Warp, request }, + WarpSyncAction::DropPeer(bad_peer) => SyncingAction::DropPeer(bad_peer), + WarpSyncAction::Finished => SyncingAction::Finished, + } + } +} + +impl From> for SyncingAction { + fn from(action: StateStrategyAction) -> Self { + match action { + StateStrategyAction::SendStateRequest { peer_id, request } => + SyncingAction::SendStateRequest { peer_id, key: StrategyKey::State, request }, + StateStrategyAction::DropPeer(bad_peer) => SyncingAction::DropPeer(bad_peer), + StateStrategyAction::ImportBlocks { origin, blocks } => + SyncingAction::ImportBlocks { origin, blocks }, + StateStrategyAction::Finished => SyncingAction::Finished, + } + } +} + +impl From> for SyncingAction { + fn from(action: ChainSyncAction) -> Self { + match action { + ChainSyncAction::SendBlockRequest { peer_id, request } => + SyncingAction::SendBlockRequest { peer_id, key: StrategyKey::ChainSync, request }, + ChainSyncAction::SendStateRequest { peer_id, request } => + SyncingAction::SendStateRequest { peer_id, key: StrategyKey::ChainSync, request }, + ChainSyncAction::CancelRequest { peer_id } => + SyncingAction::CancelRequest { peer_id, key: StrategyKey::ChainSync }, + ChainSyncAction::DropPeer(bad_peer) => SyncingAction::DropPeer(bad_peer), + ChainSyncAction::ImportBlocks { origin, blocks } => + SyncingAction::ImportBlocks { origin, blocks }, + ChainSyncAction::ImportJustifications { peer_id, hash, number, justifications } => + SyncingAction::ImportJustifications { peer_id, hash, number, justifications }, + } + } +} + /// Proxy to specific syncing strategies. -pub enum SyncingStrategy { - WarpSyncStrategy(WarpSync), - StateSyncStrategy(StateStrategy), - ChainSyncStrategy(ChainSync), +pub struct SyncingStrategy { + /// Syncing configuration. + config: SyncingConfig, + /// Client used by syncing strategies. + client: Arc, + /// Warp strategy. + warp: Option>, + /// State strategy. + state: Option>, + /// `ChainSync` strategy.` + chain_sync: Option>, + /// Connected peers and their best blocks used to seed a new strategy when switching to it in + /// [`SyncingStrategy::proceed_to_next`]. + peer_best_blocks: HashMap)>, } impl SyncingStrategy @@ -123,37 +194,51 @@ where if let SyncMode::Warp = config.mode { let warp_sync_config = warp_sync_config .expect("Warp sync configuration must be supplied in warp sync mode."); - Ok(Self::WarpSyncStrategy(WarpSync::new(client.clone(), warp_sync_config))) + let warp_sync = WarpSync::new(client.clone(), warp_sync_config); + Ok(Self { + config, + client, + warp: Some(warp_sync), + state: None, + chain_sync: None, + peer_best_blocks: Default::default(), + }) } else { - Ok(Self::ChainSyncStrategy(ChainSync::new( + let chain_sync = ChainSync::new( chain_sync_mode(config.mode), client.clone(), config.max_parallel_downloads, config.max_blocks_per_request, - config.metrics_registry, - )?)) + config.metrics_registry.clone(), + std::iter::empty(), + )?; + Ok(Self { + config, + client, + warp: None, + state: None, + chain_sync: Some(chain_sync), + peer_best_blocks: Default::default(), + }) } } /// Notify that a new peer has connected. pub fn add_peer(&mut self, peer_id: PeerId, best_hash: B::Hash, best_number: NumberFor) { - match self { - SyncingStrategy::WarpSyncStrategy(strategy) => - strategy.add_peer(peer_id, best_hash, best_number), - SyncingStrategy::StateSyncStrategy(strategy) => - strategy.add_peer(peer_id, best_hash, best_number), - SyncingStrategy::ChainSyncStrategy(strategy) => - strategy.add_peer(peer_id, best_hash, best_number), - } + self.peer_best_blocks.insert(peer_id, (best_hash, best_number)); + + self.warp.as_mut().map(|s| s.add_peer(peer_id, best_hash, best_number)); + self.state.as_mut().map(|s| s.add_peer(peer_id, best_hash, best_number)); + self.chain_sync.as_mut().map(|s| s.add_peer(peer_id, best_hash, best_number)); } /// Notify that a peer has disconnected. pub fn remove_peer(&mut self, peer_id: &PeerId) { - match self { - SyncingStrategy::WarpSyncStrategy(strategy) => strategy.remove_peer(peer_id), - SyncingStrategy::StateSyncStrategy(strategy) => strategy.remove_peer(peer_id), - SyncingStrategy::ChainSyncStrategy(strategy) => strategy.remove_peer(peer_id), - } + self.warp.as_mut().map(|s| s.remove_peer(peer_id)); + self.state.as_mut().map(|s| s.remove_peer(peer_id)); + self.chain_sync.as_mut().map(|s| s.remove_peer(peer_id)); + + self.peer_best_blocks.remove(peer_id); } /// Submit a validated block announcement. @@ -165,14 +250,31 @@ where peer_id: PeerId, announce: &BlockAnnounce, ) -> Option<(B::Hash, NumberFor)> { - match self { - SyncingStrategy::WarpSyncStrategy(strategy) => - strategy.on_validated_block_announce(is_best, peer_id, announce), - SyncingStrategy::StateSyncStrategy(strategy) => - strategy.on_validated_block_announce(is_best, peer_id, announce), - SyncingStrategy::ChainSyncStrategy(strategy) => - strategy.on_validated_block_announce(is_best, peer_id, announce), + let new_best = if let Some(ref mut warp) = self.warp { + warp.on_validated_block_announce(is_best, peer_id, announce) + } else if let Some(ref mut state) = self.state { + state.on_validated_block_announce(is_best, peer_id, announce) + } else if let Some(ref mut chain_sync) = self.chain_sync { + chain_sync.on_validated_block_announce(is_best, peer_id, announce) + } else { + error!(target: LOG_TARGET, "No syncing strategy is active."); + debug_assert!(false); + Some((announce.header.hash(), *announce.header.number())) + }; + + if let Some(new_best) = new_best { + if let Some(best) = self.peer_best_blocks.get_mut(&peer_id) { + *best = new_best; + } else { + debug!( + target: LOG_TARGET, + "Cannot update `peer_best_blocks` as peer {peer_id} is not known to `Strategy` \ + (already disconnected?)", + ); + } } + + new_best } /// Configure an explicit fork sync request in case external code has detected that there is a @@ -183,40 +285,33 @@ where hash: &B::Hash, number: NumberFor, ) { - match self { - SyncingStrategy::WarpSyncStrategy(_) => {}, - SyncingStrategy::StateSyncStrategy(_) => {}, - SyncingStrategy::ChainSyncStrategy(strategy) => - strategy.set_sync_fork_request(peers, hash, number), + // Fork requests are only handled by `ChainSync`. + if let Some(ref mut chain_sync) = self.chain_sync { + chain_sync.set_sync_fork_request(peers.clone(), hash, number); } } /// Request extra justification. pub fn request_justification(&mut self, hash: &B::Hash, number: NumberFor) { - match self { - SyncingStrategy::WarpSyncStrategy(_) => {}, - SyncingStrategy::StateSyncStrategy(_) => {}, - SyncingStrategy::ChainSyncStrategy(strategy) => - strategy.request_justification(hash, number), + // Justifications can only be requested via `ChainSync`. + if let Some(ref mut chain_sync) = self.chain_sync { + chain_sync.request_justification(hash, number); } } /// Clear extra justification requests. pub fn clear_justification_requests(&mut self) { - match self { - SyncingStrategy::WarpSyncStrategy(_) => {}, - SyncingStrategy::StateSyncStrategy(_) => {}, - SyncingStrategy::ChainSyncStrategy(strategy) => strategy.clear_justification_requests(), + // Justification requests can only be cleared by `ChainSync`. + if let Some(ref mut chain_sync) = self.chain_sync { + chain_sync.clear_justification_requests(); } } /// Report a justification import (successful or not). pub fn on_justification_import(&mut self, hash: B::Hash, number: NumberFor, success: bool) { - match self { - SyncingStrategy::WarpSyncStrategy(_) => {}, - SyncingStrategy::StateSyncStrategy(_) => {}, - SyncingStrategy::ChainSyncStrategy(strategy) => - strategy.on_justification_import(hash, number, success), + // Only `ChainSync` is interested in justification import. + if let Some(ref mut chain_sync) = self.chain_sync { + chain_sync.on_justification_import(hash, number, success); } } @@ -224,36 +319,65 @@ where pub fn on_block_response( &mut self, peer_id: PeerId, + key: StrategyKey, request: BlockRequest, blocks: Vec>, ) { - match self { - SyncingStrategy::WarpSyncStrategy(strategy) => - strategy.on_block_response(peer_id, request, blocks), - SyncingStrategy::StateSyncStrategy(_) => {}, - SyncingStrategy::ChainSyncStrategy(strategy) => - strategy.on_block_response(peer_id, request, blocks), + if let (StrategyKey::Warp, Some(ref mut warp)) = (key, &mut self.warp) { + warp.on_block_response(peer_id, request, blocks); + } else if let (StrategyKey::ChainSync, Some(ref mut chain_sync)) = + (key, &mut self.chain_sync) + { + chain_sync.on_block_response(peer_id, request, blocks); + } else { + error!( + target: LOG_TARGET, + "`on_block_response()` called with unexpected key {key:?} \ + or corresponding strategy is not active.", + ); + debug_assert!(false); } } /// Process state response. - pub fn on_state_response(&mut self, peer_id: PeerId, response: OpaqueStateResponse) { - match self { - SyncingStrategy::WarpSyncStrategy(_) => {}, - SyncingStrategy::StateSyncStrategy(strategy) => - strategy.on_state_response(peer_id, response), - SyncingStrategy::ChainSyncStrategy(strategy) => - strategy.on_state_response(peer_id, response), + pub fn on_state_response( + &mut self, + peer_id: PeerId, + key: StrategyKey, + response: OpaqueStateResponse, + ) { + if let (StrategyKey::State, Some(ref mut state)) = (key, &mut self.state) { + state.on_state_response(peer_id, response); + } else if let (StrategyKey::ChainSync, Some(ref mut chain_sync)) = + (key, &mut self.chain_sync) + { + chain_sync.on_state_response(peer_id, response); + } else { + error!( + target: LOG_TARGET, + "`on_state_response()` called with unexpected key {key:?} \ + or corresponding strategy is not active.", + ); + debug_assert!(false); } } /// Process warp proof response. - pub fn on_warp_proof_response(&mut self, peer_id: &PeerId, response: EncodedProof) { - match self { - SyncingStrategy::WarpSyncStrategy(strategy) => - strategy.on_warp_proof_response(peer_id, response), - SyncingStrategy::StateSyncStrategy(_) => {}, - SyncingStrategy::ChainSyncStrategy(_) => {}, + pub fn on_warp_proof_response( + &mut self, + peer_id: &PeerId, + key: StrategyKey, + response: EncodedProof, + ) { + if let (StrategyKey::Warp, Some(ref mut warp)) = (key, &mut self.warp) { + warp.on_warp_proof_response(peer_id, response); + } else { + error!( + target: LOG_TARGET, + "`on_warp_proof_response()` called with unexpected key {key:?} \ + or warp strategy is not active", + ); + debug_assert!(false); } } @@ -264,226 +388,202 @@ where count: usize, results: Vec<(Result>, BlockImportError>, B::Hash)>, ) { - match self { - SyncingStrategy::WarpSyncStrategy(_) => {}, - SyncingStrategy::StateSyncStrategy(strategy) => - strategy.on_blocks_processed(imported, count, results), - SyncingStrategy::ChainSyncStrategy(strategy) => - strategy.on_blocks_processed(imported, count, results), + // Only `StateStrategy` and `ChainSync` are interested in block processing notifications. + if let Some(ref mut state) = self.state { + state.on_blocks_processed(imported, count, results); + } else if let Some(ref mut chain_sync) = self.chain_sync { + chain_sync.on_blocks_processed(imported, count, results); } } /// Notify a syncing strategy that a block has been finalized. pub fn on_block_finalized(&mut self, hash: &B::Hash, number: NumberFor) { - match self { - SyncingStrategy::WarpSyncStrategy(_) => {}, - SyncingStrategy::StateSyncStrategy(_) => {}, - SyncingStrategy::ChainSyncStrategy(strategy) => - strategy.on_block_finalized(hash, number), + // Only `ChainSync` is interested in block finalization notifications. + if let Some(ref mut chain_sync) = self.chain_sync { + chain_sync.on_block_finalized(hash, number); } } /// Inform sync about a new best imported block. pub fn update_chain_info(&mut self, best_hash: &B::Hash, best_number: NumberFor) { - match self { - SyncingStrategy::WarpSyncStrategy(_) => {}, - SyncingStrategy::StateSyncStrategy(_) => {}, - SyncingStrategy::ChainSyncStrategy(strategy) => - strategy.update_chain_info(best_hash, best_number), + // This is relevant to `ChainSync` only. + if let Some(ref mut chain_sync) = self.chain_sync { + chain_sync.update_chain_info(best_hash, best_number); } } // Are we in major sync mode? pub fn is_major_syncing(&self) -> bool { - match self { - SyncingStrategy::WarpSyncStrategy(_) => true, - SyncingStrategy::StateSyncStrategy(_) => true, - SyncingStrategy::ChainSyncStrategy(strategy) => - strategy.status().state.is_major_syncing(), - } + self.warp.is_some() || + self.state.is_some() || + match self.chain_sync { + Some(ref s) => s.status().state.is_major_syncing(), + None => unreachable!("At least one syncing startegy is active; qed"), + } } /// Get the number of peers known to the syncing strategy. pub fn num_peers(&self) -> usize { - match self { - SyncingStrategy::WarpSyncStrategy(strategy) => strategy.num_peers(), - SyncingStrategy::StateSyncStrategy(strategy) => strategy.num_peers(), - SyncingStrategy::ChainSyncStrategy(strategy) => strategy.num_peers(), - } + self.peer_best_blocks.len() } /// Returns the current sync status. pub fn status(&self) -> SyncStatus { - match self { - SyncingStrategy::WarpSyncStrategy(strategy) => strategy.status(), - SyncingStrategy::StateSyncStrategy(strategy) => strategy.status(), - SyncingStrategy::ChainSyncStrategy(strategy) => strategy.status(), + // This function presumes that startegies are executed serially and must be refactored + // once we have parallel strategies. + if let Some(ref warp) = self.warp { + warp.status() + } else if let Some(ref state) = self.state { + state.status() + } else if let Some(ref chain_sync) = self.chain_sync { + chain_sync.status() + } else { + unreachable!("At least one syncing startegy is always active; qed") } } /// Get the total number of downloaded blocks. pub fn num_downloaded_blocks(&self) -> usize { - match self { - SyncingStrategy::WarpSyncStrategy(_) => 0, - SyncingStrategy::StateSyncStrategy(_) => 0, - SyncingStrategy::ChainSyncStrategy(strategy) => strategy.num_downloaded_blocks(), - } + self.chain_sync + .as_ref() + .map_or(0, |chain_sync| chain_sync.num_downloaded_blocks()) } /// Get an estimate of the number of parallel sync requests. pub fn num_sync_requests(&self) -> usize { - match self { - SyncingStrategy::WarpSyncStrategy(_) => 0, - SyncingStrategy::StateSyncStrategy(_) => 0, - SyncingStrategy::ChainSyncStrategy(strategy) => strategy.num_sync_requests(), - } + self.chain_sync.as_ref().map_or(0, |chain_sync| chain_sync.num_sync_requests()) } /// Report Prometheus metrics pub fn report_metrics(&self) { - match self { - SyncingStrategy::WarpSyncStrategy(_) => {}, - SyncingStrategy::StateSyncStrategy(_) => {}, - SyncingStrategy::ChainSyncStrategy(strategy) => strategy.report_metrics(), + if let Some(ref chain_sync) = self.chain_sync { + chain_sync.report_metrics(); + } + } + + /// Let `WarpSync` know about target block header + pub fn set_warp_sync_target_block_header( + &mut self, + target_header: B::Header, + ) -> Result<(), ()> { + match self.warp { + Some(ref mut warp) => { + warp.set_target_block(target_header); + Ok(()) + }, + None => { + error!( + target: LOG_TARGET, + "Cannot set warp sync target block: no warp sync strategy is active." + ); + debug_assert!(false); + Err(()) + }, } } /// Get actions that should be performed by the owner on the strategy's behalf #[must_use] - pub fn actions(&mut self) -> Box>> { - match self { - SyncingStrategy::WarpSyncStrategy(strategy) => - Box::new(strategy.actions().map(|action| match action { - WarpSyncAction::SendWarpProofRequest { peer_id, request } => - SyncingAction::SendWarpProofRequest { peer_id, request }, - WarpSyncAction::SendBlockRequest { peer_id, request } => - SyncingAction::SendBlockRequest { peer_id, request }, - WarpSyncAction::DropPeer(bad_peer) => SyncingAction::DropPeer(bad_peer), - WarpSyncAction::Finished => SyncingAction::Finished, - })), - SyncingStrategy::StateSyncStrategy(strategy) => - Box::new(strategy.actions().map(|action| match action { - StateStrategyAction::SendStateRequest { peer_id, request } => - SyncingAction::SendStateRequest { peer_id, request }, - StateStrategyAction::DropPeer(bad_peer) => SyncingAction::DropPeer(bad_peer), - StateStrategyAction::ImportBlocks { origin, blocks } => - SyncingAction::ImportBlocks { origin, blocks }, - StateStrategyAction::Finished => SyncingAction::Finished, - })), - SyncingStrategy::ChainSyncStrategy(strategy) => - Box::new(strategy.actions().map(|action| match action { - ChainSyncAction::SendBlockRequest { peer_id, request } => - SyncingAction::SendBlockRequest { peer_id, request }, - ChainSyncAction::CancelBlockRequest { peer_id } => - SyncingAction::CancelBlockRequest { peer_id }, - ChainSyncAction::SendStateRequest { peer_id, request } => - SyncingAction::SendStateRequest { peer_id, request }, - ChainSyncAction::DropPeer(bad_peer) => SyncingAction::DropPeer(bad_peer), - ChainSyncAction::ImportBlocks { origin, blocks } => - SyncingAction::ImportBlocks { origin, blocks }, - ChainSyncAction::ImportJustifications { - peer_id, - hash, - number, - justifications, - } => SyncingAction::ImportJustifications { - peer_id, - hash, - number, - justifications, - }, - })), + pub fn actions(&mut self) -> Result>, ClientError> { + // This function presumes that strategies are executed serially and must be refactored once + // we have parallel strategies. + let actions: Vec<_> = if let Some(ref mut warp) = self.warp { + warp.actions().map(Into::into).collect() + } else if let Some(ref mut state) = self.state { + state.actions().map(Into::into).collect() + } else if let Some(ref mut chain_sync) = self.chain_sync { + chain_sync.actions().map(Into::into).collect() + } else { + unreachable!("At least one syncing strategy is always active; qed") + }; + + if actions.iter().any(SyncingAction::is_finished) { + self.proceed_to_next()?; } + + Ok(actions) } - /// Switch to next strategy if the active one finished. - pub fn switch_to_next( - &mut self, - config: SyncingConfig, - client: Arc, - connected_peers: impl Iterator)>, - ) -> Result<(), ClientError> { - match self { - Self::WarpSyncStrategy(warp_sync) => { - match warp_sync.take_result() { - Some(res) => { - info!( - target: LOG_TARGET, - "Warp sync is complete, continuing with state sync." - ); - let state_sync = StateStrategy::new( - client, - res.target_header, - res.target_body, - res.target_justifications, - // skip proofs, only set to `true` in `FastUnsafe` sync mode - false, - connected_peers - .map(|(peer_id, _best_hash, best_number)| (peer_id, best_number)), - ); - - *self = Self::StateSyncStrategy(state_sync); - }, - None => { - error!( - target: LOG_TARGET, - "Warp sync failed. Falling back to full sync." - ); - let mut chain_sync = match ChainSync::new( - chain_sync_mode(config.mode), - client, - config.max_parallel_downloads, - config.max_blocks_per_request, - config.metrics_registry, - ) { - Ok(chain_sync) => chain_sync, - Err(e) => { - error!(target: LOG_TARGET, "Failed to start `ChainSync`."); - return Err(e) - }, - }; - // Let `ChainSync` know about connected peers. - connected_peers.into_iter().for_each( - |(peer_id, best_hash, best_number)| { - chain_sync.add_peer(peer_id, best_hash, best_number) - }, - ); - - *self = Self::ChainSyncStrategy(chain_sync); - }, - } - }, - Self::StateSyncStrategy(state_sync) => { - if state_sync.is_succeded() { - info!(target: LOG_TARGET, "State sync is complete, continuing with block sync."); - } else { - error!(target: LOG_TARGET, "State sync failed. Falling back to full sync."); - } - let mut chain_sync = match ChainSync::new( - chain_sync_mode(config.mode), - client, - config.max_parallel_downloads, - config.max_blocks_per_request, - config.metrics_registry, - ) { - Ok(chain_sync) => chain_sync, - Err(e) => { - error!(target: LOG_TARGET, "Failed to start `ChainSync`."); - return Err(e) - }, - }; - // Let `ChainSync` know about connected peers. - connected_peers.into_iter().for_each(|(peer_id, best_hash, best_number)| { - chain_sync.add_peer(peer_id, best_hash, best_number) - }); - - *self = Self::ChainSyncStrategy(chain_sync); - }, - Self::ChainSyncStrategy(_) => { - error!(target: LOG_TARGET, "`ChainSyncStrategy` is final startegy, cannot switch to next."); - debug_assert!(false); - }, + /// Proceed with the next strategy if the active one finished. + pub fn proceed_to_next(&mut self) -> Result<(), ClientError> { + // The strategies are switched as `WarpSync` -> `StateStartegy` -> `ChainSync`. + if let Some(ref mut warp) = self.warp { + match warp.take_result() { + Some(res) => { + info!( + target: LOG_TARGET, + "Warp sync is complete, continuing with state sync." + ); + let state_sync = StateStrategy::new( + self.client.clone(), + res.target_header, + res.target_body, + res.target_justifications, + false, + self.peer_best_blocks + .iter() + .map(|(peer_id, (_, best_number))| (*peer_id, *best_number)), + ); + + self.warp = None; + self.state = Some(state_sync); + Ok(()) + }, + None => { + error!( + target: LOG_TARGET, + "Warp sync failed. Continuing with full sync." + ); + let chain_sync = match ChainSync::new( + chain_sync_mode(self.config.mode), + self.client.clone(), + self.config.max_parallel_downloads, + self.config.max_blocks_per_request, + self.config.metrics_registry.clone(), + self.peer_best_blocks.iter().map(|(peer_id, (best_hash, best_number))| { + (*peer_id, *best_hash, *best_number) + }), + ) { + Ok(chain_sync) => chain_sync, + Err(e) => { + error!(target: LOG_TARGET, "Failed to start `ChainSync`."); + return Err(e) + }, + }; + + self.warp = None; + self.chain_sync = Some(chain_sync); + Ok(()) + }, + } + } else if let Some(state) = &self.state { + if state.is_succeded() { + info!(target: LOG_TARGET, "State sync is complete, continuing with block sync."); + } else { + error!(target: LOG_TARGET, "State sync failed. Falling back to full sync."); + } + let chain_sync = match ChainSync::new( + chain_sync_mode(self.config.mode), + self.client.clone(), + self.config.max_parallel_downloads, + self.config.max_blocks_per_request, + self.config.metrics_registry.clone(), + self.peer_best_blocks.iter().map(|(peer_id, (best_hash, best_number))| { + (*peer_id, *best_hash, *best_number) + }), + ) { + Ok(chain_sync) => chain_sync, + Err(e) => { + error!(target: LOG_TARGET, "Failed to start `ChainSync`."); + return Err(e); + }, + }; + + self.state = None; + self.chain_sync = Some(chain_sync); + Ok(()) + } else { + unreachable!("Only warp & state strategies can finish; qed") } - Ok(()) } } diff --git a/substrate/client/network/sync/src/strategy/chain_sync.rs b/substrate/client/network/sync/src/strategy/chain_sync.rs index 62c260d582b..ad0c75363e7 100644 --- a/substrate/client/network/sync/src/strategy/chain_sync.rs +++ b/substrate/client/network/sync/src/strategy/chain_sync.rs @@ -30,7 +30,7 @@ use crate::{ blocks::BlockCollection, - extra_requests::ExtraRequests, + justification_requests::ExtraRequests, schema::v1::StateResponse, strategy::{ state_sync::{ImportResult, StateSync, StateSyncProvider}, @@ -212,10 +212,10 @@ struct GapSync { pub enum ChainSyncAction { /// Send block request to peer. Always implies dropping a stale block request to the same peer. SendBlockRequest { peer_id: PeerId, request: BlockRequest }, - /// Drop stale block request. - CancelBlockRequest { peer_id: PeerId }, /// Send state request to peer. SendStateRequest { peer_id: PeerId, request: OpaqueStateRequest }, + /// Drop stale request. + CancelRequest { peer_id: PeerId }, /// Peer misbehaved. Disconnect, report it and cancel the block request to it. DropPeer(BadPeer), /// Import blocks. @@ -373,6 +373,7 @@ where max_parallel_downloads: u32, max_blocks_per_request: u32, metrics_registry: Option, + initial_peers: impl Iterator)>, ) -> Result { let mut sync = Self { client, @@ -405,6 +406,10 @@ where }; sync.reset_sync_start_point()?; + initial_peers.for_each(|(peer_id, best_hash, best_number)| { + sync.add_peer(peer_id, best_hash, best_number); + }); + Ok(sync) } @@ -1312,34 +1317,35 @@ where ); let old_peers = std::mem::take(&mut self.peers); - old_peers.into_iter().for_each(|(peer_id, mut p)| { - // peers that were downloading justifications - // should be kept in that state. - if let PeerSyncState::DownloadingJustification(_) = p.state { - // We make sure our commmon number is at least something we have. - trace!( - target: LOG_TARGET, - "Keeping peer {} after restart, updating common number from={} => to={} (our best).", - peer_id, - p.common_number, - self.best_queued_number, - ); - p.common_number = self.best_queued_number; - self.peers.insert(peer_id, p); - return + old_peers.into_iter().for_each(|(peer_id, mut peer_sync)| { + match peer_sync.state { + PeerSyncState::Available => { + self.add_peer(peer_id, peer_sync.best_hash, peer_sync.best_number); + }, + PeerSyncState::AncestorSearch { .. } | + PeerSyncState::DownloadingNew(_) | + PeerSyncState::DownloadingStale(_) | + PeerSyncState::DownloadingGap(_) | + PeerSyncState::DownloadingState => { + // Cancel a request first, as `add_peer` may generate a new request. + self.actions.push(ChainSyncAction::CancelRequest { peer_id }); + self.add_peer(peer_id, peer_sync.best_hash, peer_sync.best_number); + }, + PeerSyncState::DownloadingJustification(_) => { + // Peers that were downloading justifications + // should be kept in that state. + // We make sure our commmon number is at least something we have. + trace!( + target: LOG_TARGET, + "Keeping peer {} after restart, updating common number from={} => to={} (our best).", + peer_id, + peer_sync.common_number, + self.best_queued_number, + ); + peer_sync.common_number = self.best_queued_number; + self.peers.insert(peer_id, peer_sync); + }, } - - // handle peers that were in other states. - let action = match self.add_peer_inner(peer_id, p.best_hash, p.best_number) { - // since the request is not a justification, remove it from pending responses - Ok(None) => ChainSyncAction::CancelBlockRequest { peer_id }, - // update the request if the new one is available - Ok(Some(request)) => ChainSyncAction::SendBlockRequest { peer_id, request }, - // this implies that we need to drop pending response from the peer - Err(bad_peer) => ChainSyncAction::DropPeer(bad_peer), - }; - - self.actions.push(action); }); } diff --git a/substrate/client/network/sync/src/strategy/chain_sync/test.rs b/substrate/client/network/sync/src/strategy/chain_sync/test.rs index c89096bc6c9..127b6862f0e 100644 --- a/substrate/client/network/sync/src/strategy/chain_sync/test.rs +++ b/substrate/client/network/sync/src/strategy/chain_sync/test.rs @@ -38,7 +38,9 @@ fn processes_empty_response_on_justification_request_for_unknown_block() { let client = Arc::new(TestClientBuilder::new().build()); let peer_id = PeerId::random(); - let mut sync = ChainSync::new(ChainSyncMode::Full, client.clone(), 1, 64, None).unwrap(); + let mut sync = + ChainSync::new(ChainSyncMode::Full, client.clone(), 1, 64, None, std::iter::empty()) + .unwrap(); let (a1_hash, a1_number) = { let a1 = BlockBuilderBuilder::new(&*client) @@ -91,7 +93,11 @@ fn processes_empty_response_on_justification_request_for_unknown_block() { fn restart_doesnt_affect_peers_downloading_finality_data() { let mut client = Arc::new(TestClientBuilder::new().build()); - let mut sync = ChainSync::new(ChainSyncMode::Full, client.clone(), 1, 64, None).unwrap(); + // we request max 8 blocks to always initiate block requests to both peers for the test to be + // deterministic + let mut sync = + ChainSync::new(ChainSyncMode::Full, client.clone(), 1, 8, None, std::iter::empty()) + .unwrap(); let peer_id1 = PeerId::random(); let peer_id2 = PeerId::random(); @@ -122,10 +128,13 @@ fn restart_doesnt_affect_peers_downloading_finality_data() { // we wil send block requests to these peers // for these blocks we don't know about - assert!(sync - .block_requests() - .into_iter() - .all(|(p, _)| { p == peer_id1 || p == peer_id2 })); + let actions = sync.actions().collect::>(); + assert_eq!(actions.len(), 2); + assert!(actions.iter().all(|action| match action { + ChainSyncAction::SendBlockRequest { peer_id, .. } => + peer_id == &peer_id1 || peer_id == &peer_id2, + _ => false, + })); // add a new peer at a known block sync.add_peer(peer_id3, b1_hash, b1_number); @@ -146,22 +155,29 @@ fn restart_doesnt_affect_peers_downloading_finality_data() { PeerSyncState::DownloadingJustification(b1_hash), ); - // clear old actions + // drop old actions let _ = sync.take_actions(); // we restart the sync state sync.restart(); - let actions = sync.take_actions().collect::>(); - // which should make us send out block requests to the first two peers - assert_eq!(actions.len(), 2); + // which should make us cancel and send out again block requests to the first two peers + let actions = sync.actions().collect::>(); + assert_eq!(actions.len(), 4); + let mut cancelled_first = HashSet::new(); assert!(actions.iter().all(|action| match action { - ChainSyncAction::SendBlockRequest { peer_id, .. } => - peer_id == &peer_id1 || peer_id == &peer_id2, + ChainSyncAction::CancelRequest { peer_id, .. } => { + cancelled_first.insert(peer_id); + peer_id == &peer_id1 || peer_id == &peer_id2 + }, + ChainSyncAction::SendBlockRequest { peer_id, .. } => { + assert!(cancelled_first.remove(peer_id)); + peer_id == &peer_id1 || peer_id == &peer_id2 + }, _ => false, })); - // peer 3 should be unaffected it was downloading finality data + // peer 3 should be unaffected as it was downloading finality data assert_eq!( sync.peers.get(&peer_id3).unwrap().state, PeerSyncState::DownloadingJustification(b1_hash), @@ -275,7 +291,9 @@ fn do_ancestor_search_when_common_block_to_best_qeued_gap_is_to_big() { let mut client = Arc::new(TestClientBuilder::new().build()); let info = client.info(); - let mut sync = ChainSync::new(ChainSyncMode::Full, client.clone(), 5, 64, None).unwrap(); + let mut sync = + ChainSync::new(ChainSyncMode::Full, client.clone(), 5, 64, None, std::iter::empty()) + .unwrap(); let peer_id1 = PeerId::random(); let peer_id2 = PeerId::random(); @@ -421,7 +439,9 @@ fn can_sync_huge_fork() { let info = client.info(); - let mut sync = ChainSync::new(ChainSyncMode::Full, client.clone(), 5, 64, None).unwrap(); + let mut sync = + ChainSync::new(ChainSyncMode::Full, client.clone(), 5, 64, None, std::iter::empty()) + .unwrap(); let finalized_block = blocks[MAX_BLOCKS_TO_LOOK_BACKWARDS as usize * 2 - 1].clone(); let just = (*b"TEST", Vec::new()); @@ -554,7 +574,9 @@ fn syncs_fork_without_duplicate_requests() { let info = client.info(); - let mut sync = ChainSync::new(ChainSyncMode::Full, client.clone(), 5, 64, None).unwrap(); + let mut sync = + ChainSync::new(ChainSyncMode::Full, client.clone(), 5, 64, None, std::iter::empty()) + .unwrap(); let finalized_block = blocks[MAX_BLOCKS_TO_LOOK_BACKWARDS as usize * 2 - 1].clone(); let just = (*b"TEST", Vec::new()); @@ -689,7 +711,9 @@ fn removes_target_fork_on_disconnect() { let mut client = Arc::new(TestClientBuilder::new().build()); let blocks = (0..3).map(|_| build_block(&mut client, None, false)).collect::>(); - let mut sync = ChainSync::new(ChainSyncMode::Full, client.clone(), 1, 64, None).unwrap(); + let mut sync = + ChainSync::new(ChainSyncMode::Full, client.clone(), 1, 64, None, std::iter::empty()) + .unwrap(); let peer_id1 = PeerId::random(); let common_block = blocks[1].clone(); @@ -714,7 +738,9 @@ fn can_import_response_with_missing_blocks() { let empty_client = Arc::new(TestClientBuilder::new().build()); - let mut sync = ChainSync::new(ChainSyncMode::Full, empty_client.clone(), 1, 64, None).unwrap(); + let mut sync = + ChainSync::new(ChainSyncMode::Full, empty_client.clone(), 1, 64, None, std::iter::empty()) + .unwrap(); let peer_id1 = PeerId::random(); let best_block = blocks[3].clone(); @@ -745,7 +771,9 @@ fn ancestor_search_repeat() { #[test] fn sync_restart_removes_block_but_not_justification_requests() { let mut client = Arc::new(TestClientBuilder::new().build()); - let mut sync = ChainSync::new(ChainSyncMode::Full, client.clone(), 1, 64, None).unwrap(); + let mut sync = + ChainSync::new(ChainSyncMode::Full, client.clone(), 1, 64, None, std::iter::empty()) + .unwrap(); let peers = vec![PeerId::random(), PeerId::random()]; @@ -813,7 +841,7 @@ fn sync_restart_removes_block_but_not_justification_requests() { let actions = sync.take_actions().collect::>(); for action in actions.iter() { match action { - ChainSyncAction::CancelBlockRequest { peer_id } => { + ChainSyncAction::CancelRequest { peer_id } => { pending_responses.remove(&peer_id); }, ChainSyncAction::SendBlockRequest { peer_id, .. } => { @@ -887,7 +915,9 @@ fn request_across_forks() { fork_blocks }; - let mut sync = ChainSync::new(ChainSyncMode::Full, client.clone(), 5, 64, None).unwrap(); + let mut sync = + ChainSync::new(ChainSyncMode::Full, client.clone(), 5, 64, None, std::iter::empty()) + .unwrap(); // Add the peers, all at the common ancestor 100. let common_block = blocks.last().unwrap(); diff --git a/substrate/client/network/sync/src/strategy/state.rs b/substrate/client/network/sync/src/strategy/state.rs index ae3f7b60055..12d36ff9e01 100644 --- a/substrate/client/network/sync/src/strategy/state.rs +++ b/substrate/client/network/sync/src/strategy/state.rs @@ -330,11 +330,6 @@ impl StateStrategy { } } - /// Get the number of peers known to syncing. - pub fn num_peers(&self) -> usize { - self.peers.len() - } - /// Get actions that should be performed by the owner on [`WarpSync`]'s behalf #[must_use] pub fn actions(&mut self) -> impl Iterator> { -- GitLab From aa68ea58f389c2aa4eefab4bf7bc7b787dd56580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 13 Feb 2024 21:07:51 +0100 Subject: [PATCH 056/133] Parachains-Aura: Only produce once per slot (#3308) Given how the block production is driven for Parachains right now, with the enabling of async backing we would produce two blocks per slot. Until we have a proper collator implementation, the "hack" is to prevent the production of multiple blocks per slot. Closes: https://github.com/paritytech/polkadot-sdk/issues/3282 --- cumulus/client/consensus/aura/src/collator.rs | 8 ++++++- .../consensus/aura/src/collators/basic.rs | 16 +++++++++++++ cumulus/client/consensus/aura/src/lib.rs | 24 ++++++++++++++++++- prdoc/pr_3308.prdoc | 14 +++++++++++ 4 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 prdoc/pr_3308.prdoc diff --git a/cumulus/client/consensus/aura/src/collator.rs b/cumulus/client/consensus/aura/src/collator.rs index db0799235bc..5b7669c88f4 100644 --- a/cumulus/client/consensus/aura/src/collator.rs +++ b/cumulus/client/consensus/aura/src/collator.rs @@ -258,6 +258,7 @@ where pub struct SlotClaim { author_pub: Pub, pre_digest: DigestItem, + slot: Slot, timestamp: Timestamp, } @@ -272,7 +273,7 @@ impl SlotClaim { P::Public: Codec, P::Signature: Codec, { - SlotClaim { author_pub, timestamp, pre_digest: aura_internal::pre_digest::

(slot) } + SlotClaim { author_pub, timestamp, pre_digest: aura_internal::pre_digest::

(slot), slot } } /// Get the author's public key. @@ -285,6 +286,11 @@ impl SlotClaim { &self.pre_digest } + /// Get the slot assigned to this claim. + pub fn slot(&self) -> Slot { + self.slot + } + /// Get the timestamp corresponding to the relay-chain slot this claim was /// generated against. pub fn timestamp(&self) -> Timestamp { diff --git a/cumulus/client/consensus/aura/src/collators/basic.rs b/cumulus/client/consensus/aura/src/collators/basic.rs index 78f6b726aff..8740b06005d 100644 --- a/cumulus/client/consensus/aura/src/collators/basic.rs +++ b/cumulus/client/consensus/aura/src/collators/basic.rs @@ -141,6 +141,8 @@ where collator_util::Collator::::new(params) }; + let mut last_processed_slot = 0; + while let Some(request) = collation_requests.next().await { macro_rules! reject_with_error { ($err:expr) => {{ @@ -192,6 +194,18 @@ where Err(e) => reject_with_error!(e), }; + // With async backing this function will be called every relay chain block. + // + // Most parachains currently run with 12 seconds slots and thus, they would try to + // produce multiple blocks per slot which very likely would fail on chain. Thus, we have + // this "hack" to only produce on block per slot. + // + // With https://github.com/paritytech/polkadot-sdk/issues/3168 this implementation will be + // obsolete and also the underlying issue will be fixed. + if last_processed_slot >= *claim.slot() { + continue + } + let (parachain_inherent_data, other_inherent_data) = try_request!( collator .create_inherent_data( @@ -228,6 +242,8 @@ where request.complete(None); tracing::debug!(target: crate::LOG_TARGET, "No block proposal"); } + + last_processed_slot = *claim.slot(); } } } diff --git a/cumulus/client/consensus/aura/src/lib.rs b/cumulus/client/consensus/aura/src/lib.rs index 6ededa7a92c..8e4bc658e44 100644 --- a/cumulus/client/consensus/aura/src/lib.rs +++ b/cumulus/client/consensus/aura/src/lib.rs @@ -42,7 +42,14 @@ use sp_core::crypto::Pair; use sp_inherents::CreateInherentDataProviders; use sp_keystore::KeystorePtr; use sp_runtime::traits::{Block as BlockT, Header as HeaderT, Member, NumberFor}; -use std::{convert::TryFrom, marker::PhantomData, sync::Arc}; +use std::{ + convert::TryFrom, + marker::PhantomData, + sync::{ + atomic::{AtomicU64, Ordering}, + Arc, + }, +}; mod import_queue; @@ -61,6 +68,7 @@ pub struct AuraConsensus { create_inherent_data_providers: Arc, aura_worker: Arc>, slot_duration: SlotDuration, + last_slot_processed: Arc, _phantom: PhantomData, } @@ -70,6 +78,7 @@ impl Clone for AuraConsensus { create_inherent_data_providers: self.create_inherent_data_providers.clone(), aura_worker: self.aura_worker.clone(), slot_duration: self.slot_duration, + last_slot_processed: self.last_slot_processed.clone(), _phantom: PhantomData, } } @@ -156,6 +165,7 @@ where Box::new(AuraConsensus { create_inherent_data_providers: Arc::new(create_inherent_data_providers), aura_worker: Arc::new(Mutex::new(worker)), + last_slot_processed: Default::default(), slot_duration, _phantom: PhantomData, }) @@ -221,6 +231,18 @@ where Some((validation_data.max_pov_size / 2) as usize), ); + // With async backing this function will be called every relay chain block. + // + // Most parachains currently run with 12 seconds slots and thus, they would try to produce + // multiple blocks per slot which very likely would fail on chain. Thus, we have this "hack" + // to only produce on block per slot. + // + // With https://github.com/paritytech/polkadot-sdk/issues/3168 this implementation will be + // obsolete and also the underlying issue will be fixed. + if self.last_slot_processed.fetch_max(*info.slot, Ordering::Relaxed) >= *info.slot { + return None + } + let res = self.aura_worker.lock().await.on_slot(info).await?; Some(ParachainCandidate { block: res.block, proof: res.storage_proof }) diff --git a/prdoc/pr_3308.prdoc b/prdoc/pr_3308.prdoc new file mode 100644 index 00000000000..611461e25ff --- /dev/null +++ b/prdoc/pr_3308.prdoc @@ -0,0 +1,14 @@ +title: Parachains-Aura: Only produce once per slot + +doc: + - audience: Node Dev + description: | + With the introduction of asynchronous backing the relay chain allows parachain to include blocks every 6 seconds. + The Cumulus Aura implementations, besides the lookahead collator, are building blocks when there is a free slot for + the parachain in the relay chain. Most parachains are still running with a 12s slot duration and not allowing + to build multiple blocks per slot. But, the block production logic will be triggered every 6s, resulting in error + logs like: "no space left for the block in the unincluded segment". This is solved by ensuring that we don't build + multiple blocks per slot. + +crates: + - name: "cumulus-client-consensus-aura" -- GitLab From 1b66bb51b52d3e6cacf155bd3e038b6ef44ac5da Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Wed, 14 Feb 2024 12:08:17 +0100 Subject: [PATCH 057/133] Bridges: add test 0002 to CI (#3310) Bridges: add test 0002 to CI --- .gitlab/pipeline/zombienet/bridges.yml | 7 ++++ .../environments/rococo-westend/spawn.sh | 17 +++++---- .../rococo-westend/start_relayer.sh | 23 ++++++++++++ ...only-mandatory-headers-synced-when-idle.js | 6 ++-- bridges/zombienet/helpers/utils.js | 2 +- bridges/zombienet/run-new-test.sh | 4 +-- .../tests/0001-asset-transfer/run.sh | 2 +- .../rococo-to-westend.zndsl | 8 +++++ .../run.sh | 35 +++++++++++++++++++ .../westend-to-rococo.zndsl | 7 ++++ 10 files changed, 95 insertions(+), 16 deletions(-) create mode 100755 bridges/zombienet/environments/rococo-westend/start_relayer.sh create mode 100644 bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl create mode 100755 bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle/run.sh create mode 100644 bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl diff --git a/.gitlab/pipeline/zombienet/bridges.yml b/.gitlab/pipeline/zombienet/bridges.yml index f2de526c870..16cabad6cca 100644 --- a/.gitlab/pipeline/zombienet/bridges.yml +++ b/.gitlab/pipeline/zombienet/bridges.yml @@ -53,3 +53,10 @@ zombienet-bridges-0001-asset-transfer-works: script: - /home/nonroot/bridges-polkadot-sdk/bridges/zombienet/run-new-test.sh 0001-asset-transfer --docker - echo "Done" + +zombienet-bridges-0002-mandatory-headers-synced-while-idle: + extends: + - .zombienet-bridges-common + script: + - /home/nonroot/bridges-polkadot-sdk/bridges/zombienet/run-new-test.sh 0002-mandatory-headers-synced-while-idle --docker + - echo "Done" diff --git a/bridges/zombienet/environments/rococo-westend/spawn.sh b/bridges/zombienet/environments/rococo-westend/spawn.sh index dbdc58d5fa1..550eee04bd6 100755 --- a/bridges/zombienet/environments/rococo-westend/spawn.sh +++ b/bridges/zombienet/environments/rococo-westend/spawn.sh @@ -4,11 +4,11 @@ set -e trap "trap - SIGTERM && kill -9 -$$" SIGINT SIGTERM EXIT -source "${BASH_SOURCE%/*}/../../utils/common.sh" source "${BASH_SOURCE%/*}/../../utils/zombienet.sh" # whether to init the chains (open HRMP channels, set XCM version, create reserve assets, etc) init=0 +start_relayer=0 while [ $# -ne 0 ] do arg="$1" @@ -16,6 +16,9 @@ do --init) init=1 ;; + --start-relayer) + start_relayer=1 + ;; esac shift done @@ -55,17 +58,13 @@ if [[ $init -eq 1 ]]; then run_zndsl ${BASH_SOURCE%/*}/westend-init.zndsl $westend_dir fi -relay_log=$logs_dir/relay.log -echo -e "Starting rococo-westend relay. Logs available at: $relay_log\n" -start_background_process "$helper_script run-relay" $relay_log relay_pid +if [[ $start_relayer -eq 1 ]]; then + ${BASH_SOURCE%/*}/start_relayer.sh $rococo_dir $westend_dir relayer_pid +fi -run_zndsl ${BASH_SOURCE%/*}/rococo.zndsl $rococo_dir echo $rococo_dir > $TEST_DIR/rococo.env -echo - -run_zndsl ${BASH_SOURCE%/*}/westend.zndsl $westend_dir echo $westend_dir > $TEST_DIR/westend.env echo -wait -n $rococo_pid $westend_pid $relay_pid +wait -n $rococo_pid $westend_pid $relayer_pid kill -9 -$$ diff --git a/bridges/zombienet/environments/rococo-westend/start_relayer.sh b/bridges/zombienet/environments/rococo-westend/start_relayer.sh new file mode 100755 index 00000000000..c57d4f1a437 --- /dev/null +++ b/bridges/zombienet/environments/rococo-westend/start_relayer.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -e + +source "${BASH_SOURCE%/*}/../../utils/common.sh" +source "${BASH_SOURCE%/*}/../../utils/zombienet.sh" + +rococo_dir=$1 +westend_dir=$2 +__relayer_pid=$3 + +logs_dir=$TEST_DIR/logs +helper_script="${BASH_SOURCE%/*}/helper.sh" + +relayer_log=$logs_dir/relayer.log +echo -e "Starting rococo-westend relayer. Logs available at: $relayer_log\n" +start_background_process "$helper_script run-relay" $relayer_log relayer_pid + +run_zndsl ${BASH_SOURCE%/*}/rococo.zndsl $rococo_dir +run_zndsl ${BASH_SOURCE%/*}/westend.zndsl $westend_dir + +eval $__relayer_pid="'$relayer_pid'" + diff --git a/bridges/zombienet/helpers/only-mandatory-headers-synced-when-idle.js b/bridges/zombienet/helpers/only-mandatory-headers-synced-when-idle.js index 3a3432cfaf3..979179245eb 100644 --- a/bridges/zombienet/helpers/only-mandatory-headers-synced-when-idle.js +++ b/bridges/zombienet/helpers/only-mandatory-headers-synced-when-idle.js @@ -10,7 +10,7 @@ async function run(nodeName, networkInfo, args) { // start listening to new blocks let totalGrandpaHeaders = 0; - let totalParachainHeaders = 0; + let initialParachainHeaderImported = false; api.rpc.chain.subscribeNewHeads(async function (header) { const apiAtParent = await api.at(header.parentHash); const apiAtCurrent = await api.at(header.hash); @@ -22,7 +22,7 @@ async function run(nodeName, networkInfo, args) { apiAtCurrent, currentEvents, ); - totalParachainHeaders += await utils.ensureOnlyInitialParachainHeaderImported( + initialParachainHeaderImported = await utils.ensureOnlyInitialParachainHeaderImported( bridgedChain, apiAtParent, apiAtCurrent, @@ -36,7 +36,7 @@ async function run(nodeName, networkInfo, args) { if (totalGrandpaHeaders == 0) { throw new Error("No bridged relay chain headers imported"); } - if (totalParachainHeaders == 0) { + if (!initialParachainHeaderImported) { throw new Error("No bridged parachain headers imported"); } } diff --git a/bridges/zombienet/helpers/utils.js b/bridges/zombienet/helpers/utils.js index 5a5542b56df..f6e9f5623b4 100644 --- a/bridges/zombienet/helpers/utils.js +++ b/bridges/zombienet/helpers/utils.js @@ -98,6 +98,6 @@ module.exports = { throw new Error("Unexpected parachain header import: " + newParachainHeaders + " / " + maxNewParachainHeaders); } - return newParachainHeaders; + return hasBestBridgedParachainHeader; }, } diff --git a/bridges/zombienet/run-new-test.sh b/bridges/zombienet/run-new-test.sh index 3bfcb6d83e9..2ed2a412b8a 100755 --- a/bridges/zombienet/run-new-test.sh +++ b/bridges/zombienet/run-new-test.sh @@ -2,6 +2,8 @@ set -e +trap 'kill -9 -$$ || echo "Environment already teared down"' SIGINT SIGTERM EXIT + test=$1 shift @@ -43,5 +45,3 @@ export TEST_DIR=`mktemp -d /tmp/bridges-tests-run-XXXXX` echo -e "Test folder: $TEST_DIR\n" ${BASH_SOURCE%/*}/tests/$test/run.sh - -kill -9 -$$ || echo "Environment already teared down" \ No newline at end of file diff --git a/bridges/zombienet/tests/0001-asset-transfer/run.sh b/bridges/zombienet/tests/0001-asset-transfer/run.sh index 07951a0e321..8a053ee7209 100755 --- a/bridges/zombienet/tests/0001-asset-transfer/run.sh +++ b/bridges/zombienet/tests/0001-asset-transfer/run.sh @@ -5,7 +5,7 @@ set -e source "${BASH_SOURCE%/*}/../../utils/common.sh" source "${BASH_SOURCE%/*}/../../utils/zombienet.sh" -${BASH_SOURCE%/*}/../../environments/rococo-westend/spawn.sh --init & +${BASH_SOURCE%/*}/../../environments/rococo-westend/spawn.sh --init --start-relayer & env_pid=$! ensure_process_file $env_pid $TEST_DIR/rococo.env 400 diff --git a/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl b/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl new file mode 100644 index 00000000000..d4283b36129 --- /dev/null +++ b/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl @@ -0,0 +1,8 @@ +Description: While relayer is idle, we only sync mandatory Rococo (and a single Rococo BH) headers to Westend BH. +Network: ../../../../cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml +Creds: config + +# ensure that relayer is only syncing mandatory headers while idle. This includes both headers that were +# generated while relay was offline and those in the next 100 seconds while script is active. +bridge-hub-westend-collator1: js-script ../../helpers/only-mandatory-headers-synced-when-idle.js with "300,rococo-at-westend" within 600 seconds + diff --git a/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle/run.sh b/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle/run.sh new file mode 100755 index 00000000000..423f4a1bcc0 --- /dev/null +++ b/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle/run.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +set -e + +source "${BASH_SOURCE%/*}/../../utils/common.sh" +source "${BASH_SOURCE%/*}/../../utils/zombienet.sh" + +# We use `--relayer-delay` in order to sleep some time before starting relayer. +# We want to sleep for at least 1 session, which is expected to be 60 seconds for test environment. +${BASH_SOURCE%/*}/../../environments/rococo-westend/spawn.sh & +env_pid=$! + +ensure_process_file $env_pid $TEST_DIR/rococo.env 400 +rococo_dir=`cat $TEST_DIR/rococo.env` +echo + +ensure_process_file $env_pid $TEST_DIR/westend.env 180 +westend_dir=`cat $TEST_DIR/westend.env` +echo + +# Sleep for some time before starting the relayer. We want to sleep for at least 1 session, +# which is expected to be 60 seconds for the test environment. +echo -e "Sleeping 90s before starting relayer ...\n" +sleep 90 +${BASH_SOURCE%/*}/../../environments/rococo-westend/start_relayer.sh $rococo_dir $westend_dir relayer_pid + +# Sometimes the relayer syncs multiple parachain heads in the begining leading to test failures. +# See issue: https://github.com/paritytech/parity-bridges-common/issues/2838. +# TODO: Remove this sleep after the issue is fixed. +echo -e "Sleeping 180s before runing the tests ...\n" +sleep 180 + +run_zndsl ${BASH_SOURCE%/*}/rococo-to-westend.zndsl $westend_dir +run_zndsl ${BASH_SOURCE%/*}/westend-to-rococo.zndsl $rococo_dir + diff --git a/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl b/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl new file mode 100644 index 00000000000..1ea4c92a345 --- /dev/null +++ b/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl @@ -0,0 +1,7 @@ +Description: While relayer is idle, we only sync mandatory Westend (and a single Westend BH) headers to Rococo BH. +Network: ../../../../cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml +Creds: config + +# ensure that relayer is only syncing mandatory headers while idle. This includes both headers that were +# generated while relay was offline and those in the next 100 seconds while script is active. +bridge-hub-rococo-collator1: js-script ../../helpers/only-mandatory-headers-synced-when-idle.js with "300,westend-at-rococo" within 600 seconds -- GitLab From 15103d7a86c5966938c217c8a4665fef030a68d7 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Wed, 14 Feb 2024 12:27:06 +0100 Subject: [PATCH 058/133] Remove forgotten dbg! in a snowbridge test (#3322) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Forgotten in #3287 😅 --- bridges/snowbridge/runtime/test-common/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/bridges/snowbridge/runtime/test-common/src/lib.rs b/bridges/snowbridge/runtime/test-common/src/lib.rs index 18ba2aa1559..c9bbce98e57 100644 --- a/bridges/snowbridge/runtime/test-common/src/lib.rs +++ b/bridges/snowbridge/runtime/test-common/src/lib.rs @@ -188,7 +188,6 @@ pub fn send_transfer_token_message_success( let channel_id: ChannelId = origin.into(); let nonce = snowbridge_pallet_outbound_queue::Nonce::::try_get(channel_id); - dbg!(&nonce); assert_ok!(nonce); assert_eq!(nonce.unwrap(), 1); -- GitLab From 5a50b8b8a3a40a4a9b46b0fc420cff5b3be64cd3 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Wed, 14 Feb 2024 15:05:25 +0100 Subject: [PATCH 059/133] Add XCM documentation to the sdk docs (#2628) I added a small documentation for XCM in the context of the polkadot SDK --------- Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> Co-authored-by: command-bot <> Co-authored-by: Keith Yeung --- Cargo.lock | 1 + docs/sdk/Cargo.toml | 3 ++ docs/sdk/src/polkadot_sdk/xcm.rs | 72 ++++++++++++++++++++++++++++++-- 3 files changed, 73 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 66b319b034b..47ef107799d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13312,6 +13312,7 @@ dependencies = [ "staging-chain-spec-builder", "staging-node-cli", "staging-parachain-info", + "staging-xcm", "subkey", "substrate-wasm-builder", ] diff --git a/docs/sdk/Cargo.toml b/docs/sdk/Cargo.toml index c998a601486..19c0f729f4c 100644 --- a/docs/sdk/Cargo.toml +++ b/docs/sdk/Cargo.toml @@ -61,6 +61,9 @@ sp-core = { path = "../../substrate/primitives/core" } sp-keyring = { path = "../../substrate/primitives/keyring" } sp-runtime = { path = "../../substrate/primitives/runtime" } +# XCM +xcm = { package = "staging-xcm", path = "../../polkadot/xcm" } + [dev-dependencies] parity-scale-codec = "3.6.5" scale-info = "2.9.0" diff --git a/docs/sdk/src/polkadot_sdk/xcm.rs b/docs/sdk/src/polkadot_sdk/xcm.rs index fd4d7f62aa7..5dcdc9e1de0 100644 --- a/docs/sdk/src/polkadot_sdk/xcm.rs +++ b/docs/sdk/src/polkadot_sdk/xcm.rs @@ -1,5 +1,71 @@ //! # XCM //! -//! @KiChjang @franciscoaguirre -//! TODO: RFCs, xcm-spec, the future of the repo, minimal example perhaps, forward to where actual -//! docs are hosted. +//! XCM, or Cross-Consensus Messaging, is a **language** to communicate **intentions** between +//! **consensus systems**. +//! +//! ## Overview +//! +//! XCM is a standard, whose specification lives in the [xcm format repo](https://github.com/paritytech/xcm-format). +//! It's agnostic both in programming language and blockchain platform, which means it could be used +//! in Rust in Polkadot, or in Go or C++ in any other platform like Cosmos or Ethereum. +//! +//! It enables different consensus systems to communicate with each other in an expressive manner. +//! Consensus systems include blockchains, smart contracts, and any other state machine that +//! achieves consensus in some way. +//! +//! XCM is executed on a virtual machine called the XCVM. +//! Scripts can be written with the XCM language, which are often called XCMs, messages or XCM +//! programs. Each program is a series of instructions, which get executed one after the other by +//! the virtual machine. These instructions aim to encompass all major things users typically do in +//! consensus systems. There are instructions on asset transferring, teleporting, locking, among +//! others. New instructions are added and changes to the XCVM are made via the [RFC process](https://github.com/paritytech/xcm-format/blob/master/proposals/0032-process.md). +//! +//! ## In Polkadot SDK +//! +//! The Polkadot SDK allows for easily deploying sovereign blockchains from scratch, all very +//! customizable. Dealing with many heterogeneous blockchains can be cumbersome. +//! XCM allows all these blockchains to communicate with an agreed-upon language. +//! As long as an implementation of the XCVM is implemented, the same XCM program can be executed in +//! all blockchains and perform the same task. +//! +//! ## Implementation +//! +//! A ready-to-use Rust implementation lives in the [polkadot-sdk repo](https://github.com/paritytech/polkadot-sdk/tree/master/polkadot/xcm), +//! but will be moved to its own repo in the future. +//! +//! Its main components are: +//! - `src`: the definition of the basic types and instructions +//! - [`xcm-executor`](https://paritytech.github.io/polkadot-sdk/master/staging_xcm_executor/struct.XcmExecutor.html): +//! an implementation of the virtual machine to execute instructions +//! - `pallet-xcm`: A FRAME pallet for interacting with the executor +//! - `xcm-builder`: a collection of types to configure the executor +//! - `xcm-simulator`: a playground for trying out different XCM programs and executor +//! configurations +//! +//! ## Example +//! +//! To perform the very usual operation of transferring assets, the following XCM program can be +//! used: +#![doc = docify::embed!("src/polkadot_sdk/xcm.rs", example_transfer)] +//! +//! ## Get started +//! +//! To learn how it works and to get started, go to the [XCM docs](https://paritytech.github.io/xcm-docs/). + +#[cfg(test)] +mod tests { + use xcm::latest::prelude::*; + + #[docify::export] + #[test] + fn example_transfer() { + let _transfer_program = Xcm::<()>(vec![ + WithdrawAsset((Here, 100u128).into()), + BuyExecution { fees: (Here, 100u128).into(), weight_limit: Unlimited }, + DepositAsset { + assets: All.into(), + beneficiary: AccountId32 { id: [0u8; 32].into(), network: None }.into(), + }, + ]); + } +} -- GitLab From 7ec692d11c5b0d749ad89a71df835dae4d3756ed Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Wed, 14 Feb 2024 16:41:07 +0200 Subject: [PATCH 060/133] chainHead: Error on duplicate unpin hashes (#3313) This PR addresses an issue where calling chainHead_unpin with duplicate hashes could lead to unintended side effects. This backports: https://github.com/paritytech/json-rpc-interface-spec/pull/135 While at it, have added a test to check that the global reference count is decremented only once on unpin. cc @paritytech/subxt-team --------- Signed-off-by: Alexandru Vasile Co-authored-by: Davide Galassi --- .../rpc-spec-v2/src/chain_head/chain_head.rs | 2 + .../rpc-spec-v2/src/chain_head/error.rs | 7 ++ .../src/chain_head/subscription/error.rs | 6 +- .../src/chain_head/subscription/inner.rs | 88 ++++++++++++++- .../rpc-spec-v2/src/chain_head/tests.rs | 102 ++++++++++++++++++ 5 files changed, 203 insertions(+), 2 deletions(-) diff --git a/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs b/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs index 0e207addcae..bcca1dc18e2 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs @@ -424,6 +424,8 @@ where // Block is not part of the subscription. Err(ChainHeadRpcError::InvalidBlock) }, + Err(SubscriptionManagementError::DuplicateHashes) => + Err(ChainHeadRpcError::InvalidDuplicateHashes), Err(_) => Err(ChainHeadRpcError::InvalidBlock), } } diff --git a/substrate/client/rpc-spec-v2/src/chain_head/error.rs b/substrate/client/rpc-spec-v2/src/chain_head/error.rs index bf290edb29e..8c50e445aa0 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/error.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/error.rs @@ -32,6 +32,9 @@ pub enum Error { /// Wait-for-continue event not generated. #[error("Wait for continue event was not generated for the subscription")] InvalidContinue, + /// Received duplicate hashes for the `chainHead_unpin` method. + #[error("Received duplicate hashes for the `chainHead_unpin` method")] + InvalidDuplicateHashes, /// Invalid parameter provided to the RPC method. #[error("Invalid parameter: {0}")] InvalidParam(String), @@ -49,6 +52,8 @@ pub mod rpc_spec_v2 { pub const INVALID_RUNTIME_CALL: i32 = -32802; /// Wait-for-continue event not generated. pub const INVALID_CONTINUE: i32 = -32803; + /// Received duplicate hashes for the `chainHead_unpin` method. + pub const INVALID_DUPLICATE_HASHES: i32 = -32804; } /// General purpose errors, as defined in @@ -71,6 +76,8 @@ impl From for ErrorObject<'static> { ErrorObject::owned(rpc_spec_v2::INVALID_RUNTIME_CALL, msg, None::<()>), Error::InvalidContinue => ErrorObject::owned(rpc_spec_v2::INVALID_CONTINUE, msg, None::<()>), + Error::InvalidDuplicateHashes => + ErrorObject::owned(rpc_spec_v2::INVALID_DUPLICATE_HASHES, msg, None::<()>), Error::InvalidParam(_) => ErrorObject::owned(json_rpc_spec::INVALID_PARAM_ERROR, msg, None::<()>), Error::InternalError(_) => diff --git a/substrate/client/rpc-spec-v2/src/chain_head/subscription/error.rs b/substrate/client/rpc-spec-v2/src/chain_head/subscription/error.rs index 38e8fd7384f..2c22e51ca4d 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/subscription/error.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/subscription/error.rs @@ -38,6 +38,9 @@ pub enum SubscriptionManagementError { /// The specified subscription ID is not present. #[error("Subscription is absent")] SubscriptionAbsent, + /// The unpin method was called with duplicate hashes. + #[error("Duplicate hashes")] + DuplicateHashes, /// Custom error. #[error("Subscription error {0}")] Custom(String), @@ -52,7 +55,8 @@ impl PartialEq for SubscriptionManagementError { (Self::Blockchain(_), Self::Blockchain(_)) | (Self::BlockHashAbsent, Self::BlockHashAbsent) | (Self::BlockHeaderAbsent, Self::BlockHeaderAbsent) | - (Self::SubscriptionAbsent, Self::SubscriptionAbsent) => true, + (Self::SubscriptionAbsent, Self::SubscriptionAbsent) | + (Self::DuplicateHashes, Self::DuplicateHashes) => true, (Self::Custom(lhs), Self::Custom(rhs)) => lhs == rhs, _ => false, } diff --git a/substrate/client/rpc-spec-v2/src/chain_head/subscription/inner.rs b/substrate/client/rpc-spec-v2/src/chain_head/subscription/inner.rs index 2b250f3dc2c..d2879679501 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/subscription/inner.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/subscription/inner.rs @@ -22,7 +22,7 @@ use sc_client_api::Backend; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; use sp_runtime::traits::Block as BlockT; use std::{ - collections::{hash_map::Entry, HashMap}, + collections::{hash_map::Entry, HashMap, HashSet}, sync::{atomic::AtomicBool, Arc}, time::{Duration, Instant}, }; @@ -750,11 +750,27 @@ impl> SubscriptionsInner { } } + /// Ensure the provided hashes are unique. + fn ensure_hash_uniqueness( + hashes: impl IntoIterator + Clone, + ) -> Result<(), SubscriptionManagementError> { + let mut set = HashSet::new(); + hashes.into_iter().try_for_each(|hash| { + if !set.insert(hash) { + Err(SubscriptionManagementError::DuplicateHashes) + } else { + Ok(()) + } + }) + } + pub fn unpin_blocks( &mut self, sub_id: &str, hashes: impl IntoIterator + Clone, ) -> Result<(), SubscriptionManagementError> { + Self::ensure_hash_uniqueness(hashes.clone())?; + let Some(sub) = self.subs.get_mut(sub_id) else { return Err(SubscriptionManagementError::SubscriptionAbsent) }; @@ -985,6 +1001,76 @@ mod tests { assert!(block_state.is_none()); } + #[test] + fn unpin_duplicate_hashes() { + let (backend, mut client) = init_backend(); + let block = BlockBuilderBuilder::new(&*client) + .on_parent_block(client.chain_info().genesis_hash) + .with_parent_block_number(0) + .build() + .unwrap() + .build() + .unwrap() + .block; + let hash_1 = block.header.hash(); + futures::executor::block_on(client.import(BlockOrigin::Own, block.clone())).unwrap(); + let block = BlockBuilderBuilder::new(&*client) + .on_parent_block(hash_1) + .with_parent_block_number(1) + .build() + .unwrap() + .build() + .unwrap() + .block; + let hash_2 = block.header.hash(); + futures::executor::block_on(client.import(BlockOrigin::Own, block.clone())).unwrap(); + let block = BlockBuilderBuilder::new(&*client) + .on_parent_block(hash_2) + .with_parent_block_number(2) + .build() + .unwrap() + .build() + .unwrap() + .block; + let hash_3 = block.header.hash(); + futures::executor::block_on(client.import(BlockOrigin::Own, block.clone())).unwrap(); + + let mut subs = + SubscriptionsInner::new(10, Duration::from_secs(10), MAX_OPERATIONS_PER_SUB, backend); + let id_1 = "abc".to_string(); + let id_2 = "abcd".to_string(); + + // Pin all blocks for the first subscription. + let _stop = subs.insert_subscription(id_1.clone(), true).unwrap(); + assert_eq!(subs.pin_block(&id_1, hash_1).unwrap(), true); + assert_eq!(subs.pin_block(&id_1, hash_2).unwrap(), true); + assert_eq!(subs.pin_block(&id_1, hash_3).unwrap(), true); + + // Pin only block 2 for the second subscription. + let _stop = subs.insert_subscription(id_2.clone(), true).unwrap(); + assert_eq!(subs.pin_block(&id_2, hash_2).unwrap(), true); + + // Check reference count. + assert_eq!(*subs.global_blocks.get(&hash_1).unwrap(), 1); + assert_eq!(*subs.global_blocks.get(&hash_2).unwrap(), 2); + assert_eq!(*subs.global_blocks.get(&hash_3).unwrap(), 1); + + // Unpin the same block twice. + let err = subs.unpin_blocks(&id_1, vec![hash_1, hash_1, hash_2, hash_2]).unwrap_err(); + assert_eq!(err, SubscriptionManagementError::DuplicateHashes); + + // Check reference count must be unaltered. + assert_eq!(*subs.global_blocks.get(&hash_1).unwrap(), 1); + assert_eq!(*subs.global_blocks.get(&hash_2).unwrap(), 2); + assert_eq!(*subs.global_blocks.get(&hash_3).unwrap(), 1); + + // Unpin the blocks correctly. + subs.unpin_blocks(&id_1, vec![hash_1, hash_2]).unwrap(); + assert_eq!(subs.global_blocks.get(&hash_1), None); + assert_eq!(*subs.global_blocks.get(&hash_2).unwrap(), 1); + assert_eq!(*subs.global_blocks.get(&hash_3).unwrap(), 1); + } + #[test] fn subscription_lock_block() { let builder = TestClientBuilder::new(); diff --git a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs index 955a361e3ea..ccb928262e4 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs @@ -1617,6 +1617,108 @@ async fn follow_with_unpin() { assert!(sub.next::>().await.is_none()); } +#[tokio::test] +async fn unpin_duplicate_hashes() { + let builder = TestClientBuilder::new(); + let backend = builder.backend(); + let mut client = Arc::new(builder.build()); + + let api = ChainHead::new( + client.clone(), + backend, + Arc::new(TaskExecutor::default()), + ChainHeadConfig { + global_max_pinned_blocks: 3, + subscription_max_pinned_duration: Duration::from_secs(MAX_PINNED_SECS), + subscription_max_ongoing_operations: MAX_OPERATIONS, + operation_max_storage_items: MAX_PAGINATION_LIMIT, + }, + ) + .into_rpc(); + + let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [false]).await.unwrap(); + let sub_id = sub.subscription_id(); + let sub_id = serde_json::to_string(&sub_id).unwrap(); + + let block = BlockBuilderBuilder::new(&*client) + .on_parent_block(client.chain_info().genesis_hash) + .with_parent_block_number(0) + .build() + .unwrap() + .build() + .unwrap() + .block; + let block_hash = format!("{:?}", block.header.hash()); + client.import(BlockOrigin::Own, block.clone()).await.unwrap(); + + // Ensure the imported block is propagated and pinned for this subscription. + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::Initialized(_) + ); + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::NewBlock(_) + ); + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::BestBlockChanged(_) + ); + + // Try to unpin duplicate hashes. + let err = api + .call::<_, serde_json::Value>( + "chainHead_unstable_unpin", + rpc_params![&sub_id, vec![&block_hash, &block_hash]], + ) + .await + .unwrap_err(); + assert_matches!(err, + Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_DUPLICATE_HASHES && err.message() == "Received duplicate hashes for the `chainHead_unpin` method" + ); + + // Block tree: + // finalized_block -> block -> block2 + let block2 = BlockBuilderBuilder::new(&*client) + .on_parent_block(block.hash()) + .with_parent_block_number(1) + .build() + .unwrap() + .build() + .unwrap() + .block; + let block_hash_2 = format!("{:?}", block2.header.hash()); + client.import(BlockOrigin::Own, block2.clone()).await.unwrap(); + + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::NewBlock(_) + ); + + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::BestBlockChanged(_) + ); + + // Try to unpin duplicate hashes. + let err = api + .call::<_, serde_json::Value>( + "chainHead_unstable_unpin", + rpc_params![&sub_id, vec![&block_hash, &block_hash_2, &block_hash]], + ) + .await + .unwrap_err(); + assert_matches!(err, + Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_DUPLICATE_HASHES && err.message() == "Received duplicate hashes for the `chainHead_unpin` method" + ); + + // Can unpin blocks. + let _res: () = api + .call("chainHead_unstable_unpin", rpc_params![&sub_id, vec![&block_hash, &block_hash_2]]) + .await + .unwrap(); +} + #[tokio::test] async fn follow_with_multiple_unpin_hashes() { let builder = TestClientBuilder::new(); -- GitLab From 7e7c488ba8d3709ac79050a08a80ec4a9cafd091 Mon Sep 17 00:00:00 2001 From: Sergej Sakac <73715684+Szegoo@users.noreply.github.com> Date: Wed, 14 Feb 2024 18:00:03 +0100 Subject: [PATCH 061/133] Validate code when scheduling uprades from registrar (#3232) Currently, anyone can registrar a code that exceeds the code size limit when performing the upgrade from the registrar. This PR fixes that and adds a new test to cover this. cc @bkchr @eskimor --- polkadot/primitives/src/lib.rs | 4 +- polkadot/primitives/src/v6/mod.rs | 3 + polkadot/primitives/test-helpers/src/lib.rs | 2 +- .../runtime/common/src/integration_tests.rs | 3 +- .../runtime/common/src/paras_registrar/mod.rs | 63 ++++++++++++-- .../runtime/parachains/src/configuration.rs | 2 +- .../runtime/parachains/src/inclusion/tests.rs | 10 +-- .../src/paras/benchmarking/pvf_check.rs | 4 +- polkadot/runtime/parachains/src/paras/mod.rs | 17 +++- .../runtime/parachains/src/paras/tests.rs | 86 +++++++++++-------- prdoc/pr_3232.prdoc | 13 +++ 11 files changed, 146 insertions(+), 61 deletions(-) create mode 100644 prdoc/pr_3232.prdoc diff --git a/polkadot/primitives/src/lib.rs b/polkadot/primitives/src/lib.rs index 2570bcadf60..2ddd9b58dfe 100644 --- a/polkadot/primitives/src/lib.rs +++ b/polkadot/primitives/src/lib.rs @@ -57,8 +57,8 @@ pub use v6::{ UpgradeRestriction, UpwardMessage, ValidDisputeStatementKind, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature, ValidityAttestation, ValidityError, ASSIGNMENT_KEY_TYPE_ID, LEGACY_MIN_BACKING_VOTES, LOWEST_PUBLIC_ID, - MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE, MAX_POV_SIZE, ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE, - PARACHAINS_INHERENT_IDENTIFIER, PARACHAIN_KEY_TYPE_ID, + MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE, MAX_POV_SIZE, MIN_CODE_SIZE, + ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE, PARACHAINS_INHERENT_IDENTIFIER, PARACHAIN_KEY_TYPE_ID, }; #[cfg(feature = "std")] diff --git a/polkadot/primitives/src/v6/mod.rs b/polkadot/primitives/src/v6/mod.rs index fd0b32db799..4938d20d2d1 100644 --- a/polkadot/primitives/src/v6/mod.rs +++ b/polkadot/primitives/src/v6/mod.rs @@ -362,6 +362,9 @@ pub const PARACHAINS_INHERENT_IDENTIFIER: InherentIdentifier = *b"parachn0"; /// The key type ID for parachain assignment key. pub const ASSIGNMENT_KEY_TYPE_ID: KeyTypeId = KeyTypeId(*b"asgn"); +/// Compressed or not the wasm blob can never be less than 9 bytes. +pub const MIN_CODE_SIZE: u32 = 9; + /// Maximum compressed code size we support right now. /// At the moment we have runtime upgrade on chain, which restricts scalability severely. If we want /// to have bigger values, we should fix that first. diff --git a/polkadot/primitives/test-helpers/src/lib.rs b/polkadot/primitives/test-helpers/src/lib.rs index d532d6ff57f..c0118f5960a 100644 --- a/polkadot/primitives/test-helpers/src/lib.rs +++ b/polkadot/primitives/test-helpers/src/lib.rs @@ -126,7 +126,7 @@ pub fn dummy_candidate_descriptor>(relay_parent: H) -> CandidateD /// Create meaningless validation code. pub fn dummy_validation_code() -> ValidationCode { - ValidationCode(vec![1, 2, 3]) + ValidationCode(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]) } /// Create meaningless head data. diff --git a/polkadot/runtime/common/src/integration_tests.rs b/polkadot/runtime/common/src/integration_tests.rs index f551743b395..3aa291f0f1f 100644 --- a/polkadot/runtime/common/src/integration_tests.rs +++ b/polkadot/runtime/common/src/integration_tests.rs @@ -36,6 +36,7 @@ use pallet_identity::{self, legacy::IdentityInfo}; use parity_scale_codec::Encode; use primitives::{ BlockNumber, HeadData, Id as ParaId, SessionIndex, ValidationCode, LOWEST_PUBLIC_ID, + MAX_CODE_SIZE, }; use runtime_parachains::{ configuration, origin, paras, shared, Origin as ParaOrigin, ParaLifecycle, @@ -315,7 +316,7 @@ pub fn new_test_ext() -> TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); configuration::GenesisConfig:: { config: configuration::HostConfiguration { - max_code_size: 2 * 1024 * 1024, // 2 MB + max_code_size: MAX_CODE_SIZE, max_head_data_size: 1 * 1024 * 1024, // 1 MB ..Default::default() }, diff --git a/polkadot/runtime/common/src/paras_registrar/mod.rs b/polkadot/runtime/common/src/paras_registrar/mod.rs index c05c8a1ae00..5450c4309e4 100644 --- a/polkadot/runtime/common/src/paras_registrar/mod.rs +++ b/polkadot/runtime/common/src/paras_registrar/mod.rs @@ -26,7 +26,7 @@ use frame_support::{ traits::{Currency, Get, ReservableCurrency}, }; use frame_system::{self, ensure_root, ensure_signed}; -use primitives::{HeadData, Id as ParaId, ValidationCode, LOWEST_PUBLIC_ID}; +use primitives::{HeadData, Id as ParaId, ValidationCode, LOWEST_PUBLIC_ID, MIN_CODE_SIZE}; use runtime_parachains::{ configuration, ensure_parachain, paras::{self, ParaGenesisArgs, SetGoAhead}, @@ -182,8 +182,8 @@ pub mod pallet { ParaLocked, /// The ID given for registration has not been reserved. NotReserved, - /// Registering parachain with empty code is not allowed. - EmptyCode, + /// The validation code is invalid. + InvalidCode, /// Cannot perform a parachain slot / lifecycle swap. Check that the state of both paras /// are correct for the swap to work. CannotSwap, @@ -657,7 +657,7 @@ impl Pallet { para_kind: ParaKind, ) -> Result<(ParaGenesisArgs, BalanceOf), sp_runtime::DispatchError> { let config = configuration::Pallet::::config(); - ensure!(validation_code.0.len() > 0, Error::::EmptyCode); + ensure!(validation_code.0.len() >= MIN_CODE_SIZE as usize, Error::::InvalidCode); ensure!(validation_code.0.len() <= config.max_code_size as usize, Error::::CodeTooLarge); ensure!( genesis_head.0.len() <= config.max_head_data_size as usize, @@ -712,7 +712,7 @@ mod tests { }; use frame_system::limits; use pallet_balances::Error as BalancesError; - use primitives::{Balance, BlockNumber, SessionIndex}; + use primitives::{Balance, BlockNumber, SessionIndex, MAX_CODE_SIZE}; use runtime_parachains::{configuration, origin, shared}; use sp_core::H256; use sp_io::TestExternalities; @@ -849,7 +849,7 @@ mod tests { configuration::GenesisConfig:: { config: configuration::HostConfiguration { - max_code_size: 2 * 1024 * 1024, // 2 MB + max_code_size: MAX_CODE_SIZE, max_head_data_size: 1 * 1024 * 1024, // 1 MB ..Default::default() }, @@ -1032,6 +1032,51 @@ mod tests { }); } + #[test] + fn schedule_code_upgrade_validates_code() { + new_test_ext().execute_with(|| { + const START_SESSION_INDEX: SessionIndex = 1; + run_to_session(START_SESSION_INDEX); + + let para_id = LOWEST_PUBLIC_ID; + assert!(!Parachains::is_parathread(para_id)); + + let validation_code = test_validation_code(32); + assert_ok!(Registrar::reserve(RuntimeOrigin::signed(1))); + assert_eq!(Balances::reserved_balance(&1), ::ParaDeposit::get()); + assert_ok!(Registrar::register( + RuntimeOrigin::signed(1), + para_id, + test_genesis_head(32), + validation_code.clone(), + )); + conclude_pvf_checking::(&validation_code, VALIDATORS, START_SESSION_INDEX); + + run_to_session(START_SESSION_INDEX + 2); + assert!(Parachains::is_parathread(para_id)); + + let new_code = test_validation_code(0); + assert_noop!( + Registrar::schedule_code_upgrade( + RuntimeOrigin::signed(1), + para_id, + new_code.clone(), + ), + paras::Error::::InvalidCode + ); + + let new_code = test_validation_code(max_code_size() as usize + 1); + assert_noop!( + Registrar::schedule_code_upgrade( + RuntimeOrigin::signed(1), + para_id, + new_code.clone(), + ), + paras::Error::::InvalidCode + ); + }); + } + #[test] fn register_handles_basic_errors() { new_test_ext().execute_with(|| { @@ -1310,7 +1355,7 @@ mod tests { RuntimeOrigin::signed(1), para_id, vec![1; 3].into(), - vec![1, 2, 3].into(), + test_validation_code(32) )); assert_noop!(Registrar::add_lock(RuntimeOrigin::signed(2), para_id), BadOrigin); @@ -1473,7 +1518,7 @@ mod benchmarking { use crate::traits::Registrar as RegistrarT; use frame_support::assert_ok; use frame_system::RawOrigin; - use primitives::{MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE}; + use primitives::{MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE, MIN_CODE_SIZE}; use runtime_parachains::{paras, shared, Origin as ParaOrigin}; use sp_runtime::traits::Bounded; @@ -1604,7 +1649,7 @@ mod benchmarking { } schedule_code_upgrade { - let b in 1 .. MAX_CODE_SIZE; + let b in MIN_CODE_SIZE .. MAX_CODE_SIZE; let new_code = ValidationCode(vec![0; b as usize]); let para_id = ParaId::from(1000); }: _(RawOrigin::Root, para_id, new_code) diff --git a/polkadot/runtime/parachains/src/configuration.rs b/polkadot/runtime/parachains/src/configuration.rs index 4619313590e..7cc5b31fc8f 100644 --- a/polkadot/runtime/parachains/src/configuration.rs +++ b/polkadot/runtime/parachains/src/configuration.rs @@ -281,7 +281,7 @@ impl> Default for HostConfiguration::set_active_validators_ascending(validator_public.clone()); shared::Pallet::::set_session_index(5); - let new_validation_code: ValidationCode = vec![1, 2, 3, 4, 5].into(); + let new_validation_code: ValidationCode = vec![9, 8, 7, 6, 5, 4, 3, 2, 1].into(); let new_validation_code_hash = new_validation_code.hash(); // Otherwise upgrade is no-op. diff --git a/polkadot/runtime/parachains/src/paras/benchmarking/pvf_check.rs b/polkadot/runtime/parachains/src/paras/benchmarking/pvf_check.rs index 05c4c9c37b4..53ccc35c477 100644 --- a/polkadot/runtime/parachains/src/paras/benchmarking/pvf_check.rs +++ b/polkadot/runtime/parachains/src/paras/benchmarking/pvf_check.rs @@ -27,10 +27,10 @@ const SESSION_INDEX: SessionIndex = 1; const VALIDATOR_NUM: usize = 800; const CAUSES_NUM: usize = 100; fn validation_code() -> ValidationCode { - ValidationCode(vec![0]) + ValidationCode(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]) } fn old_validation_code() -> ValidationCode { - ValidationCode(vec![1]) + ValidationCode(vec![9, 8, 7, 6, 5, 4, 3, 2, 1]) } /// Prepares the PVF check statement and the validator signature to pass into diff --git a/polkadot/runtime/parachains/src/paras/mod.rs b/polkadot/runtime/parachains/src/paras/mod.rs index e97df8e4a2b..39371391b1f 100644 --- a/polkadot/runtime/parachains/src/paras/mod.rs +++ b/polkadot/runtime/parachains/src/paras/mod.rs @@ -119,7 +119,7 @@ use frame_system::pallet_prelude::*; use parity_scale_codec::{Decode, Encode}; use primitives::{ ConsensusLog, HeadData, Id as ParaId, PvfCheckStatement, SessionIndex, UpgradeGoAhead, - UpgradeRestriction, ValidationCode, ValidationCodeHash, ValidatorSignature, + UpgradeRestriction, ValidationCode, ValidationCodeHash, ValidatorSignature, MIN_CODE_SIZE, }; use scale_info::{Type, TypeInfo}; use sp_core::RuntimeDebug; @@ -679,6 +679,8 @@ pub mod pallet { PvfCheckSubjectInvalid, /// Parachain cannot currently schedule a code upgrade. CannotUpgradeCode, + /// Invalid validation code size. + InvalidCode, } /// All currently active PVF pre-checking votes. @@ -1230,6 +1232,10 @@ impl Pallet { // Check that we can schedule an upgrade at all. ensure!(Self::can_upgrade_validation_code(id), Error::::CannotUpgradeCode); let config = configuration::Pallet::::config(); + // Validation code sanity checks: + ensure!(new_code.0.len() >= MIN_CODE_SIZE as usize, Error::::InvalidCode); + ensure!(new_code.0.len() <= config.max_code_size as usize, Error::::InvalidCode); + let current_block = frame_system::Pallet::::block_number(); // Schedule the upgrade with a delay just like if a parachain triggered the upgrade. let upgrade_block = current_block.saturating_add(config.validation_upgrade_delay); @@ -1890,7 +1896,14 @@ impl Pallet { ) -> Weight { let mut weight = T::DbWeight::get().reads(1); - // Enacting this should be prevented by the `can_schedule_upgrade` + // Should be prevented by checks in `schedule_code_upgrade_external` + let new_code_len = new_code.0.len(); + if new_code_len < MIN_CODE_SIZE as usize || new_code_len > cfg.max_code_size as usize { + log::warn!(target: LOG_TARGET, "attempted to schedule an upgrade with invalid new validation code",); + return weight + } + + // Enacting this should be prevented by the `can_upgrade_validation_code` if FutureCodeHash::::contains_key(&id) { // This branch should never be reached. Signalling an upgrade is disallowed for a para // that already has one upgrade scheduled. diff --git a/polkadot/runtime/parachains/src/paras/tests.rs b/polkadot/runtime/parachains/src/paras/tests.rs index cca200c2765..24ea919ec87 100644 --- a/polkadot/runtime/parachains/src/paras/tests.rs +++ b/polkadot/runtime/parachains/src/paras/tests.rs @@ -67,6 +67,16 @@ fn submit_super_majority_pvf_votes( .for_each(sign_and_include_pvf_check_statement); } +fn test_validation_code_1() -> ValidationCode { + let validation_code = vec![1, 2, 3, 4, 5, 6, 7, 8, 9]; + ValidationCode(validation_code) +} + +fn test_validation_code_2() -> ValidationCode { + let validation_code = vec![9, 8, 7, 6, 5, 4, 3, 2, 1]; + ValidationCode(validation_code) +} + fn run_to_block(to: BlockNumber, new_session: Option>) { let keystore: KeystorePtr = Arc::new(LocalKeystore::in_memory()); for validator in VALIDATORS.iter() { @@ -284,7 +294,7 @@ fn para_past_code_pruning_in_initialize() { let id = ParaId::from(0u32); let at_block: BlockNumber = 10; let included_block: BlockNumber = 12; - let validation_code = ValidationCode(vec![4, 5, 6]); + let validation_code = test_validation_code_2(); Paras::increase_code_ref(&validation_code.hash(), &validation_code); PastCodeHash::::insert(&(id, at_block), &validation_code.hash()); @@ -377,8 +387,8 @@ fn note_past_code_sets_up_pruning_correctly() { let id_a = ParaId::from(0u32); let id_b = ParaId::from(1u32); - Paras::note_past_code(id_a, 10, 12, ValidationCode(vec![1, 2, 3]).hash()); - Paras::note_past_code(id_b, 20, 23, ValidationCode(vec![4, 5, 6]).hash()); + Paras::note_past_code(id_a, 10, 12, test_validation_code_1().hash()); + Paras::note_past_code(id_b, 20, 23, test_validation_code_2().hash()); assert_eq!(PastCodePruning::::get(), vec![(id_a, 12), (id_b, 23)]); assert_eq!( @@ -398,7 +408,7 @@ fn code_upgrade_applied_after_delay() { let validation_upgrade_delay = 5; let validation_upgrade_cooldown = 10; - let original_code = ValidationCode(vec![1, 2, 3]); + let original_code = test_validation_code_1(); let paras = vec![( 0u32.into(), ParaGenesisArgs { @@ -425,7 +435,7 @@ fn code_upgrade_applied_after_delay() { check_code_is_stored(&original_code); let para_id = ParaId::from(0); - let new_code = ValidationCode(vec![4, 5, 6]); + let new_code = test_validation_code_2(); // Wait for at least one session change to set active validators. const EXPECTED_SESSION: SessionIndex = 1; @@ -516,7 +526,7 @@ fn code_upgrade_applied_without_setting_go_ahead_signal() { let validation_upgrade_delay = 5; let validation_upgrade_cooldown = 10; - let original_code = ValidationCode(vec![1, 2, 3]); + let original_code = test_validation_code_1(); let paras = vec![( 0u32.into(), ParaGenesisArgs { @@ -543,7 +553,7 @@ fn code_upgrade_applied_without_setting_go_ahead_signal() { check_code_is_stored(&original_code); let para_id = ParaId::from(0); - let new_code = ValidationCode(vec![4, 5, 6]); + let new_code = test_validation_code_2(); // Wait for at least one session change to set active validators. const EXPECTED_SESSION: SessionIndex = 1; @@ -637,7 +647,7 @@ fn code_upgrade_applied_after_delay_even_when_late() { let validation_upgrade_delay = 5; let validation_upgrade_cooldown = 10; - let original_code = ValidationCode(vec![1, 2, 3]); + let original_code = test_validation_code_1(); let paras = vec![( 0u32.into(), ParaGenesisArgs { @@ -662,7 +672,7 @@ fn code_upgrade_applied_after_delay_even_when_late() { new_test_ext(genesis_config).execute_with(|| { let para_id = ParaId::from(0); - let new_code = ValidationCode(vec![4, 5, 6]); + let new_code = test_validation_code_2(); // Wait for at least one session change to set active validators. const EXPECTED_SESSION: SessionIndex = 1; @@ -750,8 +760,8 @@ fn submit_code_change_when_not_allowed_is_err() { new_test_ext(genesis_config).execute_with(|| { let para_id = ParaId::from(0); - let new_code = ValidationCode(vec![4, 5, 6]); - let newer_code = ValidationCode(vec![4, 5, 6, 7]); + let new_code = test_validation_code_1(); + let newer_code = test_validation_code_2(); // Wait for at least one session change to set active validators. const EXPECTED_SESSION: SessionIndex = 1; @@ -832,8 +842,8 @@ fn upgrade_restriction_elapsed_doesnt_mean_can_upgrade() { new_test_ext(genesis_config).execute_with(|| { let para_id = 0u32.into(); - let new_code = ValidationCode(vec![4, 5, 6]); - let newer_code = ValidationCode(vec![4, 5, 6, 7]); + let new_code = test_validation_code_1(); + let newer_code = test_validation_code_2(); // Wait for at least one session change to set active validators. const EXPECTED_SESSION: SessionIndex = 1; @@ -880,7 +890,7 @@ fn full_parachain_cleanup_storage() { let code_retention_period = 20; let validation_upgrade_delay = 1 + 5; - let original_code = ValidationCode(vec![1, 2, 3]); + let original_code = test_validation_code_1(); let paras = vec![( 0u32.into(), ParaGenesisArgs { @@ -910,7 +920,7 @@ fn full_parachain_cleanup_storage() { check_code_is_stored(&original_code); let para_id = ParaId::from(0); - let new_code = ValidationCode(vec![4, 5, 6]); + let new_code = test_validation_code_2(); // Wait for at least one session change to set active validators. const EXPECTED_SESSION: SessionIndex = 1; @@ -993,8 +1003,8 @@ fn full_parachain_cleanup_storage() { fn cannot_offboard_ongoing_pvf_check() { let para_id = ParaId::from(0); - let existing_code: ValidationCode = vec![1, 2, 3].into(); - let new_code: ValidationCode = vec![3, 2, 1].into(); + let existing_code = test_validation_code_1(); + let new_code = test_validation_code_2(); let paras = vec![( para_id, @@ -1152,7 +1162,7 @@ fn code_hash_at_returns_up_to_end_of_code_retention_period() { ParaGenesisArgs { para_kind: ParaKind::Parachain, genesis_head: dummy_head_data(), - validation_code: vec![1, 2, 3].into(), + validation_code: test_validation_code_1(), }, )]; @@ -1174,8 +1184,8 @@ fn code_hash_at_returns_up_to_end_of_code_retention_period() { const EXPECTED_SESSION: SessionIndex = 1; let para_id = ParaId::from(0); - let old_code: ValidationCode = vec![1, 2, 3].into(); - let new_code: ValidationCode = vec![4, 5, 6].into(); + let old_code = test_validation_code_1(); + let new_code = test_validation_code_2(); Paras::schedule_code_upgrade( para_id, new_code.clone(), @@ -1219,7 +1229,7 @@ fn code_hash_at_returns_up_to_end_of_code_retention_period() { #[test] fn code_ref_is_cleaned_correctly() { new_test_ext(Default::default()).execute_with(|| { - let code: ValidationCode = vec![1, 2, 3].into(); + let code = test_validation_code_1(); Paras::increase_code_ref(&code.hash(), &code); Paras::increase_code_ref(&code.hash(), &code); @@ -1244,8 +1254,8 @@ fn pvf_check_coalescing_onboarding_and_upgrade() { let a = ParaId::from(111); let b = ParaId::from(222); - let existing_code: ValidationCode = vec![1, 2, 3].into(); - let validation_code: ValidationCode = vec![3, 2, 1].into(); + let existing_code = test_validation_code_1(); + let validation_code = test_validation_code_2(); let paras = vec![( a, @@ -1320,7 +1330,7 @@ fn pvf_check_coalescing_onboarding_and_upgrade() { fn pvf_check_onboarding_reject_on_expiry() { let pvf_voting_ttl = 2; let a = ParaId::from(111); - let validation_code: ValidationCode = vec![3, 2, 1].into(); + let validation_code = test_validation_code_1(); let genesis_config = MockGenesisConfig { configuration: crate::configuration::GenesisConfig { @@ -1368,8 +1378,8 @@ fn pvf_check_onboarding_reject_on_expiry() { #[test] fn pvf_check_upgrade_reject() { let a = ParaId::from(111); - let old_code: ValidationCode = vec![1, 2, 3].into(); - let new_code: ValidationCode = vec![3, 2, 1].into(); + let old_code = test_validation_code_1(); + let new_code = test_validation_code_2(); let paras = vec![( a, @@ -1437,8 +1447,8 @@ fn pvf_check_upgrade_reject() { #[test] fn pvf_check_submit_vote() { - let code_a: ValidationCode = vec![3, 2, 1].into(); - let code_b: ValidationCode = vec![1, 2, 3].into(); + let code_a = test_validation_code_1(); + let code_b = test_validation_code_2(); let check = |stmt: PvfCheckStatement| -> (Result<_, _>, Result<_, _>) { let validators = &[ @@ -1554,8 +1564,8 @@ fn pvf_check_submit_vote() { #[test] fn include_pvf_check_statement_refunds_weight() { let a = ParaId::from(111); - let old_code: ValidationCode = vec![1, 2, 3].into(); - let new_code: ValidationCode = vec![3, 2, 1].into(); + let old_code = test_validation_code_1(); + let new_code = test_validation_code_2(); let paras = vec![( a, @@ -1620,7 +1630,7 @@ fn include_pvf_check_statement_refunds_weight() { fn add_trusted_validation_code_inserts_with_no_users() { // This test is to ensure that trusted validation code is inserted into the storage // with the reference count equal to 0. - let validation_code = ValidationCode(vec![1, 2, 3]); + let validation_code = test_validation_code_1(); new_test_ext(Default::default()).execute_with(|| { assert_ok!(Paras::add_trusted_validation_code( RuntimeOrigin::root(), @@ -1634,7 +1644,7 @@ fn add_trusted_validation_code_inserts_with_no_users() { fn add_trusted_validation_code_idempotent() { // This test makes sure that calling add_trusted_validation_code twice with the same // parameters is a no-op. - let validation_code = ValidationCode(vec![1, 2, 3]); + let validation_code = test_validation_code_1(); new_test_ext(Default::default()).execute_with(|| { assert_ok!(Paras::add_trusted_validation_code( RuntimeOrigin::root(), @@ -1653,7 +1663,7 @@ fn add_trusted_validation_code_idempotent() { fn poke_unused_validation_code_removes_code_cleanly() { // This test makes sure that calling poke_unused_validation_code with a code that is currently // in the storage but has no users will remove it cleanly from the storage. - let validation_code = ValidationCode(vec![1, 2, 3]); + let validation_code = test_validation_code_1(); new_test_ext(Default::default()).execute_with(|| { assert_ok!(Paras::add_trusted_validation_code( RuntimeOrigin::root(), @@ -1672,7 +1682,7 @@ fn poke_unused_validation_code_removes_code_cleanly() { #[test] fn poke_unused_validation_code_doesnt_remove_code_with_users() { let para_id = 100.into(); - let validation_code = ValidationCode(vec![1, 2, 3]); + let validation_code = test_validation_code_1(); new_test_ext(Default::default()).execute_with(|| { // First we add the code to the storage. assert_ok!(Paras::add_trusted_validation_code( @@ -1708,7 +1718,7 @@ fn increase_code_ref_doesnt_have_allergy_on_add_trusted_validation_code() { // to a disaster. // NOTE that this test is extra paranoid, as it is not really possible to hit // `decrease_code_ref` without calling `increase_code_ref` first. - let code = ValidationCode(vec![1, 2, 3]); + let code = test_validation_code_1(); new_test_ext(Default::default()).execute_with(|| { assert_ok!(Paras::add_trusted_validation_code(RuntimeOrigin::root(), code.clone())); @@ -1732,7 +1742,7 @@ fn add_trusted_validation_code_insta_approval() { // `add_trusted_validation_code` and uses the `CodeByHash::contains_key` which is what // `add_trusted_validation_code` uses. let para_id = 100.into(); - let validation_code = ValidationCode(vec![1, 2, 3]); + let validation_code = test_validation_code_1(); let validation_upgrade_delay = 25; let minimum_validation_upgrade_delay = 2; let genesis_config = MockGenesisConfig { @@ -1779,7 +1789,7 @@ fn add_trusted_validation_code_enacts_existing_pvf_vote() { // already going through PVF pre-checking voting will conclude the voting and enact the // code upgrade. let para_id = 100.into(); - let validation_code = ValidationCode(vec![1, 2, 3]); + let validation_code = test_validation_code_1(); let validation_upgrade_delay = 25; let minimum_validation_upgrade_delay = 2; let genesis_config = MockGenesisConfig { @@ -1868,7 +1878,7 @@ fn verify_para_head_is_externally_accessible() { #[test] fn most_recent_context() { - let validation_code: ValidationCode = vec![1, 2, 3].into(); + let validation_code = test_validation_code_1(); let genesis_config = MockGenesisConfig::default(); diff --git a/prdoc/pr_3232.prdoc b/prdoc/pr_3232.prdoc new file mode 100644 index 00000000000..c6a339931c6 --- /dev/null +++ b/prdoc/pr_3232.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Validate code when scheduling uprades + +doc: + - audience: Runtime User + description: | + Adds checks to ensure that the validation code is valid before scheduling + a code upgrade. + +crates: + - name: polkadot-runtime-parachains -- GitLab From c7c4fe0184e88cc236a1b620337e8eaeeeccb594 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 14 Feb 2024 23:18:22 +0100 Subject: [PATCH 062/133] rpc: bump jsonrpsee v0.22 and fix race in `rpc v2 chain_head` (#3230) Close #2992 Breaking changes: - rpc server grafana metric `substrate_rpc_requests_started` is removed (not possible to implement anymore) - rpc server grafana metric `substrate_rpc_requests_finished` is removed (not possible to implement anymore) - rpc server ws ping/pong not ACK:ed within 30 seconds more than three times then the connection will be closed Added - rpc server grafana metric `substrate_rpc_sessions_time` is added to get the duration for each websocket session --- Cargo.lock | 270 +++++++++++------ .../client/relay-chain-interface/Cargo.toml | 2 +- .../client/relay-chain-interface/src/lib.rs | 2 +- .../relay-chain-rpc-interface/Cargo.toml | 2 +- .../src/light_client_worker.rs | 9 +- .../src/reconnecting_ws_client.rs | 2 +- .../src/rpc_client.rs | 2 +- cumulus/parachain-template/node/Cargo.toml | 2 +- cumulus/polkadot-parachain/Cargo.toml | 2 +- cumulus/test/service/Cargo.toml | 2 +- polkadot/rpc/Cargo.toml | 2 +- prdoc/pr_3230.prdoc | 19 ++ substrate/bin/minimal/node/Cargo.toml | 2 +- substrate/bin/node-template/node/Cargo.toml | 2 +- substrate/bin/node/cli/Cargo.toml | 2 +- substrate/bin/node/rpc/Cargo.toml | 2 +- .../client/consensus/babe/rpc/Cargo.toml | 2 +- .../client/consensus/babe/rpc/src/lib.rs | 4 +- .../client/consensus/beefy/rpc/Cargo.toml | 2 +- .../client/consensus/beefy/rpc/src/lib.rs | 16 +- .../client/consensus/grandpa/rpc/Cargo.toml | 2 +- .../client/consensus/grandpa/rpc/src/lib.rs | 6 +- .../client/consensus/manual-seal/Cargo.toml | 2 +- .../merkle-mountain-range/rpc/Cargo.toml | 2 +- substrate/client/rpc-api/Cargo.toml | 2 +- substrate/client/rpc-servers/Cargo.toml | 5 +- substrate/client/rpc-servers/src/lib.rs | 101 ++++++- .../client/rpc-servers/src/middleware.rs | 226 -------------- .../rpc-servers/src/middleware/metrics.rs | 281 ++++++++++++++++++ .../client/rpc-servers/src/middleware/mod.rs | 23 ++ substrate/client/rpc-spec-v2/Cargo.toml | 2 +- .../client/rpc-spec-v2/src/archive/tests.rs | 7 +- .../client/rpc-spec-v2/src/chain_head/api.rs | 8 +- .../rpc-spec-v2/src/chain_head/chain_head.rs | 117 +++++--- .../rpc-spec-v2/src/chain_head/tests.rs | 31 +- .../rpc-spec-v2/src/transaction/tests.rs | 8 +- substrate/client/rpc/Cargo.toml | 2 +- substrate/client/rpc/src/author/tests.rs | 11 +- substrate/client/rpc/src/dev/tests.rs | 2 +- substrate/client/rpc/src/state/tests.rs | 4 +- substrate/client/rpc/src/system/tests.rs | 9 +- substrate/client/rpc/src/utils.rs | 10 +- substrate/client/service/Cargo.toml | 2 +- substrate/client/service/src/lib.rs | 9 +- substrate/client/sync-state-rpc/Cargo.toml | 2 +- .../frame/transaction-payment/rpc/Cargo.toml | 2 +- .../frame/remote-externalities/Cargo.toml | 2 +- substrate/utils/frame/rpc/client/Cargo.toml | 2 +- substrate/utils/frame/rpc/client/src/lib.rs | 4 +- .../rpc/state-trie-migration-rpc/Cargo.toml | 3 +- substrate/utils/frame/rpc/support/Cargo.toml | 4 +- substrate/utils/frame/rpc/support/src/lib.rs | 4 +- substrate/utils/frame/rpc/system/Cargo.toml | 2 +- 53 files changed, 777 insertions(+), 468 deletions(-) create mode 100644 prdoc/pr_3230.prdoc delete mode 100644 substrate/client/rpc-servers/src/middleware.rs create mode 100644 substrate/client/rpc-servers/src/middleware/metrics.rs create mode 100644 substrate/client/rpc-servers/src/middleware/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 47ef107799d..c29c5646bbe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,16 +86,16 @@ dependencies = [ [[package]] name = "aes-gcm" -version = "0.9.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" +checksum = "bc3be92e19a7ef47457b8e6f90707e12b6ac5d20c6f3866584fa3be0787d839f" dependencies = [ "aead 0.4.3", "aes 0.7.5", "cipher 0.3.0", - "ctr 0.8.0", + "ctr 0.7.0", "ghash 0.4.4", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] @@ -109,7 +109,7 @@ dependencies = [ "cipher 0.4.4", "ctr 0.9.2", "ghash 0.5.0", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] @@ -1099,7 +1099,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ "concurrent-queue", - "event-listener", + "event-listener 2.5.3", "futures-core", ] @@ -1109,7 +1109,7 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" dependencies = [ - "async-lock", + "async-lock 2.8.0", "async-task", "concurrent-queue", "fastrand 1.9.0", @@ -1123,7 +1123,7 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" dependencies = [ - "async-lock", + "async-lock 2.8.0", "autocfg", "blocking", "futures-lite", @@ -1135,7 +1135,7 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ - "async-lock", + "async-lock 2.8.0", "autocfg", "cfg-if", "concurrent-queue", @@ -1155,7 +1155,18 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" dependencies = [ - "event-listener", + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" +dependencies = [ + "event-listener 4.0.3", + "event-listener-strategy", + "pin-project-lite 0.2.12", ] [[package]] @@ -1177,11 +1188,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" dependencies = [ "async-io", - "async-lock", + "async-lock 2.8.0", "autocfg", "blocking", "cfg-if", - "event-listener", + "event-listener 2.5.3", "futures-lite", "rustix 0.37.23", "signal-hook", @@ -1580,7 +1591,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" dependencies = [ "async-channel", - "async-lock", + "async-lock 2.8.0", "async-task", "atomic-waker", "fastrand 1.9.0", @@ -3467,7 +3478,7 @@ checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" dependencies = [ "generic-array 0.14.7", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle 2.5.0", "zeroize", ] @@ -3499,24 +3510,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ "generic-array 0.14.7", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] name = "crypto-mac" -version = "0.11.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" dependencies = [ "generic-array 0.14.7", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] name = "ctr" -version = "0.8.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +checksum = "a232f92a03f37dd7d7dd2adc67166c77e9cd88de5b019b9a9eecfaeaf7bfd481" dependencies = [ "cipher 0.3.0", ] @@ -4356,7 +4367,7 @@ dependencies = [ "byteorder", "digest 0.8.1", "rand_core 0.5.1", - "subtle 2.4.1", + "subtle 2.5.0", "zeroize", ] @@ -4369,7 +4380,7 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", - "subtle 2.4.1", + "subtle 2.5.0", "zeroize", ] @@ -4386,7 +4397,7 @@ dependencies = [ "fiat-crypto", "platforms", "rustc_version 0.4.0", - "subtle 2.4.1", + "subtle 2.5.0", "zeroize", ] @@ -4610,7 +4621,7 @@ dependencies = [ "block-buffer 0.10.4", "const-oid", "crypto-common", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] @@ -4816,7 +4827,7 @@ dependencies = [ "rand_core 0.6.4", "serde", "sha2 0.10.7", - "subtle 2.4.1", + "subtle 2.5.0", "zeroize", ] @@ -4870,7 +4881,7 @@ dependencies = [ "pkcs8", "rand_core 0.6.4", "sec1", - "subtle 2.4.1", + "subtle 2.5.0", "zeroize", ] @@ -5098,6 +5109,27 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "event-listener" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite 0.2.12", +] + +[[package]] +name = "event-listener-strategy" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +dependencies = [ + "event-listener 4.0.3", + "pin-project-lite 0.2.12", +] + [[package]] name = "exit-future" version = "0.2.0" @@ -5244,7 +5276,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ "rand_core 0.6.4", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] @@ -5861,9 +5893,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -5892,9 +5924,9 @@ checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -6166,7 +6198,7 @@ checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] @@ -6319,7 +6351,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" dependencies = [ - "crypto-mac 0.11.1", + "crypto-mac 0.11.0", "digest 0.9.0", ] @@ -6447,9 +6479,9 @@ dependencies = [ "hyper", "log", "rustls 0.21.6", - "rustls-native-certs", + "rustls-native-certs 0.6.3", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", ] [[package]] @@ -6777,9 +6809,9 @@ checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" [[package]] name = "jsonrpsee" -version = "0.20.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "affdc52f7596ccb2d7645231fc6163bb314630c989b64998f3699a28b4d5d4dc" +checksum = "4a95f7cc23d5fab0cdeeaf6bad8c8f5e7a3aa7f0d211957ea78232b327ab27b0" dependencies = [ "jsonrpsee-core", "jsonrpsee-http-client", @@ -6793,19 +6825,20 @@ dependencies = [ [[package]] name = "jsonrpsee-client-transport" -version = "0.20.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b005c793122d03217da09af68ba9383363caa950b90d3436106df8cabce935" +checksum = "6b1736cfa3845fd9f8f43751f2b8e0e83f7b6081e754502f7d63b6587692cc83" dependencies = [ "futures-util", "http", "jsonrpsee-core", "pin-project", - "rustls-native-certs", + "rustls-native-certs 0.7.0", + "rustls-pki-types", "soketto", "thiserror", "tokio", - "tokio-rustls", + "tokio-rustls 0.25.0", "tokio-util", "tracing", "url", @@ -6813,12 +6846,12 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.20.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2327ba8df2fdbd5e897e2b5ed25ce7f299d345b9736b6828814c3dbd1fd47b" +checksum = "82030d038658974732103e623ba2e0abec03bbbe175b39c0a2fafbada60c5868" dependencies = [ "anyhow", - "async-lock", + "async-lock 3.3.0", "async-trait", "beef", "futures-timer", @@ -6826,21 +6859,22 @@ dependencies = [ "hyper", "jsonrpsee-types", "parking_lot 0.12.1", + "pin-project", "rand", "rustc-hash", "serde", "serde_json", - "soketto", "thiserror", "tokio", + "tokio-stream", "tracing", ] [[package]] name = "jsonrpsee-http-client" -version = "0.20.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f80c17f62c7653ce767e3d7288b793dfec920f97067ceb189ebdd3570f2bc20" +checksum = "36a06ef0de060005fddf772d54597bb6a8b0413da47dcffd304b0306147b9678" dependencies = [ "async-trait", "hyper", @@ -6858,12 +6892,12 @@ dependencies = [ [[package]] name = "jsonrpsee-proc-macros" -version = "0.20.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29110019693a4fa2dbda04876499d098fa16d70eba06b1e6e2b3f1b251419515" +checksum = "69fc56131589f82e57805f7338b87023db4aafef813555708b159787e34ad6bc" dependencies = [ "heck", - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.0.0", "proc-macro2", "quote", "syn 1.0.109", @@ -6871,15 +6905,16 @@ dependencies = [ [[package]] name = "jsonrpsee-server" -version = "0.20.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c39a00449c9ef3f50b84fc00fc4acba20ef8f559f07902244abf4c15c5ab9c" +checksum = "d85be77fe5b2a94589e3164fb780017f7aff7d646b49278c0d0346af16975c8e" dependencies = [ "futures-util", "http", "hyper", "jsonrpsee-core", "jsonrpsee-types", + "pin-project", "route-recognizer", "serde", "serde_json", @@ -6894,23 +6929,22 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.20.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be0be325642e850ed0bdff426674d2e66b2b7117c9be23a7caef68a2902b7d9" +checksum = "9a48fdc1202eafc51c63e00406575e59493284ace8b8b61aa16f3a6db5d64f1a" dependencies = [ "anyhow", "beef", "serde", "serde_json", "thiserror", - "tracing", ] [[package]] name = "jsonrpsee-ws-client" -version = "0.20.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca9cb3933ccae417eb6b08c3448eb1cb46e39834e5b503e395e5e5bd08546c0" +checksum = "c5ce25d70a8e4d3cc574bbc3cad0137c326ad64b194793d5e7bbdd3fa4504181" dependencies = [ "http", "jsonrpsee-client-transport", @@ -7639,7 +7673,7 @@ checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" dependencies = [ "crunchy", "digest 0.9.0", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] @@ -8136,7 +8170,7 @@ dependencies = [ "rand", "rand_chacha 0.3.1", "rand_distr", - "subtle 2.4.1", + "subtle 2.5.0", "thiserror", "zeroize", ] @@ -11542,7 +11576,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" dependencies = [ - "crypto-mac 0.11.1", + "crypto-mac 0.11.0", ] [[package]] @@ -13859,7 +13893,7 @@ checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ "cpufeatures", "opaque-debug 0.3.0", - "universal-hash 0.4.1", + "universal-hash 0.4.0", ] [[package]] @@ -13882,7 +13916,7 @@ dependencies = [ "cfg-if", "cpufeatures", "opaque-debug 0.3.0", - "universal-hash 0.4.1", + "universal-hash 0.4.0", ] [[package]] @@ -14705,12 +14739,12 @@ dependencies = [ "percent-encoding", "pin-project-lite 0.2.12", "rustls 0.21.6", - "rustls-pemfile", + "rustls-pemfile 1.0.3", "serde", "serde_json", "serde_urlencoded", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", "tower-service", "url", "wasm-bindgen", @@ -14737,7 +14771,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ "hmac 0.12.1", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] @@ -15218,10 +15252,24 @@ checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" dependencies = [ "log", "ring 0.16.20", - "rustls-webpki", + "rustls-webpki 0.101.4", "sct", ] +[[package]] +name = "rustls" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" +dependencies = [ + "log", + "ring 0.17.7", + "rustls-pki-types", + "rustls-webpki 0.102.2", + "subtle 2.5.0", + "zeroize", +] + [[package]] name = "rustls-native-certs" version = "0.6.3" @@ -15229,7 +15277,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 1.0.3", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.0.0", + "rustls-pki-types", "schannel", "security-framework", ] @@ -15243,6 +15304,22 @@ dependencies = [ "base64 0.21.2", ] +[[package]] +name = "rustls-pemfile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e4980fa29e4c4b212ffb3db068a564cbf560e51d3944b7c88bd8bf5bec64f4" +dependencies = [ + "base64 0.21.2", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a716eb65e3158e90e17cd93d855216e27bde02745ab842f2cab4a39dba1bacf" + [[package]] name = "rustls-webpki" version = "0.101.4" @@ -15253,6 +15330,17 @@ dependencies = [ "untrusted 0.7.1", ] +[[package]] +name = "rustls-webpki" +version = "0.102.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +dependencies = [ + "ring 0.17.7", + "rustls-pki-types", + "untrusted 0.9.0", +] + [[package]] name = "rustversion" version = "1.0.14" @@ -16410,9 +16498,12 @@ dependencies = [ name = "sc-rpc-server" version = "11.0.0" dependencies = [ + "futures", "http", + "hyper", "jsonrpsee", "log", + "pin-project", "serde_json", "substrate-prometheus-endpoint", "tokio", @@ -16860,7 +16951,7 @@ dependencies = [ "merlin 2.0.1", "rand_core 0.5.1", "sha2 0.8.2", - "subtle 2.4.1", + "subtle 2.5.0", "zeroize", ] @@ -16895,7 +16986,7 @@ dependencies = [ "rand_core 0.6.4", "serde_bytes", "sha2 0.10.7", - "subtle 2.4.1", + "subtle 2.5.0", "zeroize", ] @@ -16937,7 +17028,7 @@ dependencies = [ "der", "generic-array 0.14.7", "pkcs8", - "subtle 2.4.1", + "subtle 2.5.0", "zeroize", ] @@ -17457,7 +17548,7 @@ dependencies = [ "async-executor", "async-fs", "async-io", - "async-lock", + "async-lock 2.8.0", "async-net", "async-process", "blocking", @@ -17480,7 +17571,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0bb30cf57b7b5f6109ce17c3164445e2d6f270af2cb48f6e4d31c2967c9a9f5" dependencies = [ "arrayvec 0.7.4", - "async-lock", + "async-lock 2.8.0", "atomic-take", "base64 0.21.2", "bip39", @@ -17491,7 +17582,7 @@ dependencies = [ "derive_more", "ed25519-zebra 4.0.3", "either", - "event-listener", + "event-listener 2.5.3", "fnv", "futures-lite", "futures-util", @@ -17534,12 +17625,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "256b5bad1d6b49045e95fe87492ce73d5af81545d8b4d8318a872d2007024c33" dependencies = [ "async-channel", - "async-lock", + "async-lock 2.8.0", "base64 0.21.2", "blake2-rfc", "derive_more", "either", - "event-listener", + "event-listener 2.5.3", "fnv", "futures-channel", "futures-lite", @@ -17575,7 +17666,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c9d1425eb528a21de2755c75af4c9b5d57f50a0d4c3b7f1828a4cd03f8ba155" dependencies = [ - "aes-gcm 0.9.4", + "aes-gcm 0.9.2", "blake2 0.10.6", "chacha20poly1305", "curve25519-dalek 4.1.2", @@ -17583,7 +17674,7 @@ dependencies = [ "ring 0.16.20", "rustc_version 0.4.0", "sha2 0.10.7", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] @@ -19425,7 +19516,7 @@ dependencies = [ "bitflags 1.3.2", "byteorder", "keccak", - "subtle 2.4.1", + "subtle 2.5.0", "zeroize", ] @@ -19757,9 +19848,9 @@ checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" [[package]] name = "subtle" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "subtle-ng" @@ -20339,6 +20430,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.2", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.14" @@ -20888,12 +20990,12 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "universal-hash" -version = "0.4.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" dependencies = [ "generic-array 0.14.7", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] @@ -20903,7 +21005,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ "crypto-common", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] diff --git a/cumulus/client/relay-chain-interface/Cargo.toml b/cumulus/client/relay-chain-interface/Cargo.toml index 004d30f7f94..5cdfff23ae1 100644 --- a/cumulus/client/relay-chain-interface/Cargo.toml +++ b/cumulus/client/relay-chain-interface/Cargo.toml @@ -22,5 +22,5 @@ sc-client-api = { path = "../../../substrate/client/api" } futures = "0.3.28" async-trait = "0.1.74" thiserror = "1.0.48" -jsonrpsee-core = "0.20.3" +jsonrpsee-core = "0.22" parity-scale-codec = "3.6.4" diff --git a/cumulus/client/relay-chain-interface/src/lib.rs b/cumulus/client/relay-chain-interface/src/lib.rs index 3dda6163580..de5e7891b30 100644 --- a/cumulus/client/relay-chain-interface/src/lib.rs +++ b/cumulus/client/relay-chain-interface/src/lib.rs @@ -22,7 +22,7 @@ use sc_client_api::StorageProof; use futures::Stream; use async_trait::async_trait; -use jsonrpsee_core::Error as JsonRpcError; +use jsonrpsee_core::ClientError as JsonRpcError; use parity_scale_codec::Error as CodecError; use sp_api::ApiError; diff --git a/cumulus/client/relay-chain-rpc-interface/Cargo.toml b/cumulus/client/relay-chain-rpc-interface/Cargo.toml index fef6f82537d..fc89cdd5a9e 100644 --- a/cumulus/client/relay-chain-rpc-interface/Cargo.toml +++ b/cumulus/client/relay-chain-rpc-interface/Cargo.toml @@ -33,7 +33,7 @@ tokio-util = { version = "0.7.8", features = ["compat"] } futures = "0.3.28" futures-timer = "3.0.2" parity-scale-codec = "3.6.4" -jsonrpsee = { version = "0.20.3", features = ["ws-client"] } +jsonrpsee = { version = "0.22", features = ["ws-client"] } tracing = "0.1.37" async-trait = "0.1.74" url = "2.4.0" diff --git a/cumulus/client/relay-chain-rpc-interface/src/light_client_worker.rs b/cumulus/client/relay-chain-rpc-interface/src/light_client_worker.rs index 6fd057e170b..9a49b60281b 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/light_client_worker.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/light_client_worker.rs @@ -19,12 +19,9 @@ //! we treat the light-client as a normal JsonRPC target. use futures::{channel::mpsc::Sender, prelude::*, stream::FuturesUnordered}; -use jsonrpsee::core::{ - client::{ - Client as JsonRpseeClient, ClientBuilder, ClientT, ReceivedMessage, TransportReceiverT, - TransportSenderT, - }, - Error, +use jsonrpsee::core::client::{ + Client as JsonRpseeClient, ClientBuilder, ClientT, Error, ReceivedMessage, TransportReceiverT, + TransportSenderT, }; use smoldot_light::{ChainId, Client as SmoldotClient, JsonRpcResponses}; use std::{num::NonZeroU32, sync::Arc}; diff --git a/cumulus/client/relay-chain-rpc-interface/src/reconnecting_ws_client.rs b/cumulus/client/relay-chain-rpc-interface/src/reconnecting_ws_client.rs index 322bcc93dae..b716feef1c9 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/reconnecting_ws_client.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/reconnecting_ws_client.rs @@ -27,7 +27,7 @@ use jsonrpsee::{ core::{ client::{Client as JsonRpcClient, ClientT, Subscription}, params::ArrayParams, - Error as JsonRpseeError, JsonValue, + ClientError as JsonRpseeError, JsonValue, }, ws_client::WsClientBuilder, }; diff --git a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs index c64fff77a29..a912997e947 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs @@ -19,7 +19,7 @@ use futures::channel::{ oneshot::Sender as OneshotSender, }; use jsonrpsee::{ - core::{params::ArrayParams, Error as JsonRpseeError}, + core::{params::ArrayParams, ClientError as JsonRpseeError}, rpc_params, }; use serde::de::DeserializeOwned; diff --git a/cumulus/parachain-template/node/Cargo.toml b/cumulus/parachain-template/node/Cargo.toml index 0ad07ac7aac..fce5bd8f075 100644 --- a/cumulus/parachain-template/node/Cargo.toml +++ b/cumulus/parachain-template/node/Cargo.toml @@ -18,7 +18,7 @@ clap = { version = "4.4.18", features = ["derive"] } log = { workspace = true, default-features = true } codec = { package = "parity-scale-codec", version = "3.0.0" } serde = { version = "1.0.195", features = ["derive"] } -jsonrpsee = { version = "0.20.3", features = ["server"] } +jsonrpsee = { version = "0.22", features = ["server"] } futures = "0.3.28" serde_json = "1.0.111" diff --git a/cumulus/polkadot-parachain/Cargo.toml b/cumulus/polkadot-parachain/Cargo.toml index 646efe3c5f5..9905ec86f47 100644 --- a/cumulus/polkadot-parachain/Cargo.toml +++ b/cumulus/polkadot-parachain/Cargo.toml @@ -38,7 +38,7 @@ coretime-rococo-runtime = { path = "../parachains/runtimes/coretime/coretime-roc coretime-westend-runtime = { path = "../parachains/runtimes/coretime/coretime-westend" } bridge-hub-westend-runtime = { path = "../parachains/runtimes/bridge-hubs/bridge-hub-westend" } penpal-runtime = { path = "../parachains/runtimes/testing/penpal" } -jsonrpsee = { version = "0.20.3", features = ["server"] } +jsonrpsee = { version = "0.22", features = ["server"] } people-rococo-runtime = { path = "../parachains/runtimes/people/people-rococo" } people-westend-runtime = { path = "../parachains/runtimes/people/people-westend" } parachains-common = { path = "../parachains/common" } diff --git a/cumulus/test/service/Cargo.toml b/cumulus/test/service/Cargo.toml index c17cdf35419..b710146ffe8 100644 --- a/cumulus/test/service/Cargo.toml +++ b/cumulus/test/service/Cargo.toml @@ -17,7 +17,7 @@ async-trait = "0.1.74" clap = { version = "4.4.18", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0" } criterion = { version = "0.5.1", features = ["async_tokio"] } -jsonrpsee = { version = "0.20.3", features = ["server"] } +jsonrpsee = { version = "0.22", features = ["server"] } rand = "0.8.5" serde = { version = "1.0.195", features = ["derive"] } serde_json = "1.0.111" diff --git a/polkadot/rpc/Cargo.toml b/polkadot/rpc/Cargo.toml index 98ce2e482ce..5af5e63b175 100644 --- a/polkadot/rpc/Cargo.toml +++ b/polkadot/rpc/Cargo.toml @@ -10,7 +10,7 @@ description = "Polkadot specific RPC functionality." workspace = true [dependencies] -jsonrpsee = { version = "0.20.3", features = ["server"] } +jsonrpsee = { version = "0.22", features = ["server"] } polkadot-primitives = { path = "../primitives" } sc-client-api = { path = "../../substrate/client/api" } sp-blockchain = { path = "../../substrate/primitives/blockchain" } diff --git a/prdoc/pr_3230.prdoc b/prdoc/pr_3230.prdoc new file mode 100644 index 00000000000..e6d32f918d3 --- /dev/null +++ b/prdoc/pr_3230.prdoc @@ -0,0 +1,19 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: rpc server remove prometheus metrics `substrate_rpc_requests_started/finished` and refactor WS ping/pongs. + +doc: + - audience: Node Operator + description: | + This PR updates the rpc server library to `jsonrpsee v0.22` to utilize new APIs. + + Breaking changes: + - Remove prometheus RPC metrics `substrate_rpc_requests_started` and `substrate_rpc_requests_finished`. + - The RPC server now disconnects inactive peers that didn't acknowledge WebSocket + pings more than three times in time. + + Added: + - Add prometheus RPC `substrate_rpc_sessions_time` to collect the duration for each WebSocket + session. +crates: [ ] diff --git a/substrate/bin/minimal/node/Cargo.toml b/substrate/bin/minimal/node/Cargo.toml index 34476a72954..2cea39d0df8 100644 --- a/substrate/bin/minimal/node/Cargo.toml +++ b/substrate/bin/minimal/node/Cargo.toml @@ -23,7 +23,7 @@ name = "minimal-node" clap = { version = "4.4.18", features = ["derive"] } futures = { version = "0.3.21", features = ["thread-pool"] } futures-timer = "3.0.1" -jsonrpsee = { version = "0.20.3", features = ["server"] } +jsonrpsee = { version = "0.22", features = ["server"] } serde_json = "1.0.111" sc-cli = { path = "../../../client/cli" } diff --git a/substrate/bin/node-template/node/Cargo.toml b/substrate/bin/node-template/node/Cargo.toml index da601a665e9..0dcf2fefc43 100644 --- a/substrate/bin/node-template/node/Cargo.toml +++ b/substrate/bin/node-template/node/Cargo.toml @@ -48,7 +48,7 @@ frame-system = { path = "../../../frame/system" } pallet-transaction-payment = { path = "../../../frame/transaction-payment", default-features = false } # These dependencies are used for the node template's RPCs -jsonrpsee = { version = "0.20.3", features = ["server"] } +jsonrpsee = { version = "0.22", features = ["server"] } sp-api = { path = "../../../primitives/api" } sc-rpc-api = { path = "../../../client/rpc-api" } sp-blockchain = { path = "../../../primitives/blockchain" } diff --git a/substrate/bin/node/cli/Cargo.toml b/substrate/bin/node/cli/Cargo.toml index efdbd0f5b2d..25ca10c1bb1 100644 --- a/substrate/bin/node/cli/Cargo.toml +++ b/substrate/bin/node/cli/Cargo.toml @@ -44,7 +44,7 @@ array-bytes = "6.1" clap = { version = "4.4.18", features = ["derive"], optional = true } codec = { package = "parity-scale-codec", version = "3.6.1" } serde = { version = "1.0.195", features = ["derive"] } -jsonrpsee = { version = "0.20.3", features = ["server"] } +jsonrpsee = { version = "0.22", features = ["server"] } futures = "0.3.21" log = { workspace = true, default-features = true } rand = "0.8" diff --git a/substrate/bin/node/rpc/Cargo.toml b/substrate/bin/node/rpc/Cargo.toml index 63a30965a16..894dbf0da85 100644 --- a/substrate/bin/node/rpc/Cargo.toml +++ b/substrate/bin/node/rpc/Cargo.toml @@ -16,7 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.20.3", features = ["server"] } +jsonrpsee = { version = "0.22", features = ["server"] } node-primitives = { path = "../primitives" } pallet-transaction-payment-rpc = { path = "../../../frame/transaction-payment/rpc" } mmr-rpc = { path = "../../../client/merkle-mountain-range/rpc" } diff --git a/substrate/client/consensus/babe/rpc/Cargo.toml b/substrate/client/consensus/babe/rpc/Cargo.toml index 2ca029444d0..49b4b1a4a6d 100644 --- a/substrate/client/consensus/babe/rpc/Cargo.toml +++ b/substrate/client/consensus/babe/rpc/Cargo.toml @@ -16,7 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] } +jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } futures = "0.3.21" serde = { version = "1.0.195", features = ["derive"] } thiserror = "1.0" diff --git a/substrate/client/consensus/babe/rpc/src/lib.rs b/substrate/client/consensus/babe/rpc/src/lib.rs index 307b1f955ba..8b183e7dfdc 100644 --- a/substrate/client/consensus/babe/rpc/src/lib.rs +++ b/substrate/client/consensus/babe/rpc/src/lib.rs @@ -260,7 +260,7 @@ mod tests { let (response, _) = api.raw_json_request(request, 1).await.unwrap(); let expected = r#"{"jsonrpc":"2.0","result":{"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY":{"primary":[0],"secondary":[1,2,4],"secondary_vrf":[]}},"id":1}"#; - assert_eq!(&response.result, expected); + assert_eq!(response, expected); } #[tokio::test] @@ -272,6 +272,6 @@ mod tests { let (response, _) = api.raw_json_request(request, 1).await.unwrap(); let expected = r#"{"jsonrpc":"2.0","error":{"code":-32601,"message":"RPC call is unsafe to be called externally"},"id":1}"#; - assert_eq!(&response.result, expected); + assert_eq!(response, expected); } } diff --git a/substrate/client/consensus/beefy/rpc/Cargo.toml b/substrate/client/consensus/beefy/rpc/Cargo.toml index 810959dad31..cff3cc5b3bb 100644 --- a/substrate/client/consensus/beefy/rpc/Cargo.toml +++ b/substrate/client/consensus/beefy/rpc/Cargo.toml @@ -14,7 +14,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } futures = "0.3.21" -jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] } +jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } log = { workspace = true, default-features = true } parking_lot = "0.12.1" serde = { version = "1.0.195", features = ["derive"] } diff --git a/substrate/client/consensus/beefy/rpc/src/lib.rs b/substrate/client/consensus/beefy/rpc/src/lib.rs index 03c83e92716..f01baee2d6e 100644 --- a/substrate/client/consensus/beefy/rpc/src/lib.rs +++ b/substrate/client/consensus/beefy/rpc/src/lib.rs @@ -184,10 +184,10 @@ mod tests { async fn uninitialized_rpc_handler() { let (rpc, _) = setup_io_handler(); let request = r#"{"jsonrpc":"2.0","method":"beefy_getFinalizedHead","params":[],"id":1}"#; - let expected_response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"BEEFY RPC endpoint not ready"},"id":1}"#.to_string(); + let expected_response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"BEEFY RPC endpoint not ready"},"id":1}"#; let (response, _) = rpc.raw_json_request(&request, 1).await.unwrap(); - assert_eq!(expected_response, response.result); + assert_eq!(expected_response, response); } #[tokio::test] @@ -205,20 +205,18 @@ mod tests { \"jsonrpc\":\"2.0\",\ \"result\":\"0x2f0039e93a27221fcf657fb877a1d4f60307106113e885096cb44a461cd0afbf\",\ \"id\":1\ - }" - .to_string(); + }"; let not_ready = "{\ \"jsonrpc\":\"2.0\",\ \"error\":{\"code\":1,\"message\":\"BEEFY RPC endpoint not ready\"},\ \"id\":1\ - }" - .to_string(); + }"; let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2); while std::time::Instant::now() < deadline { let (response, _) = io.raw_json_request(request, 1).await.expect("RPC requests work"); - if response.result != not_ready { - assert_eq!(response.result, expected); + if response != not_ready { + assert_eq!(response, expected); // Success return } @@ -249,7 +247,7 @@ mod tests { .unwrap(); let expected = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; - assert_eq!(response.result, expected); + assert_eq!(response, expected); } fn create_finality_proof() -> BeefyVersionedFinalityProof { diff --git a/substrate/client/consensus/grandpa/rpc/Cargo.toml b/substrate/client/consensus/grandpa/rpc/Cargo.toml index e6f884336b5..affb7b4a512 100644 --- a/substrate/client/consensus/grandpa/rpc/Cargo.toml +++ b/substrate/client/consensus/grandpa/rpc/Cargo.toml @@ -15,7 +15,7 @@ workspace = true [dependencies] finality-grandpa = { version = "0.16.2", features = ["derive-codec"] } futures = "0.3.16" -jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] } +jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } log = { workspace = true, default-features = true } parity-scale-codec = { version = "3.6.1", features = ["derive"] } serde = { version = "1.0.195", features = ["derive"] } diff --git a/substrate/client/consensus/grandpa/rpc/src/lib.rs b/substrate/client/consensus/grandpa/rpc/src/lib.rs index 878cefacc47..0557eab93e2 100644 --- a/substrate/client/consensus/grandpa/rpc/src/lib.rs +++ b/substrate/client/consensus/grandpa/rpc/src/lib.rs @@ -273,7 +273,7 @@ mod tests { let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":0}"#; let (response, _) = rpc.raw_json_request(&request, 1).await.unwrap(); - assert_eq!(expected_response, response.result); + assert_eq!(expected_response, response); } #[tokio::test] @@ -295,7 +295,7 @@ mod tests { let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":0}"#; let (response, _) = rpc.raw_json_request(&request, 1).await.unwrap(); - assert_eq!(expected_response, response.result); + assert_eq!(expected_response, response); } #[tokio::test] @@ -317,7 +317,7 @@ mod tests { .unwrap(); let expected = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; - assert_eq!(response.result, expected); + assert_eq!(response, expected); } fn create_justification() -> GrandpaJustification { diff --git a/substrate/client/consensus/manual-seal/Cargo.toml b/substrate/client/consensus/manual-seal/Cargo.toml index 80eeac4dd1d..51745c996aa 100644 --- a/substrate/client/consensus/manual-seal/Cargo.toml +++ b/substrate/client/consensus/manual-seal/Cargo.toml @@ -16,7 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] } +jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } assert_matches = "1.3.0" async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1" } diff --git a/substrate/client/merkle-mountain-range/rpc/Cargo.toml b/substrate/client/merkle-mountain-range/rpc/Cargo.toml index 99467e5468f..79286cf961d 100644 --- a/substrate/client/merkle-mountain-range/rpc/Cargo.toml +++ b/substrate/client/merkle-mountain-range/rpc/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } -jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] } +jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } serde = { version = "1.0.195", features = ["derive"] } sp-api = { path = "../../../primitives/api" } sp-blockchain = { path = "../../../primitives/blockchain" } diff --git a/substrate/client/rpc-api/Cargo.toml b/substrate/client/rpc-api/Cargo.toml index 8e781151ba8..45f47920e07 100644 --- a/substrate/client/rpc-api/Cargo.toml +++ b/substrate/client/rpc-api/Cargo.toml @@ -28,4 +28,4 @@ sp-core = { path = "../../primitives/core" } sp-rpc = { path = "../../primitives/rpc" } sp-runtime = { path = "../../primitives/runtime" } sp-version = { path = "../../primitives/version" } -jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] } +jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } diff --git a/substrate/client/rpc-servers/Cargo.toml b/substrate/client/rpc-servers/Cargo.toml index 8a0b8f80f43..c8935c36afd 100644 --- a/substrate/client/rpc-servers/Cargo.toml +++ b/substrate/client/rpc-servers/Cargo.toml @@ -16,7 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.20.3", features = ["server"] } +jsonrpsee = { version = "0.22", features = ["server"] } log = { workspace = true, default-features = true } serde_json = "1.0.111" tokio = { version = "1.22.0", features = ["parking_lot"] } @@ -24,3 +24,6 @@ prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../.. tower-http = { version = "0.4.0", features = ["cors"] } tower = { version = "0.4.13", features = ["util"] } http = "0.2.8" +hyper = "0.14.27" +futures = "0.3.29" +pin-project = "1.1.3" diff --git a/substrate/client/rpc-servers/src/lib.rs b/substrate/client/rpc-servers/src/lib.rs index 5d8da190f62..29b34b2945b 100644 --- a/substrate/client/rpc-servers/src/lib.rs +++ b/substrate/client/rpc-servers/src/lib.rs @@ -22,21 +22,32 @@ pub mod middleware; -use std::{error::Error as StdError, net::SocketAddr, time::Duration}; +use std::{convert::Infallible, error::Error as StdError, net::SocketAddr, time::Duration}; use http::header::HeaderValue; +use hyper::{ + server::conn::AddrStream, + service::{make_service_fn, service_fn}, +}; use jsonrpsee::{ - server::middleware::{HostFilterLayer, ProxyGetRequestLayer}, - RpcModule, + server::{ + middleware::{ + http::{HostFilterLayer, ProxyGetRequestLayer}, + rpc::RpcServiceBuilder, + }, + stop_channel, ws, PingConfig, StopHandle, TowerServiceBuilder, + }, + Methods, RpcModule, }; use tokio::net::TcpListener; +use tower::Service; use tower_http::cors::{AllowOrigin, CorsLayer}; -pub use crate::middleware::RpcMetrics; pub use jsonrpsee::core::{ id_providers::{RandomIntegerIdProvider, RandomStringIdProvider}, traits::IdProvider, }; +pub use middleware::{MetricsLayer, RpcMetrics}; const MEGABYTE: u32 = 1024 * 1024; @@ -92,7 +103,7 @@ pub async fn start_server( let local_addr = std_listener.local_addr().ok(); let host_filter = hosts_filtering(cors.is_some(), local_addr); - let middleware = tower::ServiceBuilder::new() + let http_middleware = tower::ServiceBuilder::new() .option_layer(host_filter) // Proxy `GET /health` requests to internal `system_health` method. .layer(ProxyGetRequestLayer::new("/health", "system_health")?) @@ -103,10 +114,15 @@ pub async fn start_server( .max_response_body_size(max_payload_out_mb.saturating_mul(MEGABYTE)) .max_connections(max_connections) .max_subscriptions_per_connection(max_subs_per_conn) - .ping_interval(Duration::from_secs(30)) - .set_middleware(middleware) + .enable_ws_ping( + PingConfig::new() + .ping_interval(Duration::from_secs(30)) + .inactive_limit(Duration::from_secs(60)) + .max_failures(3), + ) + .set_http_middleware(http_middleware) .set_message_buffer_capacity(message_buffer_capacity) - .custom_tokio_runtime(tokio_handle); + .custom_tokio_runtime(tokio_handle.clone()); if let Some(provider) = id_provider { builder = builder.set_id_provider(provider); @@ -114,22 +130,66 @@ pub async fn start_server( builder = builder.set_id_provider(RandomStringIdProvider::new(16)); }; - let rpc_api = build_rpc_api(rpc_api); - let handle = if let Some(metrics) = metrics { - let server = builder.set_logger(metrics).build_from_tcp(std_listener)?; - server.start(rpc_api) - } else { - let server = builder.build_from_tcp(std_listener)?; - server.start(rpc_api) + let (stop_handle, server_handle) = stop_channel(); + let cfg = PerConnection { + methods: build_rpc_api(rpc_api).into(), + service_builder: builder.to_service_builder(), + metrics, + tokio_handle, + stop_handle: stop_handle.clone(), }; + let make_service = make_service_fn(move |_conn: &AddrStream| { + let cfg = cfg.clone(); + + async move { + let cfg = cfg.clone(); + + Ok::<_, Infallible>(service_fn(move |req| { + let PerConnection { service_builder, metrics, tokio_handle, stop_handle, methods } = + cfg.clone(); + + let is_websocket = ws::is_upgrade_request(&req); + let transport_label = if is_websocket { "ws" } else { "http" }; + + let metrics = metrics.map(|m| MetricsLayer::new(m, transport_label)); + let rpc_middleware = RpcServiceBuilder::new().option_layer(metrics.clone()); + let mut svc = + service_builder.set_rpc_middleware(rpc_middleware).build(methods, stop_handle); + + async move { + if is_websocket { + let on_disconnect = svc.on_session_closed(); + + // Spawn a task to handle when the connection is closed. + tokio_handle.spawn(async move { + let now = std::time::Instant::now(); + metrics.as_ref().map(|m| m.ws_connect()); + on_disconnect.await; + metrics.as_ref().map(|m| m.ws_disconnect(now)); + }); + } + + svc.call(req).await + } + })) + } + }); + + let server = hyper::Server::from_tcp(std_listener)?.serve(make_service); + + tokio::spawn(async move { + let graceful = server.with_graceful_shutdown(async move { stop_handle.shutdown().await }); + let _ = graceful.await; + }); + log::info!( "Running JSON-RPC server: addr={}, allowed origins={}", local_addr.map_or_else(|| "unknown".to_string(), |a| a.to_string()), format_cors(cors) ); - Ok(handle) + Ok(server_handle) } fn hosts_filtering(enabled: bool, addr: Option) -> Option { @@ -185,3 +245,12 @@ fn format_cors(maybe_cors: Option<&Vec>) -> String { format!("{:?}", ["*"]) } } + +#[derive(Clone)] +struct PerConnection { + methods: Methods, + stop_handle: StopHandle, + metrics: Option, + tokio_handle: tokio::runtime::Handle, + service_builder: TowerServiceBuilder, +} diff --git a/substrate/client/rpc-servers/src/middleware.rs b/substrate/client/rpc-servers/src/middleware.rs deleted file mode 100644 index fabb64eafa7..00000000000 --- a/substrate/client/rpc-servers/src/middleware.rs +++ /dev/null @@ -1,226 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! RPC middleware to collect prometheus metrics on RPC calls. - -use jsonrpsee::server::logger::{ - HttpRequest, Logger, MethodKind, Params, SuccessOrError, TransportProtocol, -}; -use prometheus_endpoint::{ - register, Counter, CounterVec, HistogramOpts, HistogramVec, Opts, PrometheusError, Registry, - U64, -}; -use std::net::SocketAddr; - -/// Histogram time buckets in microseconds. -const HISTOGRAM_BUCKETS: [f64; 11] = [ - 5.0, - 25.0, - 100.0, - 500.0, - 1_000.0, - 2_500.0, - 10_000.0, - 25_000.0, - 100_000.0, - 1_000_000.0, - 10_000_000.0, -]; - -/// Metrics for RPC middleware storing information about the number of requests started/completed, -/// calls started/completed and their timings. -#[derive(Debug, Clone)] -pub struct RpcMetrics { - /// Number of RPC requests received since the server started. - requests_started: CounterVec, - /// Number of RPC requests completed since the server started. - requests_finished: CounterVec, - /// Histogram over RPC execution times. - calls_time: HistogramVec, - /// Number of calls started. - calls_started: CounterVec, - /// Number of calls completed. - calls_finished: CounterVec, - /// Number of Websocket sessions opened. - ws_sessions_opened: Option>, - /// Number of Websocket sessions closed. - ws_sessions_closed: Option>, -} - -impl RpcMetrics { - /// Create an instance of metrics - pub fn new(metrics_registry: Option<&Registry>) -> Result, PrometheusError> { - if let Some(metrics_registry) = metrics_registry { - Ok(Some(Self { - requests_started: register( - CounterVec::new( - Opts::new( - "substrate_rpc_requests_started", - "Number of RPC requests (not calls) received by the server.", - ), - &["protocol"], - )?, - metrics_registry, - )?, - requests_finished: register( - CounterVec::new( - Opts::new( - "substrate_rpc_requests_finished", - "Number of RPC requests (not calls) processed by the server.", - ), - &["protocol"], - )?, - metrics_registry, - )?, - calls_time: register( - HistogramVec::new( - HistogramOpts::new( - "substrate_rpc_calls_time", - "Total time [μs] of processed RPC calls", - ) - .buckets(HISTOGRAM_BUCKETS.to_vec()), - &["protocol", "method"], - )?, - metrics_registry, - )?, - calls_started: register( - CounterVec::new( - Opts::new( - "substrate_rpc_calls_started", - "Number of received RPC calls (unique un-batched requests)", - ), - &["protocol", "method"], - )?, - metrics_registry, - )?, - calls_finished: register( - CounterVec::new( - Opts::new( - "substrate_rpc_calls_finished", - "Number of processed RPC calls (unique un-batched requests)", - ), - &["protocol", "method", "is_error"], - )?, - metrics_registry, - )?, - ws_sessions_opened: register( - Counter::new( - "substrate_rpc_sessions_opened", - "Number of persistent RPC sessions opened", - )?, - metrics_registry, - )? - .into(), - ws_sessions_closed: register( - Counter::new( - "substrate_rpc_sessions_closed", - "Number of persistent RPC sessions closed", - )?, - metrics_registry, - )? - .into(), - })) - } else { - Ok(None) - } - } -} - -impl Logger for RpcMetrics { - type Instant = std::time::Instant; - - fn on_connect( - &self, - _remote_addr: SocketAddr, - _request: &HttpRequest, - transport: TransportProtocol, - ) { - if let TransportProtocol::WebSocket = transport { - self.ws_sessions_opened.as_ref().map(|counter| counter.inc()); - } - } - - fn on_request(&self, transport: TransportProtocol) -> Self::Instant { - let transport_label = transport_label_str(transport); - let now = std::time::Instant::now(); - self.requests_started.with_label_values(&[transport_label]).inc(); - now - } - - fn on_call(&self, name: &str, params: Params, kind: MethodKind, transport: TransportProtocol) { - let transport_label = transport_label_str(transport); - log::trace!( - target: "rpc_metrics", - "[{}] on_call name={} params={:?} kind={}", - transport_label, - name, - params, - kind, - ); - self.calls_started.with_label_values(&[transport_label, name]).inc(); - } - - fn on_result( - &self, - name: &str, - success_or_error: SuccessOrError, - started_at: Self::Instant, - transport: TransportProtocol, - ) { - let transport_label = transport_label_str(transport); - let micros = started_at.elapsed().as_micros(); - log::debug!( - target: "rpc_metrics", - "[{}] {} call took {} μs", - transport_label, - name, - micros, - ); - self.calls_time.with_label_values(&[transport_label, name]).observe(micros as _); - - self.calls_finished - .with_label_values(&[ - transport_label, - name, - // the label "is_error", so `success` should be regarded as false - // and vice-versa to be registrered correctly. - if success_or_error.is_success() { "false" } else { "true" }, - ]) - .inc(); - } - - fn on_response(&self, result: &str, started_at: Self::Instant, transport: TransportProtocol) { - let transport_label = transport_label_str(transport); - log::trace!(target: "rpc_metrics", "[{}] on_response started_at={:?}", transport_label, started_at); - log::trace!(target: "rpc_metrics::extra", "[{}] result={:?}", transport_label, result); - self.requests_finished.with_label_values(&[transport_label]).inc(); - } - - fn on_disconnect(&self, _remote_addr: SocketAddr, transport: TransportProtocol) { - if let TransportProtocol::WebSocket = transport { - self.ws_sessions_closed.as_ref().map(|counter| counter.inc()); - } - } -} - -fn transport_label_str(t: TransportProtocol) -> &'static str { - match t { - TransportProtocol::Http => "http", - TransportProtocol::WebSocket => "ws", - } -} diff --git a/substrate/client/rpc-servers/src/middleware/metrics.rs b/substrate/client/rpc-servers/src/middleware/metrics.rs new file mode 100644 index 00000000000..c2d1956c3b3 --- /dev/null +++ b/substrate/client/rpc-servers/src/middleware/metrics.rs @@ -0,0 +1,281 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! RPC middleware to collect prometheus metrics on RPC calls. + +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, + time::Instant, +}; + +use jsonrpsee::{server::middleware::rpc::RpcServiceT, types::Request, MethodResponse}; +use pin_project::pin_project; +use prometheus_endpoint::{ + register, Counter, CounterVec, HistogramOpts, HistogramVec, Opts, PrometheusError, Registry, + U64, +}; + +/// Histogram time buckets in microseconds. +const HISTOGRAM_BUCKETS: [f64; 11] = [ + 5.0, + 25.0, + 100.0, + 500.0, + 1_000.0, + 2_500.0, + 10_000.0, + 25_000.0, + 100_000.0, + 1_000_000.0, + 10_000_000.0, +]; + +/// Metrics for RPC middleware storing information about the number of requests started/completed, +/// calls started/completed and their timings. +#[derive(Debug, Clone)] +pub struct RpcMetrics { + /// Histogram over RPC execution times. + calls_time: HistogramVec, + /// Number of calls started. + calls_started: CounterVec, + /// Number of calls completed. + calls_finished: CounterVec, + /// Number of Websocket sessions opened. + ws_sessions_opened: Option>, + /// Number of Websocket sessions closed. + ws_sessions_closed: Option>, + /// Histogram over RPC websocket sessions. + ws_sessions_time: HistogramVec, +} + +impl RpcMetrics { + /// Create an instance of metrics + pub fn new(metrics_registry: Option<&Registry>) -> Result, PrometheusError> { + if let Some(metrics_registry) = metrics_registry { + Ok(Some(Self { + calls_time: register( + HistogramVec::new( + HistogramOpts::new( + "substrate_rpc_calls_time", + "Total time [μs] of processed RPC calls", + ) + .buckets(HISTOGRAM_BUCKETS.to_vec()), + &["protocol", "method"], + )?, + metrics_registry, + )?, + calls_started: register( + CounterVec::new( + Opts::new( + "substrate_rpc_calls_started", + "Number of received RPC calls (unique un-batched requests)", + ), + &["protocol", "method"], + )?, + metrics_registry, + )?, + calls_finished: register( + CounterVec::new( + Opts::new( + "substrate_rpc_calls_finished", + "Number of processed RPC calls (unique un-batched requests)", + ), + &["protocol", "method", "is_error"], + )?, + metrics_registry, + )?, + ws_sessions_opened: register( + Counter::new( + "substrate_rpc_sessions_opened", + "Number of persistent RPC sessions opened", + )?, + metrics_registry, + )? + .into(), + ws_sessions_closed: register( + Counter::new( + "substrate_rpc_sessions_closed", + "Number of persistent RPC sessions closed", + )?, + metrics_registry, + )? + .into(), + ws_sessions_time: register( + HistogramVec::new( + HistogramOpts::new( + "substrate_rpc_sessions_time", + "Total time [s] for each websocket session", + ) + .buckets(HISTOGRAM_BUCKETS.to_vec()), + &["protocol"], + )?, + metrics_registry, + )?, + })) + } else { + Ok(None) + } + } + + pub(crate) fn ws_connect(&self) { + self.ws_sessions_opened.as_ref().map(|counter| counter.inc()); + } + + pub(crate) fn ws_disconnect(&self, now: Instant) { + let micros = now.elapsed().as_secs(); + + self.ws_sessions_closed.as_ref().map(|counter| counter.inc()); + self.ws_sessions_time.with_label_values(&["ws"]).observe(micros as _); + } +} + +/// Metrics layer. +#[derive(Clone)] +pub struct MetricsLayer { + inner: RpcMetrics, + transport_label: &'static str, +} + +impl MetricsLayer { + /// Create a new [`MetricsLayer`]. + pub fn new(metrics: RpcMetrics, transport_label: &'static str) -> Self { + Self { inner: metrics, transport_label } + } + + pub(crate) fn ws_connect(&self) { + self.inner.ws_connect(); + } + + pub(crate) fn ws_disconnect(&self, now: Instant) { + self.inner.ws_disconnect(now) + } +} + +impl tower::Layer for MetricsLayer { + type Service = Metrics; + + fn layer(&self, inner: S) -> Self::Service { + Metrics::new(inner, self.inner.clone(), self.transport_label) + } +} + +/// Metrics middleware. +#[derive(Clone)] +pub struct Metrics { + service: S, + metrics: RpcMetrics, + transport_label: &'static str, +} + +impl Metrics { + /// Create a new metrics middleware. + pub fn new(service: S, metrics: RpcMetrics, transport_label: &'static str) -> Metrics { + Metrics { service, metrics, transport_label } + } +} + +impl<'a, S> RpcServiceT<'a> for Metrics +where + S: Send + Sync + RpcServiceT<'a>, +{ + type Future = ResponseFuture<'a, S::Future>; + + fn call(&self, req: Request<'a>) -> Self::Future { + let now = Instant::now(); + + log::trace!( + target: "rpc_metrics", + "[{}] on_call name={} params={:?}", + self.transport_label, + req.method_name(), + req.params(), + ); + self.metrics + .calls_started + .with_label_values(&[self.transport_label, req.method_name()]) + .inc(); + + ResponseFuture { + fut: self.service.call(req.clone()), + metrics: self.metrics.clone(), + req, + now, + transport_label: self.transport_label, + } + } +} + +/// Response future for metrics. +#[pin_project] +pub struct ResponseFuture<'a, F> { + #[pin] + fut: F, + metrics: RpcMetrics, + req: Request<'a>, + now: Instant, + transport_label: &'static str, +} + +impl<'a, F> std::fmt::Debug for ResponseFuture<'a, F> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("ResponseFuture") + } +} + +impl<'a, F: Future> Future for ResponseFuture<'a, F> { + type Output = F::Output; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + + let res = this.fut.poll(cx); + if let Poll::Ready(rp) = &res { + let method_name = this.req.method_name(); + let transport_label = &this.transport_label; + let now = this.now; + let metrics = &this.metrics; + + log::trace!(target: "rpc_metrics", "[{transport_label}] on_response started_at={:?}", now); + log::trace!(target: "rpc_metrics::extra", "[{transport_label}] result={:?}", rp); + + let micros = now.elapsed().as_micros(); + log::debug!( + target: "rpc_metrics", + "[{transport_label}] {method_name} call took {} μs", + micros, + ); + metrics + .calls_time + .with_label_values(&[transport_label, method_name]) + .observe(micros as _); + metrics + .calls_finished + .with_label_values(&[ + transport_label, + method_name, + // the label "is_error", so `success` should be regarded as false + // and vice-versa to be registrered correctly. + if rp.is_success() { "false" } else { "true" }, + ]) + .inc(); + } + res + } +} diff --git a/substrate/client/rpc-servers/src/middleware/mod.rs b/substrate/client/rpc-servers/src/middleware/mod.rs new file mode 100644 index 00000000000..1c193058244 --- /dev/null +++ b/substrate/client/rpc-servers/src/middleware/mod.rs @@ -0,0 +1,23 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! JSON-RPC specific middleware. + +pub mod metrics; + +pub use metrics::*; diff --git a/substrate/client/rpc-spec-v2/Cargo.toml b/substrate/client/rpc-spec-v2/Cargo.toml index 1b7870764dc..8269b631bdd 100644 --- a/substrate/client/rpc-spec-v2/Cargo.toml +++ b/substrate/client/rpc-spec-v2/Cargo.toml @@ -16,7 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] } +jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } # Internal chain structures for "chain_spec". sc-chain-spec = { path = "../chain-spec" } # Pool for submitting extrinsics required by "transaction" diff --git a/substrate/client/rpc-spec-v2/src/archive/tests.rs b/substrate/client/rpc-spec-v2/src/archive/tests.rs index 09b2410eac6..1803ffa3a31 100644 --- a/substrate/client/rpc-spec-v2/src/archive/tests.rs +++ b/substrate/client/rpc-spec-v2/src/archive/tests.rs @@ -32,8 +32,7 @@ use super::{ use assert_matches::assert_matches; use codec::{Decode, Encode}; use jsonrpsee::{ - core::{EmptyServerParams as EmptyParams, Error}, - rpc_params, RpcModule, + core::EmptyServerParams as EmptyParams, rpc_params, MethodsError as Error, RpcModule, }; use sc_block_builder::BlockBuilderBuilder; use sc_client_api::ChildInfo; @@ -294,7 +293,7 @@ async fn archive_call() { ) .await .unwrap_err(); - assert_matches!(err, Error::Call(err) if err.code() == 3001 && err.message().contains("Invalid parameter")); + assert_matches!(err, Error::JsonRpc(err) if err.code() == 3001 && err.message().contains("Invalid parameter")); // Pass an invalid parameters that cannot be decode. let err = api @@ -305,7 +304,7 @@ async fn archive_call() { ) .await .unwrap_err(); - assert_matches!(err, Error::Call(err) if err.code() == 3001 && err.message().contains("Invalid parameter")); + assert_matches!(err, Error::JsonRpc(err) if err.code() == 3001 && err.message().contains("Invalid parameter")); // Invalid hash. let result: MethodResult = api diff --git a/substrate/client/rpc-spec-v2/src/chain_head/api.rs b/substrate/client/rpc-spec-v2/src/chain_head/api.rs index 7c3b8d81c82..00000e1fb27 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/api.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/api.rs @@ -26,7 +26,7 @@ use crate::{ }, common::events::StorageQuery, }; -use jsonrpsee::proc_macros::rpc; +use jsonrpsee::{proc_macros::rpc, server::ResponsePayload}; use sp_rpc::list::ListOrValue; #[rpc(client, server)] @@ -59,7 +59,7 @@ pub trait ChainHeadApi { &self, follow_subscription: String, hash: Hash, - ) -> Result; + ) -> ResponsePayload<'static, MethodResponse>; /// Retrieves the header of a pinned block. /// @@ -92,7 +92,7 @@ pub trait ChainHeadApi { hash: Hash, items: Vec>, child_trie: Option, - ) -> Result; + ) -> ResponsePayload<'static, MethodResponse>; /// Call into the Runtime API at a specified block's state. /// @@ -106,7 +106,7 @@ pub trait ChainHeadApi { hash: Hash, function: String, call_parameters: String, - ) -> Result; + ) -> ResponsePayload<'static, MethodResponse>; /// Unpin a block or multiple blocks reported by the `follow` method. /// diff --git a/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs b/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs index bcca1dc18e2..2bda22b4523 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs @@ -36,7 +36,8 @@ use crate::{ use codec::Encode; use futures::future::FutureExt; use jsonrpsee::{ - core::async_trait, types::SubscriptionId, PendingSubscriptionSink, SubscriptionSink, + core::async_trait, server::ResponsePayload, types::SubscriptionId, MethodResponseFuture, + PendingSubscriptionSink, SubscriptionSink, }; use log::debug; use sc_client_api::{ @@ -218,16 +219,17 @@ where &self, follow_subscription: String, hash: Block::Hash, - ) -> Result { + ) -> ResponsePayload<'static, MethodResponse> { let mut block_guard = match self.subscriptions.lock_block(&follow_subscription, hash, 1) { Ok(block) => block, Err(SubscriptionManagementError::SubscriptionAbsent) | - Err(SubscriptionManagementError::ExceededLimits) => return Ok(MethodResponse::LimitReached), + Err(SubscriptionManagementError::ExceededLimits) => + return ResponsePayload::success(MethodResponse::LimitReached), Err(SubscriptionManagementError::BlockHashAbsent) => { // Block is not part of the subscription. - return Err(ChainHeadRpcError::InvalidBlock.into()) + return ResponsePayload::error(ChainHeadRpcError::InvalidBlock); }, - Err(_) => return Err(ChainHeadRpcError::InvalidBlock.into()), + Err(_) => return ResponsePayload::error(ChainHeadRpcError::InvalidBlock), }; let operation_id = block_guard.operation().operation_id(); @@ -254,7 +256,7 @@ where hash ); self.subscriptions.remove_subscription(&follow_subscription); - return Err(ChainHeadRpcError::InvalidBlock.into()) + return ResponsePayload::error(ChainHeadRpcError::InvalidBlock) }, Err(error) => FollowEvent::::OperationError(OperationError { operation_id: operation_id.clone(), @@ -262,8 +264,20 @@ where }), }; - let _ = block_guard.response_sender().unbounded_send(event); - Ok(MethodResponse::Started(MethodResponseStarted { operation_id, discarded_items: None })) + let (rp, rp_fut) = method_started_response(operation_id, None); + + let fut = async move { + // Events should only by generated + // if the response was successfully propagated. + if rp_fut.await.is_err() { + return; + } + let _ = block_guard.response_sender().unbounded_send(event); + }; + + self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); + + rp } fn chain_head_unstable_header( @@ -294,31 +308,40 @@ where hash: Block::Hash, items: Vec>, child_trie: Option, - ) -> Result { + ) -> ResponsePayload<'static, MethodResponse> { // Gain control over parameter parsing and returned error. - let items = items + let items = match items .into_iter() .map(|query| { let key = StorageKey(parse_hex_param(query.key)?); Ok(StorageQuery { key, query_type: query.query_type }) }) - .collect::, ChainHeadRpcError>>()?; + .collect::, ChainHeadRpcError>>() + { + Ok(items) => items, + Err(err) => { + return ResponsePayload::error(err); + }, + }; - let child_trie = child_trie - .map(|child_trie| parse_hex_param(child_trie)) - .transpose()? - .map(ChildInfo::new_default_from_vec); + let child_trie = match child_trie.map(|child_trie| parse_hex_param(child_trie)).transpose() + { + Ok(c) => c.map(ChildInfo::new_default_from_vec), + Err(e) => return ResponsePayload::error(e), + }; let mut block_guard = match self.subscriptions.lock_block(&follow_subscription, hash, items.len()) { Ok(block) => block, Err(SubscriptionManagementError::SubscriptionAbsent) | - Err(SubscriptionManagementError::ExceededLimits) => return Ok(MethodResponse::LimitReached), + Err(SubscriptionManagementError::ExceededLimits) => { + return ResponsePayload::success(MethodResponse::LimitReached); + }, Err(SubscriptionManagementError::BlockHashAbsent) => { // Block is not part of the subscription. - return Err(ChainHeadRpcError::InvalidBlock.into()) + return ResponsePayload::error(ChainHeadRpcError::InvalidBlock) }, - Err(_) => return Err(ChainHeadRpcError::InvalidBlock.into()), + Err(_) => return ResponsePayload::error(ChainHeadRpcError::InvalidBlock), }; let mut storage_client = ChainHeadStorage::::new( @@ -334,16 +357,21 @@ where let mut items = items; items.truncate(num_operations); + let (rp, rp_is_success) = method_started_response(operation_id, Some(discarded)); + let fut = async move { + // Events should only by generated + // if the response was successfully propagated. + if rp_is_success.await.is_err() { + return; + } storage_client.generate_events(block_guard, hash, items, child_trie).await; }; self.executor .spawn_blocking("substrate-rpc-subscription", Some("rpc"), fut.boxed()); - Ok(MethodResponse::Started(MethodResponseStarted { - operation_id, - discarded_items: Some(discarded), - })) + + rp } fn chain_head_unstable_call( @@ -352,29 +380,31 @@ where hash: Block::Hash, function: String, call_parameters: String, - ) -> Result { - let call_parameters = Bytes::from(parse_hex_param(call_parameters)?); + ) -> ResponsePayload<'static, MethodResponse> { + let call_parameters = match parse_hex_param(call_parameters) { + Ok(hex) => Bytes::from(hex), + Err(err) => return ResponsePayload::error(err), + }; let mut block_guard = match self.subscriptions.lock_block(&follow_subscription, hash, 1) { Ok(block) => block, Err(SubscriptionManagementError::SubscriptionAbsent) | Err(SubscriptionManagementError::ExceededLimits) => { // Invalid invalid subscription ID. - return Ok(MethodResponse::LimitReached) + return ResponsePayload::success(MethodResponse::LimitReached) }, Err(SubscriptionManagementError::BlockHashAbsent) => { // Block is not part of the subscription. - return Err(ChainHeadRpcError::InvalidBlock.into()) + return ResponsePayload::error(ChainHeadRpcError::InvalidBlock) }, - Err(_) => return Err(ChainHeadRpcError::InvalidBlock.into()), + Err(_) => return ResponsePayload::error(ChainHeadRpcError::InvalidBlock), }; // Reject subscription if with_runtime is false. if !block_guard.has_runtime() { - return Err(ChainHeadRpcError::InvalidRuntimeCall( + return ResponsePayload::error(ChainHeadRpcError::InvalidRuntimeCall( "The runtime updates flag must be set".to_string(), - ) - .into()) + )); } let operation_id = block_guard.operation().operation_id(); @@ -395,11 +425,20 @@ where }) }); - let _ = block_guard.response_sender().unbounded_send(event); - Ok(MethodResponse::Started(MethodResponseStarted { - operation_id: operation_id.clone(), - discarded_items: None, - })) + let (rp, rp_fut) = method_started_response(operation_id, None); + + let fut = async move { + // Events should only by generated + // if the response was successfully propagated. + if rp_fut.await.is_err() { + return; + } + let _ = block_guard.response_sender().unbounded_send(event); + }; + + self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); + + rp } fn chain_head_unstable_unpin( @@ -463,3 +502,11 @@ where Ok(()) } } + +fn method_started_response( + operation_id: String, + discarded_items: Option, +) -> (ResponsePayload<'static, MethodResponse>, MethodResponseFuture) { + let rp = MethodResponse::Started(MethodResponseStarted { operation_id, discarded_items }); + ResponsePayload::success(rp).notify_on_completion() +} diff --git a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs index ccb928262e4..9544736d84c 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs @@ -27,8 +27,7 @@ use assert_matches::assert_matches; use codec::{Decode, Encode}; use futures::Future; use jsonrpsee::{ - core::{error::Error, server::Subscription as RpcSubscription}, - rpc_params, RpcModule, + core::server::Subscription as RpcSubscription, rpc_params, MethodsError as Error, RpcModule, }; use sc_block_builder::BlockBuilderBuilder; use sc_client_api::ChildInfo; @@ -359,7 +358,7 @@ async fn get_header() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" + Error::JsonRpc(ref err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" ); // Obtain the valid header. @@ -388,7 +387,7 @@ async fn get_body() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" + Error::JsonRpc(ref err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" ); // Valid call. @@ -473,7 +472,7 @@ async fn call_runtime() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" + Error::JsonRpc(ref err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" ); // Pass an invalid parameters that cannot be decode. @@ -486,7 +485,7 @@ async fn call_runtime() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::json_rpc_spec::INVALID_PARAM_ERROR && err.message().contains("Invalid parameter") + Error::JsonRpc(err) if err.code() == super::error::json_rpc_spec::INVALID_PARAM_ERROR && err.message().contains("Invalid parameter") ); // Valid call. @@ -589,7 +588,7 @@ async fn call_runtime_without_flag() { .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_RUNTIME_CALL && err.message().contains("subscription was started with `withRuntime` set to `false`") + Error::JsonRpc(ref err) if err.code() == super::error::rpc_spec_v2::INVALID_RUNTIME_CALL && err.message().contains("subscription was started with `withRuntime` set to `false`") ); } @@ -627,7 +626,7 @@ async fn get_storage_hash() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" + Error::JsonRpc(ref err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" ); // Valid call without storage at the key. @@ -895,7 +894,7 @@ async fn get_storage_value() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" + Error::JsonRpc(ref err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" ); // Valid call without storage at the key. @@ -1571,7 +1570,7 @@ async fn follow_with_unpin() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" + Error::JsonRpc(ref err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" ); // To not exceed the number of pinned blocks, we need to unpin before the next import. @@ -1674,7 +1673,7 @@ async fn unpin_duplicate_hashes() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_DUPLICATE_HASHES && err.message() == "Received duplicate hashes for the `chainHead_unpin` method" + Error::JsonRpc(err) if err.code() == super::error::rpc_spec_v2::INVALID_DUPLICATE_HASHES && err.message() == "Received duplicate hashes for the `chainHead_unpin` method" ); // Block tree: @@ -1709,7 +1708,7 @@ async fn unpin_duplicate_hashes() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_DUPLICATE_HASHES && err.message() == "Received duplicate hashes for the `chainHead_unpin` method" + Error::JsonRpc(err) if err.code() == super::error::rpc_spec_v2::INVALID_DUPLICATE_HASHES && err.message() == "Received duplicate hashes for the `chainHead_unpin` method" ); // Can unpin blocks. @@ -1822,7 +1821,7 @@ async fn follow_with_multiple_unpin_hashes() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" + Error::JsonRpc(ref err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" ); let _res: () = api @@ -1839,7 +1838,7 @@ async fn follow_with_multiple_unpin_hashes() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" + Error::JsonRpc(ref err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" ); // Unpin multiple blocks. @@ -1857,7 +1856,7 @@ async fn follow_with_multiple_unpin_hashes() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" + Error::JsonRpc(ref err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" ); let err = api @@ -1868,7 +1867,7 @@ async fn follow_with_multiple_unpin_hashes() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" + Error::JsonRpc(ref err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" ); } diff --git a/substrate/client/rpc-spec-v2/src/transaction/tests.rs b/substrate/client/rpc-spec-v2/src/transaction/tests.rs index 45477494768..382f5adeae1 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/tests.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/tests.rs @@ -24,7 +24,7 @@ use crate::{ use assert_matches::assert_matches; use codec::Encode; use futures::Future; -use jsonrpsee::{core::error::Error, rpc_params, RpcModule}; +use jsonrpsee::{rpc_params, MethodsError as Error, RpcModule}; use sc_transaction_pool::*; use sc_transaction_pool_api::{ChainEvent, MaintainedTransactionPool, TransactionPool}; use sp_core::{testing::TaskExecutor, traits::SpawnNamed}; @@ -194,7 +194,7 @@ async fn tx_broadcast_invalid_tx() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::json_rpc_spec::INVALID_PARAM_ERROR && err.message() == "Invalid params" + Error::JsonRpc(err) if err.code() == super::error::json_rpc_spec::INVALID_PARAM_ERROR && err.message() == "Invalid params" ); assert_eq!(0, pool.status().ready); @@ -219,7 +219,7 @@ async fn tx_broadcast_invalid_tx() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::json_rpc_spec::INVALID_PARAM_ERROR && err.message() == "Invalid operation id" + Error::JsonRpc(err) if err.code() == super::error::json_rpc_spec::INVALID_PARAM_ERROR && err.message() == "Invalid operation id" ); } @@ -233,6 +233,6 @@ async fn tx_invalid_stop() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::json_rpc_spec::INVALID_PARAM_ERROR && err.message() == "Invalid operation id" + Error::JsonRpc(err) if err.code() == super::error::json_rpc_spec::INVALID_PARAM_ERROR && err.message() == "Invalid operation id" ); } diff --git a/substrate/client/rpc/Cargo.toml b/substrate/client/rpc/Cargo.toml index a39d9226b62..5726c66e8b3 100644 --- a/substrate/client/rpc/Cargo.toml +++ b/substrate/client/rpc/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" -jsonrpsee = { version = "0.20.3", features = ["server"] } +jsonrpsee = { version = "0.22", features = ["server"] } log = { workspace = true, default-features = true } parking_lot = "0.12.1" serde_json = "1.0.111" diff --git a/substrate/client/rpc/src/author/tests.rs b/substrate/client/rpc/src/author/tests.rs index 471016a015d..937870eb53f 100644 --- a/substrate/client/rpc/src/author/tests.rs +++ b/substrate/client/rpc/src/author/tests.rs @@ -21,10 +21,7 @@ use super::*; use crate::testing::{test_executor, timeout_secs}; use assert_matches::assert_matches; use codec::Encode; -use jsonrpsee::{ - core::{EmptyServerParams as EmptyParams, Error as RpcError}, - RpcModule, -}; +use jsonrpsee::{core::EmptyServerParams as EmptyParams, MethodsError as RpcError, RpcModule}; use sc_transaction_pool::{BasicPool, FullChainApi}; use sc_transaction_pool_api::TransactionStatus; use sp_core::{ @@ -103,7 +100,7 @@ async fn author_submit_transaction_should_not_cause_error() { assert_matches!( api.call::<_, H256>("author_submitExtrinsic", [xt]).await, - Err(RpcError::Call(err)) if err.message().contains("Already Imported") && err.code() == 1013 + Err(RpcError::JsonRpc(err)) if err.message().contains("Already Imported") && err.code() == 1013 ); } @@ -160,7 +157,7 @@ async fn author_should_return_watch_validation_error() { assert_matches!( failed_sub, - Err(RpcError::Call(err)) if err.message().contains("Invalid Transaction") && err.code() == 1010 + Err(RpcError::JsonRpc(err)) if err.message().contains("Invalid Transaction") && err.code() == 1010 ); } @@ -276,7 +273,7 @@ async fn author_has_session_keys() { assert_matches!( api.call::<_, bool>("author_hasSessionKeys", vec![Bytes::from(vec![1, 2, 3])]).await, - Err(RpcError::Call(err)) if err.message().contains("Session keys are not encoded correctly") + Err(RpcError::JsonRpc(err)) if err.message().contains("Session keys are not encoded correctly") ); } diff --git a/substrate/client/rpc/src/dev/tests.rs b/substrate/client/rpc/src/dev/tests.rs index 5eb4897056c..e8f9ba4990d 100644 --- a/substrate/client/rpc/src/dev/tests.rs +++ b/substrate/client/rpc/src/dev/tests.rs @@ -100,7 +100,7 @@ async fn deny_unsafe_works() { let (resp, _) = api.raw_json_request(&request, 1).await.expect("Raw calls should succeed"); assert_eq!( - resp.result, + resp, r#"{"jsonrpc":"2.0","error":{"code":-32601,"message":"RPC call is unsafe to be called externally"},"id":1}"# ); } diff --git a/substrate/client/rpc/src/state/tests.rs b/substrate/client/rpc/src/state/tests.rs index 25a34faed9a..96f4c1be960 100644 --- a/substrate/client/rpc/src/state/tests.rs +++ b/substrate/client/rpc/src/state/tests.rs @@ -21,7 +21,7 @@ use super::*; use crate::testing::{test_executor, timeout_secs}; use assert_matches::assert_matches; use futures::executor; -use jsonrpsee::core::{EmptyServerParams as EmptyParams, Error as RpcError}; +use jsonrpsee::{core::EmptyServerParams as EmptyParams, MethodsError as RpcError}; use sc_block_builder::BlockBuilderBuilder; use sc_rpc_api::DenyUnsafe; use sp_consensus::BlockOrigin; @@ -525,7 +525,7 @@ async fn wildcard_storage_subscriptions_are_rpc_unsafe() { let api_rpc = api.into_rpc(); let err = api_rpc.subscribe_unbounded("state_subscribeStorage", EmptyParams::new()).await; - assert_matches!(err, Err(RpcError::Call(e)) if e.message() == "RPC call is unsafe to be called externally"); + assert_matches!(err, Err(RpcError::JsonRpc(e)) if e.message() == "RPC call is unsafe to be called externally"); } #[tokio::test] diff --git a/substrate/client/rpc/src/system/tests.rs b/substrate/client/rpc/src/system/tests.rs index 21d13ccfafa..03967c63523 100644 --- a/substrate/client/rpc/src/system/tests.rs +++ b/substrate/client/rpc/src/system/tests.rs @@ -19,10 +19,7 @@ use super::{helpers::SyncState, *}; use assert_matches::assert_matches; use futures::prelude::*; -use jsonrpsee::{ - core::{EmptyServerParams as EmptyParams, Error as RpcError}, - RpcModule, -}; +use jsonrpsee::{core::EmptyServerParams as EmptyParams, MethodsError as RpcError, RpcModule}; use sc_network::{self, config::Role, PeerId}; use sc_rpc_api::system::helpers::PeerInfo; use sc_utils::mpsc::tracing_unbounded; @@ -311,7 +308,7 @@ async fn system_network_add_reserved() { let bad_peer_id = ["/ip4/198.51.100.19/tcp/30333"]; assert_matches!( api(None).call::<_, ()>("system_addReservedPeer", bad_peer_id).await, - Err(RpcError::Call(err)) if err.message().contains("Peer id is missing from the address") + Err(RpcError::JsonRpc(err)) if err.message().contains("Peer id is missing from the address") ); } @@ -327,7 +324,7 @@ async fn system_network_remove_reserved() { assert_matches!( api(None).call::<_, String>("system_removeReservedPeer", bad_peer_id).await, - Err(RpcError::Call(err)) if err.message().contains("base-58 decode error: provided string contained invalid character '/' at byte 0") + Err(RpcError::JsonRpc(err)) if err.message().contains("base-58 decode error: provided string contained invalid character '/' at byte 0") ); } #[tokio::test] diff --git a/substrate/client/rpc/src/utils.rs b/substrate/client/rpc/src/utils.rs index b5ae4a2b6bc..6ec48efef84 100644 --- a/substrate/client/rpc/src/utils.rs +++ b/substrate/client/rpc/src/utils.rs @@ -80,7 +80,7 @@ where Either::Left((Ok(sink), _)) => break sink, Either::Right((Some(msg), f)) => { if buf.push_back(msg).is_err() { - log::warn!(target: "rpc", "Subscription::accept failed buffer limit={} exceed; dropping subscription", buf.max_cap); + log::warn!(target: "rpc", "Subscription::accept failed buffer limit={} exceeded; dropping subscription", buf.max_cap); return } accept_fut = f; @@ -125,7 +125,13 @@ async fn inner_pipe_from_stream( // New item from the stream Either::Right((Either::Right((Some(v), n)), c)) => { if buf.push_back(v).is_err() { - log::warn!(target: "rpc", "Subscription buffer limit={} exceed; dropping subscription", buf.max_cap); + log::warn!( + target: "rpc", + "Subscription buffer limit={} exceeded for subscription={} conn_id={}; dropping subscription", + buf.max_cap, + sink.method_name(), + sink.connection_id() + ); return } diff --git a/substrate/client/service/Cargo.toml b/substrate/client/service/Cargo.toml index 41ec22a97c2..fd7a8cc1a11 100644 --- a/substrate/client/service/Cargo.toml +++ b/substrate/client/service/Cargo.toml @@ -28,7 +28,7 @@ runtime-benchmarks = [ ] [dependencies] -jsonrpsee = { version = "0.20.3", features = ["server"] } +jsonrpsee = { version = "0.22", features = ["server"] } thiserror = "1.0.48" futures = "0.3.21" rand = "0.8.5" diff --git a/substrate/client/service/src/lib.rs b/substrate/client/service/src/lib.rs index a76e1544bfb..1fbfd14c3be 100644 --- a/substrate/client/service/src/lib.rs +++ b/substrate/client/service/src/lib.rs @@ -38,7 +38,7 @@ use std::{collections::HashMap, net::SocketAddr}; use codec::{Decode, Encode}; use futures::{pin_mut, FutureExt, StreamExt}; -use jsonrpsee::{core::Error as JsonRpseeError, RpcModule}; +use jsonrpsee::RpcModule; use log::{debug, error, warn}; use sc_client_api::{blockchain::HeaderBackend, BlockBackend, BlockchainEvents, ProofProvider}; use sc_network::{ @@ -109,17 +109,14 @@ impl RpcHandlers { pub async fn rpc_query( &self, json_query: &str, - ) -> Result<(String, tokio::sync::mpsc::Receiver), JsonRpseeError> { + ) -> Result<(String, tokio::sync::mpsc::Receiver), serde_json::Error> { // Because `tokio::sync::mpsc::channel` is used under the hood // it will panic if it's set to usize::MAX. // // This limit is used to prevent panics and is large enough. const TOKIO_MPSC_MAX_SIZE: usize = tokio::sync::Semaphore::MAX_PERMITS; - self.0 - .raw_json_request(json_query, TOKIO_MPSC_MAX_SIZE) - .await - .map(|(method_res, recv)| (method_res.result, recv)) + self.0.raw_json_request(json_query, TOKIO_MPSC_MAX_SIZE).await } /// Provides access to the underlying `RpcModule` diff --git a/substrate/client/sync-state-rpc/Cargo.toml b/substrate/client/sync-state-rpc/Cargo.toml index 328ecd17f75..0d22bb105a6 100644 --- a/substrate/client/sync-state-rpc/Cargo.toml +++ b/substrate/client/sync-state-rpc/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } -jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] } +jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } serde = { version = "1.0.195", features = ["derive"] } serde_json = "1.0.111" thiserror = "1.0.48" diff --git a/substrate/frame/transaction-payment/rpc/Cargo.toml b/substrate/frame/transaction-payment/rpc/Cargo.toml index e7edf8a4b87..6d7f632af82 100644 --- a/substrate/frame/transaction-payment/rpc/Cargo.toml +++ b/substrate/frame/transaction-payment/rpc/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } -jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] } +jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } pallet-transaction-payment-rpc-runtime-api = { path = "runtime-api" } sp-api = { path = "../../../primitives/api" } sp-blockchain = { path = "../../../primitives/blockchain" } diff --git a/substrate/utils/frame/remote-externalities/Cargo.toml b/substrate/utils/frame/remote-externalities/Cargo.toml index 0fee718d832..a3376c7af40 100644 --- a/substrate/utils/frame/remote-externalities/Cargo.toml +++ b/substrate/utils/frame/remote-externalities/Cargo.toml @@ -15,7 +15,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.20.3", features = ["http-client"] } +jsonrpsee = { version = "0.22", features = ["http-client"] } codec = { package = "parity-scale-codec", version = "3.6.1" } log = { workspace = true, default-features = true } serde = "1.0.195" diff --git a/substrate/utils/frame/rpc/client/Cargo.toml b/substrate/utils/frame/rpc/client/Cargo.toml index d1959fe9452..0057bf6eb50 100644 --- a/substrate/utils/frame/rpc/client/Cargo.toml +++ b/substrate/utils/frame/rpc/client/Cargo.toml @@ -15,7 +15,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.20.3", features = ["ws-client"] } +jsonrpsee = { version = "0.22", features = ["ws-client"] } sc-rpc-api = { path = "../../../../client/rpc-api" } async-trait = "0.1.74" serde = "1" diff --git a/substrate/utils/frame/rpc/client/src/lib.rs b/substrate/utils/frame/rpc/client/src/lib.rs index 9349ee2d357..221f260b156 100644 --- a/substrate/utils/frame/rpc/client/src/lib.rs +++ b/substrate/utils/frame/rpc/client/src/lib.rs @@ -44,9 +44,9 @@ use std::collections::VecDeque; pub use jsonrpsee::{ core::{ - client::{ClientT, Subscription, SubscriptionClientT}, + client::{ClientT, Error, Subscription, SubscriptionClientT}, params::BatchRequestBuilder, - Error, RpcResult, + RpcResult, }, rpc_params, ws_client::{WsClient, WsClientBuilder}, diff --git a/substrate/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml b/substrate/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml index 83afff94a8d..b2004bfcacd 100644 --- a/substrate/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml +++ b/substrate/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml @@ -23,7 +23,8 @@ sp-core = { path = "../../../../primitives/core" } sp-state-machine = { path = "../../../../primitives/state-machine" } sp-trie = { path = "../../../../primitives/trie" } trie-db = "0.28.0" -jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] } + +jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } # Substrate Dependencies sc-client-api = { path = "../../../../client/api" } diff --git a/substrate/utils/frame/rpc/support/Cargo.toml b/substrate/utils/frame/rpc/support/Cargo.toml index 3750e3272c9..a4b0a7777fb 100644 --- a/substrate/utils/frame/rpc/support/Cargo.toml +++ b/substrate/utils/frame/rpc/support/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } -jsonrpsee = { version = "0.20.3", features = ["jsonrpsee-types"] } +jsonrpsee = { version = "0.22", features = ["jsonrpsee-types"] } serde = "1" frame-support = { path = "../../../../frame/support" } sc-rpc-api = { path = "../../../../client/rpc-api" } @@ -24,7 +24,7 @@ sp-storage = { path = "../../../../primitives/storage" } [dev-dependencies] scale-info = "2.10.0" -jsonrpsee = { version = "0.20.3", features = ["jsonrpsee-types", "ws-client"] } +jsonrpsee = { version = "0.22", features = ["jsonrpsee-types", "ws-client"] } tokio = "1.22.0" sp-core = { path = "../../../../primitives/core" } sp-runtime = { path = "../../../../primitives/runtime" } diff --git a/substrate/utils/frame/rpc/support/src/lib.rs b/substrate/utils/frame/rpc/support/src/lib.rs index e3ccbd69658..8280c46aadf 100644 --- a/substrate/utils/frame/rpc/support/src/lib.rs +++ b/substrate/utils/frame/rpc/support/src/lib.rs @@ -23,7 +23,7 @@ use codec::{DecodeAll, FullCodec, FullEncode}; use core::marker::PhantomData; use frame_support::storage::generator::{StorageDoubleMap, StorageMap, StorageValue}; -use jsonrpsee::core::Error as RpcError; +use jsonrpsee::core::ClientError as RpcError; use sc_rpc_api::state::StateApiClient; use serde::{de::DeserializeOwned, Serialize}; use sp_storage::{StorageData, StorageKey}; @@ -31,7 +31,7 @@ use sp_storage::{StorageData, StorageKey}; /// A typed query on chain state usable from an RPC client. /// /// ```no_run -/// # use jsonrpsee::core::Error as RpcError; +/// # use jsonrpsee::core::ClientError as RpcError; /// # use jsonrpsee::ws_client::WsClientBuilder; /// # use codec::Encode; /// # use frame_support::{construct_runtime, derive_impl, traits::ConstU32}; diff --git a/substrate/utils/frame/rpc/system/Cargo.toml b/substrate/utils/frame/rpc/system/Cargo.toml index 8097a4bf3d3..f9a84a01af8 100644 --- a/substrate/utils/frame/rpc/system/Cargo.toml +++ b/substrate/utils/frame/rpc/system/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } -jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] } +jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } futures = "0.3.21" log = { workspace = true, default-features = true } frame-system-rpc-runtime-api = { path = "../../../../frame/system/rpc/runtime-api" } -- GitLab From 5cfc05d5182ca22c45fd0910b86a4d316c4a11f3 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 14 Feb 2024 23:24:36 +0100 Subject: [PATCH 063/133] Use simple-mermaid from crates-io (#3304) Re https://github.com/paritytech/polkadot-sdk/issues/2922 Changes: - Use crates-io version of simple-mermaid as [v0.1.1](https://github.com/glueball/simple-mermaid/releases/tag/v0.1.1) supports no-std. - Remove from `frame` since i did not see it used. --------- Signed-off-by: Oliver Tale-Yazdi --- Cargo.lock | 6 +++--- docs/sdk/Cargo.toml | 2 +- substrate/frame/Cargo.toml | 2 -- substrate/primitives/runtime/Cargo.toml | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c29c5646bbe..8d9c1c84c24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5438,7 +5438,6 @@ dependencies = [ "pallet-examples", "parity-scale-codec", "scale-info", - "simple-mermaid", "sp-api", "sp-arithmetic", "sp-block-builder", @@ -17488,8 +17487,9 @@ dependencies = [ [[package]] name = "simple-mermaid" -version = "0.1.0" -source = "git+https://github.com/kianenigma/simple-mermaid.git?rev=e48b187bcfd5cc75111acd9d241f1bd36604344b#e48b187bcfd5cc75111acd9d241f1bd36604344b" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "620a1d43d70e142b1d46a929af51d44f383db9c7a2ec122de2cd992ccfcf3c18" [[package]] name = "siphasher" diff --git a/docs/sdk/Cargo.toml b/docs/sdk/Cargo.toml index 19c0f729f4c..05aced8751a 100644 --- a/docs/sdk/Cargo.toml +++ b/docs/sdk/Cargo.toml @@ -22,7 +22,7 @@ pallet-examples = { path = "../../substrate/frame/examples" } pallet-default-config-example = { path = "../../substrate/frame/examples/default-config" } # How we build docs in rust-docs -simple-mermaid = { git = "https://github.com/kianenigma/simple-mermaid.git", rev = "e48b187bcfd5cc75111acd9d241f1bd36604344b" } +simple-mermaid = "0.1.1" docify = "0.2.7" # Polkadot SDK deps, typically all should only be in scope such that we can link to their doc item. diff --git a/substrate/frame/Cargo.toml b/substrate/frame/Cargo.toml index 01c1358ecb9..3f148bf4c83 100644 --- a/substrate/frame/Cargo.toml +++ b/substrate/frame/Cargo.toml @@ -48,7 +48,6 @@ frame-executive = { default-features = false, path = "../frame/executive", optio frame-system-rpc-runtime-api = { default-features = false, path = "../frame/system/rpc/runtime-api", optional = true } docify = "0.2.7" -simple-mermaid = { git = "https://github.com/kianenigma/simple-mermaid.git", rev = "e48b187bcfd5cc75111acd9d241f1bd36604344b", optional = true } log = { workspace = true } [dev-dependencies] @@ -78,7 +77,6 @@ std = [ "log/std", "parity-scale-codec/std", "scale-info/std", - "simple-mermaid", "sp-api?/std", "sp-arithmetic/std", "sp-block-builder?/std", diff --git a/substrate/primitives/runtime/Cargo.toml b/substrate/primitives/runtime/Cargo.toml index 940fca54ab1..f42d8d77d50 100644 --- a/substrate/primitives/runtime/Cargo.toml +++ b/substrate/primitives/runtime/Cargo.toml @@ -34,7 +34,7 @@ sp-std = { path = "../std", default-features = false } sp-weights = { path = "../weights", default-features = false } docify = { version = "0.2.7" } -simple-mermaid = { git = "https://github.com/kianenigma/simple-mermaid.git", rev = "e48b187bcfd5cc75111acd9d241f1bd36604344b", optional = true } +simple-mermaid = { version = "0.1.1", optional = true } [dev-dependencies] rand = "0.8.5" -- GitLab From f60486047f539d141da9ab7272a825cb0d5b6e5b Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Thu, 15 Feb 2024 12:13:19 +0200 Subject: [PATCH 064/133] xcm-emulator tests: remove dependency on polkadot-service full-node (#3323) Don't bring the full polkadot node (with all its dependencies) in `emulated-integration-tests-common` crate and deps. --- Cargo.lock | 2 +- .../emulated/chains/relays/rococo/src/genesis.rs | 5 ++--- .../emulated/chains/relays/westend/src/genesis.rs | 4 ++-- .../integration-tests/emulated/common/Cargo.toml | 2 +- .../integration-tests/emulated/common/src/lib.rs | 15 +++++++++++++-- 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8d9c1c84c24..ac4725dd483 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4906,10 +4906,10 @@ dependencies = [ "paste", "polkadot-primitives", "polkadot-runtime-parachains", - "polkadot-service", "sc-consensus-grandpa", "sp-authority-discovery", "sp-consensus-babe", + "sp-consensus-beefy", "sp-core", "sp-runtime", "staging-xcm", diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs index 45e1e94de01..7db9679f1c3 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs @@ -25,8 +25,7 @@ use polkadot_primitives::{AssignmentId, ValidatorId}; // Cumulus use emulated_integration_tests_common::{ - accounts, build_genesis_storage, get_account_id_from_seed, get_from_seed, get_host_config, - validators, + accounts, build_genesis_storage, get_account_id_from_seed, get_host_config, validators, }; use parachains_common::Balance; use rococo_runtime_constants::currency::UNITS as ROC; @@ -71,7 +70,7 @@ pub fn genesis() -> Storage { x.4.clone(), x.5.clone(), x.6.clone(), - get_from_seed::("Alice"), + x.7.clone(), ), ) }) diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs index e2297100a45..578b307dd93 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs @@ -26,7 +26,7 @@ use polkadot_primitives::{AssignmentId, ValidatorId}; // Cumulus use emulated_integration_tests_common::{ - accounts, build_genesis_storage, get_from_seed, get_host_config, validators, + accounts, build_genesis_storage, get_host_config, validators, }; use parachains_common::Balance; use westend_runtime_constants::currency::UNITS as WND; @@ -72,7 +72,7 @@ pub fn genesis() -> Storage { x.4.clone(), x.5.clone(), x.6.clone(), - get_from_seed::("Alice"), + x.7.clone(), ), ) }) diff --git a/cumulus/parachains/integration-tests/emulated/common/Cargo.toml b/cumulus/parachains/integration-tests/emulated/common/Cargo.toml index 381e4110f90..721c58fd864 100644 --- a/cumulus/parachains/integration-tests/emulated/common/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/common/Cargo.toml @@ -14,6 +14,7 @@ codec = { package = "parity-scale-codec", version = "3.4.0", default-features = paste = "1.0.14" # Substrate +beefy-primitives = { package = "sp-consensus-beefy", path = "../../../../../substrate/primitives/consensus/beefy" } grandpa = { package = "sc-consensus-grandpa", path = "../../../../../substrate/client/consensus/grandpa" } sp-authority-discovery = { path = "../../../../../substrate/primitives/authority-discovery" } sp-runtime = { path = "../../../../../substrate/primitives/runtime" } @@ -25,7 +26,6 @@ pallet-balances = { path = "../../../../../substrate/frame/balances" } pallet-message-queue = { path = "../../../../../substrate/frame/message-queue" } # Polkadot -polkadot-service = { path = "../../../../../polkadot/node/service", default-features = false, features = ["full-node"] } polkadot-primitives = { path = "../../../../../polkadot/primitives" } polkadot-runtime-parachains = { path = "../../../../../polkadot/runtime/parachains" } xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm" } diff --git a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs index 546f81ce825..1a5cc1f6fea 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs @@ -20,6 +20,7 @@ pub mod xcm_helpers; pub use xcm_emulator; // Substrate +use beefy_primitives::ecdsa_crypto::AuthorityId as BeefyId; use grandpa::AuthorityId as GrandpaId; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; use sp_consensus_babe::AuthorityId as BabeId; @@ -36,7 +37,6 @@ use polkadot_runtime_parachains::configuration::HostConfiguration; // Cumulus use parachains_common::{AccountId, AuraId}; use polkadot_primitives::{AssignmentId, ValidatorId}; -use polkadot_service::chain_spec::get_authority_keys_from_seed_no_beefy; pub const XCM_V2: u32 = 2; pub const XCM_V3: u32 = 3; @@ -152,7 +152,18 @@ pub mod validators { ValidatorId, AssignmentId, AuthorityDiscoveryId, + BeefyId, )> { - vec![get_authority_keys_from_seed_no_beefy("Alice")] + let seed = "Alice"; + vec![( + get_account_id_from_seed::(&format!("{}//stash", seed)), + get_account_id_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + )] } } -- GitLab From 150a360c410d9a051ac04022bb69eb1458396db6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 15 Feb 2024 12:15:05 +0100 Subject: [PATCH 065/133] sc-keystore: Improve docs (#3334) Close: https://github.com/paritytech/polkadot-sdk/issues/3320 @Hugo-Trentesaux are these docs better for explaining the internals? --- substrate/client/keystore/src/local.rs | 7 +++++++ substrate/primitives/core/src/crypto.rs | 2 ++ 2 files changed, 9 insertions(+) diff --git a/substrate/client/keystore/src/local.rs b/substrate/client/keystore/src/local.rs index ca4a87ef383..4a04a48518b 100644 --- a/substrate/client/keystore/src/local.rs +++ b/substrate/client/keystore/src/local.rs @@ -47,6 +47,13 @@ pub struct LocalKeystore(RwLock); impl LocalKeystore { /// Create a local keystore from filesystem. + /// + /// The keystore will be created at `path`. The keystore optionally supports to encrypt/decrypt + /// the keys in the keystore using `password`. + /// + /// NOTE: Even when passing a `password`, the keys on disk appear to look like normal secret + /// uris. However, without having the correct password the secret uri will not generate the + /// correct private key. See [`SecretUri`](sp_core::crypto::SecretUri) for more information. pub fn open>(path: T, password: Option) -> Result { let inner = KeystoreInner::open(path, password)?; Ok(Self(RwLock::new(inner))) diff --git a/substrate/primitives/core/src/crypto.rs b/substrate/primitives/core/src/crypto.rs index d8436d2a96c..2a8be2a2ba8 100644 --- a/substrate/primitives/core/src/crypto.rs +++ b/substrate/primitives/core/src/crypto.rs @@ -773,6 +773,8 @@ mod dummy { /// Similarly an empty password (ending the `SURI` with `///`) is perfectly valid and will /// generally be equivalent to no password at all. /// +/// The `password` is used as salt when generating the seed from the BIP-39 key phrase. +/// /// # Example /// /// Parse [`DEV_PHRASE`] secret uri with junction: -- GitLab From 5fc7622cb312f2d32ec8365012ee0a49622db8c8 Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Thu, 15 Feb 2024 13:56:58 +0100 Subject: [PATCH 066/133] Bridge zombienet tests - move all test scripts to the same folder (#3333) Related to https://github.com/paritytech/polkadot-sdk/issues/3242 --- .config/taplo.toml | 1 + .gitlab/pipeline/build.yml | 5 +-- .gitlab/pipeline/zombienet/bridges.yml | 8 ++-- bridges/{zombienet => testing}/README.md | 0 .../bridge_hub_rococo_local_network.toml | 0 .../bridge_hub_westend_local_network.toml | 0 .../rococo-westend}/bridges_rococo_westend.sh | 2 +- .../environments/rococo-westend/helper.sh | 3 ++ .../rococo-westend/rococo-init.zndsl | 8 ++++ .../environments/rococo-westend/rococo.zndsl | 7 ++++ .../environments/rococo-westend/spawn.sh | 4 +- .../rococo-westend/start_relayer.sh | 0 .../rococo-westend/westend-init.zndsl | 7 ++++ .../environments/rococo-westend/westend.zndsl | 6 +++ .../best-finalized-header-at-bridged-chain.js | 0 .../js-helpers}/chains/rococo-at-westend.js | 0 .../js-helpers}/chains/westend-at-rococo.js | 0 .../native-assets-balance-increased.js | 0 ...only-mandatory-headers-synced-when-idle.js | 0 .../only-required-headers-synced-when-idle.js | 0 .../js-helpers}/relayer-rewards.js | 0 .../helpers => testing/js-helpers}/utils.js | 0 .../js-helpers}/wait-hrmp-channel-opened.js | 0 .../js-helpers}/wrapped-assets-balance.js | 0 .../{zombienet => testing}/run-new-test.sh | 0 bridges/{zombienet => testing}/run-tests.sh | 2 +- .../scripts/invoke-script.sh | 2 +- .../scripts/start-relayer.sh | 2 +- .../scripts/sync-exit.sh | 0 .../roc-reaches-westend.zndsl | 12 ++++++ .../tests/0001-asset-transfer/run.sh | 0 .../wnd-reaches-rococo.zndsl | 12 ++++++ .../wroc-reaches-rococo.zndsl | 4 +- .../wwnd-reaches-westend.zndsl | 4 +- .../rococo-to-westend.zndsl | 4 +- .../run.sh | 0 .../westend-to-rococo.zndsl | 4 +- ...ynced-while-active-rococo-to-westend.zndsl | 6 +-- ...ynced-while-active-westend-to-rococo.zndsl | 6 +-- .../testing/utils/bridges.sh | 2 +- .../{zombienet => testing}/utils/common.sh | 0 .../utils}/generate_hex_encoded_call/index.js | 0 .../package-lock.json | 0 .../generate_hex_encoded_call/package.json | 0 .../{zombienet => testing}/utils/zombienet.sh | 0 .../environments/rococo-westend/helper.sh | 3 -- .../rococo-westend/rococo-init.zndsl | 8 ---- .../environments/rococo-westend/rococo.zndsl | 7 ---- .../rococo-westend/westend-init.zndsl | 7 ---- .../environments/rococo-westend/westend.zndsl | 6 --- ...set-transfer-works-rococo-to-westend.zndsl | 39 ------------------- ...set-transfer-works-westend-to-rococo.zndsl | 39 ------------------- .../roc-reaches-westend.zndsl | 12 ------ .../wnd-reaches-rococo.zndsl | 12 ------ ...-synced-while-idle-rococo-to-westend.zndsl | 26 ------------- ...-synced-while-idle-westend-to-rococo.zndsl | 26 ------------- .../parachains/runtimes/bridge-hubs/README.md | 26 ++++++------- ...ridges_zombienet_tests_injected.Dockerfile | 4 +- 58 files changed, 97 insertions(+), 229 deletions(-) rename bridges/{zombienet => testing}/README.md (100%) rename {cumulus/zombienet/bridge-hubs => bridges/testing/environments/rococo-westend}/bridge_hub_rococo_local_network.toml (100%) rename {cumulus/zombienet/bridge-hubs => bridges/testing/environments/rococo-westend}/bridge_hub_westend_local_network.toml (100%) rename {cumulus/scripts => bridges/testing/environments/rococo-westend}/bridges_rococo_westend.sh (99%) create mode 100755 bridges/testing/environments/rococo-westend/helper.sh create mode 100644 bridges/testing/environments/rococo-westend/rococo-init.zndsl create mode 100644 bridges/testing/environments/rococo-westend/rococo.zndsl rename bridges/{zombienet => testing}/environments/rococo-westend/spawn.sh (90%) rename bridges/{zombienet => testing}/environments/rococo-westend/start_relayer.sh (100%) create mode 100644 bridges/testing/environments/rococo-westend/westend-init.zndsl create mode 100644 bridges/testing/environments/rococo-westend/westend.zndsl rename bridges/{zombienet/helpers => testing/js-helpers}/best-finalized-header-at-bridged-chain.js (100%) rename bridges/{zombienet/helpers => testing/js-helpers}/chains/rococo-at-westend.js (100%) rename bridges/{zombienet/helpers => testing/js-helpers}/chains/westend-at-rococo.js (100%) rename bridges/{zombienet/helpers => testing/js-helpers}/native-assets-balance-increased.js (100%) rename bridges/{zombienet/helpers => testing/js-helpers}/only-mandatory-headers-synced-when-idle.js (100%) rename bridges/{zombienet/helpers => testing/js-helpers}/only-required-headers-synced-when-idle.js (100%) rename bridges/{zombienet/helpers => testing/js-helpers}/relayer-rewards.js (100%) rename bridges/{zombienet/helpers => testing/js-helpers}/utils.js (100%) rename bridges/{zombienet/helpers => testing/js-helpers}/wait-hrmp-channel-opened.js (100%) rename bridges/{zombienet/helpers => testing/js-helpers}/wrapped-assets-balance.js (100%) rename bridges/{zombienet => testing}/run-new-test.sh (100%) rename bridges/{zombienet => testing}/run-tests.sh (98%) rename bridges/{zombienet => testing}/scripts/invoke-script.sh (62%) rename bridges/{zombienet => testing}/scripts/start-relayer.sh (63%) rename bridges/{zombienet => testing}/scripts/sync-exit.sh (100%) create mode 100644 bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl rename bridges/{zombienet => testing}/tests/0001-asset-transfer/run.sh (100%) create mode 100644 bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl rename bridges/{zombienet => testing}/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl (65%) rename bridges/{zombienet => testing}/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl (65%) rename bridges/{zombienet => testing}/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl (58%) rename bridges/{zombienet => testing}/tests/0002-mandatory-headers-synced-while-idle/run.sh (100%) rename bridges/{zombienet => testing}/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl (58%) rename bridges/{zombienet => testing}/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl (77%) rename bridges/{zombienet => testing}/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl (77%) rename cumulus/scripts/bridges_common.sh => bridges/testing/utils/bridges.sh (99%) rename bridges/{zombienet => testing}/utils/common.sh (100%) rename {cumulus/scripts => bridges/testing/utils}/generate_hex_encoded_call/index.js (100%) rename {cumulus/scripts => bridges/testing/utils}/generate_hex_encoded_call/package-lock.json (100%) rename {cumulus/scripts => bridges/testing/utils}/generate_hex_encoded_call/package.json (100%) rename bridges/{zombienet => testing}/utils/zombienet.sh (100%) delete mode 100755 bridges/zombienet/environments/rococo-westend/helper.sh delete mode 100644 bridges/zombienet/environments/rococo-westend/rococo-init.zndsl delete mode 100644 bridges/zombienet/environments/rococo-westend/rococo.zndsl delete mode 100644 bridges/zombienet/environments/rococo-westend/westend-init.zndsl delete mode 100644 bridges/zombienet/environments/rococo-westend/westend.zndsl delete mode 100644 bridges/zombienet/tests/0001-asset-transfer-works-rococo-to-westend.zndsl delete mode 100644 bridges/zombienet/tests/0001-asset-transfer-works-westend-to-rococo.zndsl delete mode 100644 bridges/zombienet/tests/0001-asset-transfer/roc-reaches-westend.zndsl delete mode 100644 bridges/zombienet/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl delete mode 100644 bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle-rococo-to-westend.zndsl delete mode 100644 bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle-westend-to-rococo.zndsl diff --git a/.config/taplo.toml b/.config/taplo.toml index f5d0b7021ba..a45204923cf 100644 --- a/.config/taplo.toml +++ b/.config/taplo.toml @@ -2,6 +2,7 @@ # ignore zombienet as they do some deliberate custom toml stuff exclude = [ + "bridges/testing/**", "cumulus/zombienet/**", "polkadot/node/malus/integrationtests/**", "polkadot/zombienet_tests/**", diff --git a/.gitlab/pipeline/build.yml b/.gitlab/pipeline/build.yml index 42aabc2635d..53c47e5ff5a 100644 --- a/.gitlab/pipeline/build.yml +++ b/.gitlab/pipeline/build.yml @@ -408,8 +408,5 @@ prepare-bridges-zombienet-artifacts: - .collect-artifacts before_script: - mkdir -p ./artifacts/bridges-polkadot-sdk/bridges - - mkdir -p ./artifacts/bridges-polkadot-sdk/cumulus/zombienet script: - - cp -r bridges/zombienet ./artifacts/bridges-polkadot-sdk/bridges/zombienet - - cp -r cumulus/scripts ./artifacts/bridges-polkadot-sdk/cumulus/scripts - - cp -r cumulus/zombienet/bridge-hubs ./artifacts/bridges-polkadot-sdk/cumulus/zombienet/bridge-hubs + - cp -r bridges/testing ./artifacts/bridges-polkadot-sdk/bridges/testing diff --git a/.gitlab/pipeline/zombienet/bridges.yml b/.gitlab/pipeline/zombienet/bridges.yml index 16cabad6cca..f7c32d21c55 100644 --- a/.gitlab/pipeline/zombienet/bridges.yml +++ b/.gitlab/pipeline/zombienet/bridges.yml @@ -28,8 +28,8 @@ variables: BRIDGES_ZOMBIENET_TESTS_IMAGE_TAG: ${DOCKER_IMAGES_VERSION} BRIDGES_ZOMBIENET_TESTS_IMAGE: "docker.io/paritypr/bridges-zombienet-tests" - GH_DIR: "https://github.com/paritytech/polkadot-sdk/tree/${CI_COMMIT_SHA}/bridges/zombienet" - LOCAL_DIR: "/builds/parity/mirrors/polkadot-sdk/bridges/zombienet" + GH_DIR: "https://github.com/paritytech/polkadot-sdk/tree/${CI_COMMIT_SHA}/bridges/testing" + LOCAL_DIR: "/builds/parity/mirrors/polkadot-sdk/bridges/testing" FF_DISABLE_UMASK_FOR_DOCKER_EXECUTOR: 1 RUN_IN_CONTAINER: "1" artifacts: @@ -51,12 +51,12 @@ zombienet-bridges-0001-asset-transfer-works: extends: - .zombienet-bridges-common script: - - /home/nonroot/bridges-polkadot-sdk/bridges/zombienet/run-new-test.sh 0001-asset-transfer --docker + - /home/nonroot/bridges-polkadot-sdk/bridges/testing/run-new-test.sh 0001-asset-transfer --docker - echo "Done" zombienet-bridges-0002-mandatory-headers-synced-while-idle: extends: - .zombienet-bridges-common script: - - /home/nonroot/bridges-polkadot-sdk/bridges/zombienet/run-new-test.sh 0002-mandatory-headers-synced-while-idle --docker + - /home/nonroot/bridges-polkadot-sdk/bridges/testing/run-new-test.sh 0002-mandatory-headers-synced-while-idle --docker - echo "Done" diff --git a/bridges/zombienet/README.md b/bridges/testing/README.md similarity index 100% rename from bridges/zombienet/README.md rename to bridges/testing/README.md diff --git a/cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml b/bridges/testing/environments/rococo-westend/bridge_hub_rococo_local_network.toml similarity index 100% rename from cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml rename to bridges/testing/environments/rococo-westend/bridge_hub_rococo_local_network.toml diff --git a/cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml b/bridges/testing/environments/rococo-westend/bridge_hub_westend_local_network.toml similarity index 100% rename from cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml rename to bridges/testing/environments/rococo-westend/bridge_hub_westend_local_network.toml diff --git a/cumulus/scripts/bridges_rococo_westend.sh b/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh similarity index 99% rename from cumulus/scripts/bridges_rococo_westend.sh rename to bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh index 7bde5ede77f..84764cdaca3 100755 --- a/cumulus/scripts/bridges_rococo_westend.sh +++ b/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh @@ -1,7 +1,7 @@ #!/bin/bash # import common functions -source "$(dirname "$0")"/bridges_common.sh +source "${BASH_SOURCE%/*}/../../utils/bridges.sh" # Expected sovereign accounts. # diff --git a/bridges/testing/environments/rococo-westend/helper.sh b/bridges/testing/environments/rococo-westend/helper.sh new file mode 100755 index 00000000000..211a5b53b3d --- /dev/null +++ b/bridges/testing/environments/rococo-westend/helper.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +$POLKADOT_SDK_PATH/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh "$@" diff --git a/bridges/testing/environments/rococo-westend/rococo-init.zndsl b/bridges/testing/environments/rococo-westend/rococo-init.zndsl new file mode 100644 index 00000000000..145f2df73a6 --- /dev/null +++ b/bridges/testing/environments/rococo-westend/rococo-init.zndsl @@ -0,0 +1,8 @@ +Description: User is able to transfer WND from Westend Asset Hub to Rococo Asset Hub and back +Network: ./bridge_hub_rococo_local_network.toml +Creds: config + +# ensure that initialization has completed +asset-hub-rococo-collator1: js-script ../../js-helpers/wait-hrmp-channel-opened.js with "1013" within 300 seconds + + diff --git a/bridges/testing/environments/rococo-westend/rococo.zndsl b/bridges/testing/environments/rococo-westend/rococo.zndsl new file mode 100644 index 00000000000..bd8681af219 --- /dev/null +++ b/bridges/testing/environments/rococo-westend/rococo.zndsl @@ -0,0 +1,7 @@ +Description: User is able to transfer WND from Westend Asset Hub to Rococo Asset Hub and back +Network: ./bridge_hub_rococo_local_network.toml +Creds: config + +# relay is already started - let's wait until with-Westend GRANPDA pallet is initialized at Rococo +bridge-hub-rococo-collator1: js-script ../../js-helpers/best-finalized-header-at-bridged-chain.js with "Westend,0" within 400 seconds + diff --git a/bridges/zombienet/environments/rococo-westend/spawn.sh b/bridges/testing/environments/rococo-westend/spawn.sh similarity index 90% rename from bridges/zombienet/environments/rococo-westend/spawn.sh rename to bridges/testing/environments/rococo-westend/spawn.sh index 550eee04bd6..5a0d65ce65d 100755 --- a/bridges/zombienet/environments/rococo-westend/spawn.sh +++ b/bridges/testing/environments/rococo-westend/spawn.sh @@ -26,11 +26,11 @@ done logs_dir=$TEST_DIR/logs helper_script="${BASH_SOURCE%/*}/helper.sh" -rococo_def=$POLKADOT_SDK_PATH/cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml +rococo_def=${BASH_SOURCE%/*}/bridge_hub_rococo_local_network.toml start_zombienet $TEST_DIR $rococo_def rococo_dir rococo_pid echo -westend_def=$POLKADOT_SDK_PATH/cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml +westend_def=${BASH_SOURCE%/*}/bridge_hub_westend_local_network.toml start_zombienet $TEST_DIR $westend_def westend_dir westend_pid echo diff --git a/bridges/zombienet/environments/rococo-westend/start_relayer.sh b/bridges/testing/environments/rococo-westend/start_relayer.sh similarity index 100% rename from bridges/zombienet/environments/rococo-westend/start_relayer.sh rename to bridges/testing/environments/rococo-westend/start_relayer.sh diff --git a/bridges/testing/environments/rococo-westend/westend-init.zndsl b/bridges/testing/environments/rococo-westend/westend-init.zndsl new file mode 100644 index 00000000000..2f8e665d592 --- /dev/null +++ b/bridges/testing/environments/rococo-westend/westend-init.zndsl @@ -0,0 +1,7 @@ +Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back +Network: ./bridge_hub_westend_local_network.toml +Creds: config + +# ensure that initialization has completed +asset-hub-westend-collator1: js-script ../../js-helpers/wait-hrmp-channel-opened.js with "1002" within 600 seconds + diff --git a/bridges/testing/environments/rococo-westend/westend.zndsl b/bridges/testing/environments/rococo-westend/westend.zndsl new file mode 100644 index 00000000000..c75ae579d27 --- /dev/null +++ b/bridges/testing/environments/rococo-westend/westend.zndsl @@ -0,0 +1,6 @@ +Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back +Network: ./bridge_hub_westend_local_network.toml +Creds: config + +# relay is already started - let's wait until with-Rococo GRANPDA pallet is initialized at Westend +bridge-hub-westend-collator1: js-script ../../js-helpers/best-finalized-header-at-bridged-chain.js with "Rococo,0" within 400 seconds diff --git a/bridges/zombienet/helpers/best-finalized-header-at-bridged-chain.js b/bridges/testing/js-helpers/best-finalized-header-at-bridged-chain.js similarity index 100% rename from bridges/zombienet/helpers/best-finalized-header-at-bridged-chain.js rename to bridges/testing/js-helpers/best-finalized-header-at-bridged-chain.js diff --git a/bridges/zombienet/helpers/chains/rococo-at-westend.js b/bridges/testing/js-helpers/chains/rococo-at-westend.js similarity index 100% rename from bridges/zombienet/helpers/chains/rococo-at-westend.js rename to bridges/testing/js-helpers/chains/rococo-at-westend.js diff --git a/bridges/zombienet/helpers/chains/westend-at-rococo.js b/bridges/testing/js-helpers/chains/westend-at-rococo.js similarity index 100% rename from bridges/zombienet/helpers/chains/westend-at-rococo.js rename to bridges/testing/js-helpers/chains/westend-at-rococo.js diff --git a/bridges/zombienet/helpers/native-assets-balance-increased.js b/bridges/testing/js-helpers/native-assets-balance-increased.js similarity index 100% rename from bridges/zombienet/helpers/native-assets-balance-increased.js rename to bridges/testing/js-helpers/native-assets-balance-increased.js diff --git a/bridges/zombienet/helpers/only-mandatory-headers-synced-when-idle.js b/bridges/testing/js-helpers/only-mandatory-headers-synced-when-idle.js similarity index 100% rename from bridges/zombienet/helpers/only-mandatory-headers-synced-when-idle.js rename to bridges/testing/js-helpers/only-mandatory-headers-synced-when-idle.js diff --git a/bridges/zombienet/helpers/only-required-headers-synced-when-idle.js b/bridges/testing/js-helpers/only-required-headers-synced-when-idle.js similarity index 100% rename from bridges/zombienet/helpers/only-required-headers-synced-when-idle.js rename to bridges/testing/js-helpers/only-required-headers-synced-when-idle.js diff --git a/bridges/zombienet/helpers/relayer-rewards.js b/bridges/testing/js-helpers/relayer-rewards.js similarity index 100% rename from bridges/zombienet/helpers/relayer-rewards.js rename to bridges/testing/js-helpers/relayer-rewards.js diff --git a/bridges/zombienet/helpers/utils.js b/bridges/testing/js-helpers/utils.js similarity index 100% rename from bridges/zombienet/helpers/utils.js rename to bridges/testing/js-helpers/utils.js diff --git a/bridges/zombienet/helpers/wait-hrmp-channel-opened.js b/bridges/testing/js-helpers/wait-hrmp-channel-opened.js similarity index 100% rename from bridges/zombienet/helpers/wait-hrmp-channel-opened.js rename to bridges/testing/js-helpers/wait-hrmp-channel-opened.js diff --git a/bridges/zombienet/helpers/wrapped-assets-balance.js b/bridges/testing/js-helpers/wrapped-assets-balance.js similarity index 100% rename from bridges/zombienet/helpers/wrapped-assets-balance.js rename to bridges/testing/js-helpers/wrapped-assets-balance.js diff --git a/bridges/zombienet/run-new-test.sh b/bridges/testing/run-new-test.sh similarity index 100% rename from bridges/zombienet/run-new-test.sh rename to bridges/testing/run-new-test.sh diff --git a/bridges/zombienet/run-tests.sh b/bridges/testing/run-tests.sh similarity index 98% rename from bridges/zombienet/run-tests.sh rename to bridges/testing/run-tests.sh index 42818a5cdf3..6149d991265 100755 --- a/bridges/zombienet/run-tests.sh +++ b/bridges/testing/run-tests.sh @@ -28,7 +28,7 @@ done # assuming that we'll be using native provide && all processes will be executing locally # (we need absolute paths here, because they're used when scripts are called by zombienet from tmp folders) export POLKADOT_SDK_PATH=`realpath $(dirname "$0")/../..` -export BRIDGE_TESTS_FOLDER=$POLKADOT_SDK_PATH/bridges/zombienet/tests +export BRIDGE_TESTS_FOLDER=$POLKADOT_SDK_PATH/bridges/testing/tests # set pathc to binaries if [ "$ZOMBIENET_DOCKER_PATHS" -eq 1 ]; then diff --git a/bridges/zombienet/scripts/invoke-script.sh b/bridges/testing/scripts/invoke-script.sh similarity index 62% rename from bridges/zombienet/scripts/invoke-script.sh rename to bridges/testing/scripts/invoke-script.sh index 1f814cd0856..cd0557b071b 100755 --- a/bridges/zombienet/scripts/invoke-script.sh +++ b/bridges/testing/scripts/invoke-script.sh @@ -2,6 +2,6 @@ INVOKE_LOG=`mktemp -p $TEST_FOLDER invoke.XXXXX` -pushd $POLKADOT_SDK_PATH/cumulus/scripts +pushd $POLKADOT_SDK_PATH/bridges/testing/environments/rococo-westend ./bridges_rococo_westend.sh $1 >$INVOKE_LOG 2>&1 popd diff --git a/bridges/zombienet/scripts/start-relayer.sh b/bridges/testing/scripts/start-relayer.sh similarity index 63% rename from bridges/zombienet/scripts/start-relayer.sh rename to bridges/testing/scripts/start-relayer.sh index 630fd7316fe..38ea62fad52 100755 --- a/bridges/zombienet/scripts/start-relayer.sh +++ b/bridges/testing/scripts/start-relayer.sh @@ -2,6 +2,6 @@ RELAY_LOG=`mktemp -p $TEST_FOLDER relay.XXXXX` -pushd $POLKADOT_SDK_PATH/cumulus/scripts +pushd $POLKADOT_SDK_PATH/bridges/testing/environments/rococo-westend ./bridges_rococo_westend.sh run-relay >$RELAY_LOG 2>&1& popd diff --git a/bridges/zombienet/scripts/sync-exit.sh b/bridges/testing/scripts/sync-exit.sh similarity index 100% rename from bridges/zombienet/scripts/sync-exit.sh rename to bridges/testing/scripts/sync-exit.sh diff --git a/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl b/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl new file mode 100644 index 00000000000..203c95b73eb --- /dev/null +++ b/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl @@ -0,0 +1,12 @@ +Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back +Network: ../../environments/rococo-westend/bridge_hub_westend_local_network.toml +Creds: config + +# send ROC to //Alice from Rococo AH to Westend AH +asset-hub-westend-collator1: run ../../environments/rococo-westend/helper.sh with "reserve-transfer-assets-from-asset-hub-rococo-local" within 120 seconds + +# check that //Alice received the ROC on Westend AH +asset-hub-westend-collator1: js-script ../../js-helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,0,Rococo" within 300 seconds + +# check that the relayer //Charlie is rewarded by Westend AH +bridge-hub-westend-collator1: js-script ../../js-helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x6268726F,ThisChain,0" within 30 seconds diff --git a/bridges/zombienet/tests/0001-asset-transfer/run.sh b/bridges/testing/tests/0001-asset-transfer/run.sh similarity index 100% rename from bridges/zombienet/tests/0001-asset-transfer/run.sh rename to bridges/testing/tests/0001-asset-transfer/run.sh diff --git a/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl b/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl new file mode 100644 index 00000000000..bbd95db9cfd --- /dev/null +++ b/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl @@ -0,0 +1,12 @@ +Description: User is able to transfer WND from Westend Asset Hub to Rococo Asset Hub and back +Network: ../../environments/rococo-westend/bridge_hub_rococo_local_network.toml +Creds: config + +# send WND to //Alice from Westend AH to Rococo AH +asset-hub-rococo-collator1: run ../../environments/rococo-westend/helper.sh with "reserve-transfer-assets-from-asset-hub-westend-local" within 120 seconds + +# check that //Alice received the WND on Rococo AH +asset-hub-rococo-collator1: js-script ../../js-helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,0,Westend" within 300 seconds + +# check that the relayer //Charlie is rewarded by Rococo AH +bridge-hub-rococo-collator1: js-script ../../js-helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x62687764,ThisChain,0" within 30 seconds diff --git a/bridges/zombienet/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl b/bridges/testing/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl similarity index 65% rename from bridges/zombienet/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl rename to bridges/testing/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl index 7fbdecadeae..4c0a4675234 100644 --- a/bridges/zombienet/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl +++ b/bridges/testing/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl @@ -1,5 +1,5 @@ Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back -Network: ../../../../cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml +Network: ../../environments/rococo-westend/bridge_hub_westend_local_network.toml Creds: config # send wROC back to Alice from Westend AH to Rococo AH @@ -7,4 +7,4 @@ asset-hub-rococo-collator1: run ../../environments/rococo-westend/helper.sh with # check that //Alice received the wROC on Rococo AH # (we wait until //Alice account increases here - there are no other transactions that may increase it) -asset-hub-rococo-collator1: js-script ../../helpers/native-assets-balance-increased.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" within 300 seconds +asset-hub-rococo-collator1: js-script ../../js-helpers/native-assets-balance-increased.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" within 300 seconds diff --git a/bridges/zombienet/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl b/bridges/testing/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl similarity index 65% rename from bridges/zombienet/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl rename to bridges/testing/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl index 662e8875268..3acded97d5c 100644 --- a/bridges/zombienet/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl +++ b/bridges/testing/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl @@ -1,5 +1,5 @@ Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back -Network: ../../../../cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml +Network: ../../environments/rococo-westend/bridge_hub_westend_local_network.toml Creds: config # send wWND back to Alice from Rococo AH to Westend AH @@ -7,4 +7,4 @@ asset-hub-westend-collator1: run ../../environments/rococo-westend/helper.sh wit # check that //Alice received the wWND on Westend AH # (we wait until //Alice account increases here - there are no other transactions that may increase it) -asset-hub-westend-collator1: js-script ../../helpers/native-assets-balance-increased.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" within 300 seconds +asset-hub-westend-collator1: js-script ../../js-helpers/native-assets-balance-increased.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" within 300 seconds diff --git a/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl similarity index 58% rename from bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl rename to bridges/testing/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl index d4283b36129..82a1a103b14 100644 --- a/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl +++ b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl @@ -1,8 +1,8 @@ Description: While relayer is idle, we only sync mandatory Rococo (and a single Rococo BH) headers to Westend BH. -Network: ../../../../cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml +Network: ../../environments/rococo-westend/bridge_hub_westend_local_network.toml Creds: config # ensure that relayer is only syncing mandatory headers while idle. This includes both headers that were # generated while relay was offline and those in the next 100 seconds while script is active. -bridge-hub-westend-collator1: js-script ../../helpers/only-mandatory-headers-synced-when-idle.js with "300,rococo-at-westend" within 600 seconds +bridge-hub-westend-collator1: js-script ../../js-helpers/only-mandatory-headers-synced-when-idle.js with "300,rococo-at-westend" within 600 seconds diff --git a/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle/run.sh b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh similarity index 100% rename from bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle/run.sh rename to bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh diff --git a/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl similarity index 58% rename from bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl rename to bridges/testing/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl index 1ea4c92a345..86581324625 100644 --- a/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl +++ b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl @@ -1,7 +1,7 @@ Description: While relayer is idle, we only sync mandatory Westend (and a single Westend BH) headers to Rococo BH. -Network: ../../../../cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml +Network: ../../environments/rococo-westend/bridge_hub_rococo_local_network.toml Creds: config # ensure that relayer is only syncing mandatory headers while idle. This includes both headers that were # generated while relay was offline and those in the next 100 seconds while script is active. -bridge-hub-rococo-collator1: js-script ../../helpers/only-mandatory-headers-synced-when-idle.js with "300,westend-at-rococo" within 600 seconds +bridge-hub-rococo-collator1: js-script ../../js-helpers/only-mandatory-headers-synced-when-idle.js with "300,westend-at-rococo" within 600 seconds diff --git a/bridges/zombienet/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl b/bridges/testing/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl similarity index 77% rename from bridges/zombienet/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl rename to bridges/testing/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl index a4960344f0a..07b91481dc7 100644 --- a/bridges/zombienet/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl +++ b/bridges/testing/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl @@ -1,5 +1,5 @@ Description: While relayer is active, we only sync mandatory and required Rococo (and Rococo BH) headers to Westend BH. -Network: ../../../cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml +Network: ../environments/rococo-westend/bridge_hub_westend_local_network.toml Creds: config # step 1: initialize Westend AH @@ -9,7 +9,7 @@ asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "init-asset-hu bridge-hub-westend-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-westend-local" within 60 seconds # step 3: ensure that initialization has completed -asset-hub-westend-collator1: js-script ../helpers/wait-hrmp-channel-opened.js with "1002" within 600 seconds +asset-hub-westend-collator1: js-script ../js-helpers/wait-hrmp-channel-opened.js with "1002" within 600 seconds # step 4: send message from Westend to Rococo asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "reserve-transfer-assets-from-asset-hub-westend-local" within 60 seconds @@ -20,7 +20,7 @@ asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "reserve-trans # (it is started by sibling 0003-required-headers-synced-while-active-westend-to-rococo.zndsl) # step 6: ensure that relayer won't sync any extra headers while delivering messages and confirmations -bridge-hub-westend-collator1: js-script ../helpers/only-required-headers-synced-when-active.js with "500,rococo-at-westend" within 600 seconds +bridge-hub-westend-collator1: js-script ../js-helpers/only-required-headers-synced-when-active.js with "500,rococo-at-westend" within 600 seconds # wait until other network test has completed OR exit with an error too asset-hub-westend-collator1: run ../scripts/sync-exit.sh within 600 seconds diff --git a/bridges/zombienet/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl b/bridges/testing/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl similarity index 77% rename from bridges/zombienet/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl rename to bridges/testing/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl index 33c3ceebcf8..a6b11fc2405 100644 --- a/bridges/zombienet/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl +++ b/bridges/testing/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl @@ -1,5 +1,5 @@ Description: While relayer is active, we only sync mandatory and required Westend (and Westend BH) headers to Rococo BH. -Network: ../../../cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml +Network: ../environments/rococo-westend/bridge_hub_rococo_local_network.toml Creds: config # step 1: initialize Rococo AH @@ -9,7 +9,7 @@ asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "init-asset-hub bridge-hub-rococo-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-rococo-local" within 60 seconds # step 3: ensure that initialization has completed -asset-hub-rococo-collator1: js-script ../helpers/wait-hrmp-channel-opened.js with "1013" within 600 seconds +asset-hub-rococo-collator1: js-script ../js-helpers/wait-hrmp-channel-opened.js with "1013" within 600 seconds # step 4: send message from Rococo to Westend asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "reserve-transfer-assets-from-asset-hub-rococo-local" within 60 seconds @@ -20,7 +20,7 @@ asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "reserve-transf bridge-hub-rococo-collator1: run ../scripts/start-relayer.sh within 60 seconds # step 6: ensure that relayer won't sync any extra headers while delivering messages and confirmations -bridge-hub-rococo-collator1: js-script ../helpers/only-required-headers-synced-when-active.js with "500,westend-at-rococo" within 600 seconds +bridge-hub-rococo-collator1: js-script ../js-helpers/only-required-headers-synced-when-active.js with "500,westend-at-rococo" within 600 seconds # wait until other network test has completed OR exit with an error too asset-hub-rococo-collator1: run ../scripts/sync-exit.sh within 600 seconds diff --git a/cumulus/scripts/bridges_common.sh b/bridges/testing/utils/bridges.sh similarity index 99% rename from cumulus/scripts/bridges_common.sh rename to bridges/testing/utils/bridges.sh index 2064dbbb5b8..cfde5dfd26b 100755 --- a/cumulus/scripts/bridges_common.sh +++ b/bridges/testing/utils/bridges.sh @@ -65,7 +65,7 @@ function generate_hex_encoded_call_data() { shift echo "Input params: $@" - node $(dirname "$0")/generate_hex_encoded_call "$type" "$endpoint" "$output" "$@" + node ${BASH_SOURCE%/*}/../utils/generate_hex_encoded_call "$type" "$endpoint" "$output" "$@" local retVal=$? if [ $type != "check" ]; then diff --git a/bridges/zombienet/utils/common.sh b/bridges/testing/utils/common.sh similarity index 100% rename from bridges/zombienet/utils/common.sh rename to bridges/testing/utils/common.sh diff --git a/cumulus/scripts/generate_hex_encoded_call/index.js b/bridges/testing/utils/generate_hex_encoded_call/index.js similarity index 100% rename from cumulus/scripts/generate_hex_encoded_call/index.js rename to bridges/testing/utils/generate_hex_encoded_call/index.js diff --git a/cumulus/scripts/generate_hex_encoded_call/package-lock.json b/bridges/testing/utils/generate_hex_encoded_call/package-lock.json similarity index 100% rename from cumulus/scripts/generate_hex_encoded_call/package-lock.json rename to bridges/testing/utils/generate_hex_encoded_call/package-lock.json diff --git a/cumulus/scripts/generate_hex_encoded_call/package.json b/bridges/testing/utils/generate_hex_encoded_call/package.json similarity index 100% rename from cumulus/scripts/generate_hex_encoded_call/package.json rename to bridges/testing/utils/generate_hex_encoded_call/package.json diff --git a/bridges/zombienet/utils/zombienet.sh b/bridges/testing/utils/zombienet.sh similarity index 100% rename from bridges/zombienet/utils/zombienet.sh rename to bridges/testing/utils/zombienet.sh diff --git a/bridges/zombienet/environments/rococo-westend/helper.sh b/bridges/zombienet/environments/rococo-westend/helper.sh deleted file mode 100755 index 049693e0e29..00000000000 --- a/bridges/zombienet/environments/rococo-westend/helper.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -$POLKADOT_SDK_PATH/cumulus/scripts/bridges_rococo_westend.sh "$@" diff --git a/bridges/zombienet/environments/rococo-westend/rococo-init.zndsl b/bridges/zombienet/environments/rococo-westend/rococo-init.zndsl deleted file mode 100644 index ae3e3e7cb67..00000000000 --- a/bridges/zombienet/environments/rococo-westend/rococo-init.zndsl +++ /dev/null @@ -1,8 +0,0 @@ -Description: User is able to transfer WND from Westend Asset Hub to Rococo Asset Hub and back -Network: ../../../../cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml -Creds: config - -# ensure that initialization has completed -asset-hub-rococo-collator1: js-script ../../helpers/wait-hrmp-channel-opened.js with "1013" within 300 seconds - - diff --git a/bridges/zombienet/environments/rococo-westend/rococo.zndsl b/bridges/zombienet/environments/rococo-westend/rococo.zndsl deleted file mode 100644 index 7ecb05ceefa..00000000000 --- a/bridges/zombienet/environments/rococo-westend/rococo.zndsl +++ /dev/null @@ -1,7 +0,0 @@ -Description: User is able to transfer WND from Westend Asset Hub to Rococo Asset Hub and back -Network: ../../../../cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml -Creds: config - -# relay is already started - let's wait until with-Westend GRANPDA pallet is initialized at Rococo -bridge-hub-rococo-collator1: js-script ../../helpers/best-finalized-header-at-bridged-chain.js with "Westend,0" within 400 seconds - diff --git a/bridges/zombienet/environments/rococo-westend/westend-init.zndsl b/bridges/zombienet/environments/rococo-westend/westend-init.zndsl deleted file mode 100644 index 3b3e4bd91e2..00000000000 --- a/bridges/zombienet/environments/rococo-westend/westend-init.zndsl +++ /dev/null @@ -1,7 +0,0 @@ -Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back -Network: ../../../../cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml -Creds: config - -# ensure that initialization has completed -asset-hub-westend-collator1: js-script ../../helpers/wait-hrmp-channel-opened.js with "1002" within 600 seconds - diff --git a/bridges/zombienet/environments/rococo-westend/westend.zndsl b/bridges/zombienet/environments/rococo-westend/westend.zndsl deleted file mode 100644 index b3864d12a0e..00000000000 --- a/bridges/zombienet/environments/rococo-westend/westend.zndsl +++ /dev/null @@ -1,6 +0,0 @@ -Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back -Network: ../../../../cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml -Creds: config - -# relay is already started - let's wait until with-Rococo GRANPDA pallet is initialized at Westend -bridge-hub-westend-collator1: js-script ../../helpers/best-finalized-header-at-bridged-chain.js with "Rococo,0" within 400 seconds diff --git a/bridges/zombienet/tests/0001-asset-transfer-works-rococo-to-westend.zndsl b/bridges/zombienet/tests/0001-asset-transfer-works-rococo-to-westend.zndsl deleted file mode 100644 index 82d1eee2f45..00000000000 --- a/bridges/zombienet/tests/0001-asset-transfer-works-rococo-to-westend.zndsl +++ /dev/null @@ -1,39 +0,0 @@ -Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back -Network: ../../../cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml -Creds: config - -# step 0: start relayer -# (started by sibling 0001-asset-transfer-works-westend-to-rococo.zndsl test) - -# step 1: initialize Westend AH -asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "init-asset-hub-westend-local" within 60 seconds - -# step 2: initialize Westend bridge hub -bridge-hub-westend-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-westend-local" within 60 seconds - -# step 3: ensure that initialization has completed -asset-hub-westend-collator1: js-script ../helpers/wait-hrmp-channel-opened.js with "1002" within 600 seconds - -# step 4: relay is already started - let's wait until with-Rococo GRANPDA pallet is initialized at Westend -bridge-hub-westend-collator1: js-script ../helpers/best-finalized-header-at-bridged-chain.js with "Rococo,0" within 400 seconds - -# step 5: send WND to //Alice on Rococo AH -# (that's a required part of a sibling 0001-asset-transfer-works-westend-to-rococo.zndsl test) -asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "reserve-transfer-assets-from-asset-hub-westend-local" within 60 seconds - -# step 6: elsewhere Rococo has sent ROC to //Alice - let's wait for it -asset-hub-westend-collator1: js-script ../helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,0,Rococo" within 600 seconds - -# step 7: check that the relayer //Charlie is rewarded by both our AH and target AH -bridge-hub-westend-collator1: js-script ../helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x6268726f,BridgedChain,0" within 300 seconds -bridge-hub-westend-collator1: js-script ../helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x6268726F,ThisChain,0" within 300 seconds - -# step 8: send wROC back to Alice at Rococo AH -asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "withdraw-reserve-assets-from-asset-hub-westend-local" within 60 seconds - -# step 9: elsewhere Rococo has sent wWND to //Alice - let's wait for it -# (we wait until //Alice account increases here - there are no other transactionc that may increase it) -asset-hub-westend-collator1: js-script ../helpers/native-assets-balance-increased.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" within 600 seconds - -# wait until other network test has completed OR exit with an error too -asset-hub-westend-collator1: run ../scripts/sync-exit.sh within 600 seconds diff --git a/bridges/zombienet/tests/0001-asset-transfer-works-westend-to-rococo.zndsl b/bridges/zombienet/tests/0001-asset-transfer-works-westend-to-rococo.zndsl deleted file mode 100644 index acfe0df03d2..00000000000 --- a/bridges/zombienet/tests/0001-asset-transfer-works-westend-to-rococo.zndsl +++ /dev/null @@ -1,39 +0,0 @@ -Description: User is able to transfer WND from Westend Asset Hub to Rococo Asset Hub and back -Network: ../../../cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml -Creds: config - -# step 0: start relayer -bridge-hub-rococo-collator1: run ../scripts/start-relayer.sh within 60 seconds - -# step 1: initialize Rococo AH -asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "init-asset-hub-rococo-local" within 60 seconds - -# step 2: initialize Rococo bridge hub -bridge-hub-rococo-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-rococo-local" within 60 seconds - -# step 3: ensure that initialization has completed -asset-hub-rococo-collator1: js-script ../helpers/wait-hrmp-channel-opened.js with "1013" within 600 seconds - -# step 4: relay is already started - let's wait until with-Westend GRANPDA pallet is initialized at Rococo -bridge-hub-rococo-collator1: js-script ../helpers/best-finalized-header-at-bridged-chain.js with "Westend,0" within 400 seconds - -# step 5: send ROC to //Alice on Westend AH -# (that's a required part of a sibling 0001-asset-transfer-works-rococo-to-westend.zndsl test) -asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "reserve-transfer-assets-from-asset-hub-rococo-local" within 60 seconds - -# step 6: elsewhere Westend has sent WND to //Alice - let's wait for it -asset-hub-rococo-collator1: js-script ../helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,0,Westend" within 600 seconds - -# step 7: check that the relayer //Charlie is rewarded by both our AH and target AH -bridge-hub-rococo-collator1: js-script ../helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x62687764,BridgedChain,0" within 300 seconds -bridge-hub-rococo-collator1: js-script ../helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x62687764,ThisChain,0" within 300 seconds - -# step 8: send wWND back to Alice at Westend AH -asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "withdraw-reserve-assets-from-asset-hub-rococo-local" within 60 seconds - -# step 9: elsewhere Westend has sent wROC to //Alice - let's wait for it -# (we wait until //Alice account increases here - there are no other transactionc that may increase it) -asset-hub-rococo-collator1: js-script ../helpers/native-assets-balance-increased.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" within 600 seconds - -# wait until other network test has completed OR exit with an error too -asset-hub-rococo-collator1: run ../scripts/sync-exit.sh within 600 seconds diff --git a/bridges/zombienet/tests/0001-asset-transfer/roc-reaches-westend.zndsl b/bridges/zombienet/tests/0001-asset-transfer/roc-reaches-westend.zndsl deleted file mode 100644 index c7fb73a88c0..00000000000 --- a/bridges/zombienet/tests/0001-asset-transfer/roc-reaches-westend.zndsl +++ /dev/null @@ -1,12 +0,0 @@ -Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back -Network: ../../../../cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml -Creds: config - -# send ROC to //Alice from Rococo AH to Westend AH -asset-hub-westend-collator1: run ../../environments/rococo-westend/helper.sh with "reserve-transfer-assets-from-asset-hub-rococo-local" within 120 seconds - -# check that //Alice received the ROC on Westend AH -asset-hub-westend-collator1: js-script ../../helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,0,Rococo" within 300 seconds - -# check that the relayer //Charlie is rewarded by Westend AH -bridge-hub-westend-collator1: js-script ../../helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x6268726F,ThisChain,0" within 30 seconds diff --git a/bridges/zombienet/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl b/bridges/zombienet/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl deleted file mode 100644 index 91ce8e5cbff..00000000000 --- a/bridges/zombienet/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl +++ /dev/null @@ -1,12 +0,0 @@ -Description: User is able to transfer WND from Westend Asset Hub to Rococo Asset Hub and back -Network: ../../../../cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml -Creds: config - -# send WND to //Alice from Westend AH to Rococo AH -asset-hub-rococo-collator1: run ../../environments/rococo-westend/helper.sh with "reserve-transfer-assets-from-asset-hub-westend-local" within 120 seconds - -# check that //Alice received the WND on Rococo AH -asset-hub-rococo-collator1: js-script ../../helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,0,Westend" within 300 seconds - -# check that the relayer //Charlie is rewarded by Rococo AH -bridge-hub-rococo-collator1: js-script ../../helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x62687764,ThisChain,0" within 30 seconds diff --git a/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle-rococo-to-westend.zndsl b/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle-rococo-to-westend.zndsl deleted file mode 100644 index eb6a75c373c..00000000000 --- a/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle-rococo-to-westend.zndsl +++ /dev/null @@ -1,26 +0,0 @@ -Description: While relayer is idle, we only sync mandatory Rococo (and a single Rococo BH) headers to Westend BH. -Network: ../../../cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml -Creds: config - -# step 1: initialize Westend bridge hub -bridge-hub-westend-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-westend-local" within 60 seconds - -# step 2: sleep some time before starting relayer. We want to sleep for at least 1 session, which is expected to -# be 60 seconds for test environment. -sleep 120 seconds - -# step 3: start relayer -# (it is started by the sibling 0002-mandatory-headers-synced-while-idle-westend-to-rococo.zndsl test file) - -# it also takes some time for relayer to initialize bridge, so let's sleep for 5 minutes to be sure that parachain -# header has been synced - -# step 4: ensure that relayer is only syncing mandatory headers while idle. This includes both headers that were -# born while relay was offline and those in the next 100 seconds while script is active. -bridge-hub-westend-collator1: js-script ../helpers/only-mandatory-headers-synced-when-idle.js with "300,rococo-at-westend" within 600 seconds - -# wait until other network test has completed OR exit with an error too -asset-hub-westend-collator1: run ../scripts/sync-exit.sh within 600 seconds - -# wait until other network test has completed OR exit with an error too -asset-hub-westend-collator1: run ../scripts/sync-exit.sh within 600 seconds diff --git a/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle-westend-to-rococo.zndsl b/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle-westend-to-rococo.zndsl deleted file mode 100644 index 728d54d586a..00000000000 --- a/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle-westend-to-rococo.zndsl +++ /dev/null @@ -1,26 +0,0 @@ -Description: While relayer is idle, we only sync mandatory Westend (and a single Westend BH) headers to Rococo BH. -Network: ../../../cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml -Creds: config - -# step 1: initialize Rococo bridge hub -bridge-hub-rococo-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-rococo-local" within 60 seconds - -# step 2: sleep some time before starting relayer. We want to sleep for at least 1 session, which is expected to -# be 60 seconds for test environment. -sleep 120 seconds - -# step 3: start relayer -bridge-hub-rococo-collator1: run ../scripts/start-relayer.sh within 60 seconds - -# it also takes some time for relayer to initialize bridge, so let's sleep for 5 minutes to be sure that parachain -# header has been synced - -# step 4: ensure that relayer is only syncing mandatory headers while idle. This includes both headers that were -# born while relay was offline and those in the next 100 seconds while script is active. -bridge-hub-rococo-collator1: js-script ../helpers/only-mandatory-headers-synced-when-idle.js with "300,westend-at-rococo" within 600 seconds - -# wait until other network test has completed OR exit with an error too -asset-hub-rococo-collator1: run ../scripts/sync-exit.sh within 600 seconds - -# wait until other network test has completed OR exit with an error too -asset-hub-rococo-collator1: run ../scripts/sync-exit.sh within 600 seconds diff --git a/cumulus/parachains/runtimes/bridge-hubs/README.md b/cumulus/parachains/runtimes/bridge-hubs/README.md index 4e9403bf25e..c858532295d 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/README.md +++ b/cumulus/parachains/runtimes/bridge-hubs/README.md @@ -91,7 +91,7 @@ cd # Rococo + BridgeHubRococo + AssetHub for Rococo (mirroring Kusama) POLKADOT_BINARY=~/local_bridge_testing/bin/polkadot \ POLKADOT_PARACHAIN_BINARY=~/local_bridge_testing/bin/polkadot-parachain \ - ~/local_bridge_testing/bin/zombienet-linux --provider native spawn ./cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml + ~/local_bridge_testing/bin/zombienet-linux --provider native spawn ./bridges/testing/environments/rococo-westend/bridge_hub_rococo_local_network.toml ``` ``` @@ -100,7 +100,7 @@ cd # Westend + BridgeHubWestend + AssetHub for Westend (mirroring Polkadot) POLKADOT_BINARY=~/local_bridge_testing/bin/polkadot \ POLKADOT_PARACHAIN_BINARY=~/local_bridge_testing/bin/polkadot-parachain \ - ~/local_bridge_testing/bin/zombienet-linux --provider native spawn ./cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml + ~/local_bridge_testing/bin/zombienet-linux --provider native spawn ./bridges/testing/environments/rococo-westend/bridge_hub_westend_local_network.toml ``` ### Init bridge and run relayer between BridgeHubRococo and BridgeHubWestend @@ -112,7 +112,7 @@ POLKADOT_PARACHAIN_BINARY=~/local_bridge_testing/bin/polkadot-parachain \ ``` cd -./cumulus/scripts/bridges_rococo_westend.sh run-relay +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh run-relay ``` **Check relay-chain headers relaying:** @@ -135,10 +135,10 @@ This initialization does several things: ``` cd -./cumulus/scripts/bridges_rococo_westend.sh init-asset-hub-rococo-local -./cumulus/scripts/bridges_rococo_westend.sh init-bridge-hub-rococo-local -./cumulus/scripts/bridges_rococo_westend.sh init-asset-hub-westend-local -./cumulus/scripts/bridges_rococo_westend.sh init-bridge-hub-westend-local +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh init-asset-hub-rococo-local +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh init-bridge-hub-rococo-local +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh init-asset-hub-westend-local +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh init-bridge-hub-westend-local ``` ### Send messages - transfer asset over bridge (ROCs/WNDs) @@ -148,13 +148,13 @@ Do reserve-backed transfers: cd # ROCs from Rococo's Asset Hub to Westend's. -./cumulus/scripts/bridges_rococo_westend.sh reserve-transfer-assets-from-asset-hub-rococo-local +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh reserve-transfer-assets-from-asset-hub-rococo-local ``` ``` cd # WNDs from Westend's Asset Hub to Rococo's. -./cumulus/scripts/bridges_rococo_westend.sh reserve-transfer-assets-from-asset-hub-westend-local +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh reserve-transfer-assets-from-asset-hub-westend-local ``` - open explorers: (see zombienets) @@ -169,13 +169,13 @@ Do reserve withdraw transfers: (when previous is finished) cd # wrappedWNDs from Rococo's Asset Hub to Westend's. -./cumulus/scripts/bridges_rococo_westend.sh withdraw-reserve-assets-from-asset-hub-rococo-local +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh withdraw-reserve-assets-from-asset-hub-rococo-local ``` ``` cd # wrappedROCs from Westend's Asset Hub to Rococo's. -./cumulus/scripts/bridges_rococo_westend.sh withdraw-reserve-assets-from-asset-hub-westend-local +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh withdraw-reserve-assets-from-asset-hub-westend-local ``` ### Claim relayer's rewards on BridgeHubRococo and BridgeHubWestend @@ -188,10 +188,10 @@ cd cd # Claim rewards on BridgeHubWestend: -./cumulus/scripts/bridges_rococo_westend.sh claim-rewards-bridge-hub-rococo-local +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh claim-rewards-bridge-hub-rococo-local # Claim rewards on BridgeHubWestend: -./cumulus/scripts/bridges_rococo_westend.sh claim-rewards-bridge-hub-westend-local +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh claim-rewards-bridge-hub-westend-local ``` - open explorers: (see zombienets) diff --git a/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile b/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile index ec8881c5a33..fde9cc6e7cf 100644 --- a/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile +++ b/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile @@ -22,7 +22,7 @@ LABEL io.parity.image.authors="devops-team@parity.io" \ io.parity.image.source="https://github.com/paritytech/polkadot-sdk/blob/${VCS_REF}/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile" \ io.parity.image.revision="${VCS_REF}" \ io.parity.image.created="${BUILD_DATE}" \ - io.parity.image.documentation="https://github.com/paritytech/polkadot-sdk/bridges/zombienet" + io.parity.image.documentation="https://github.com/paritytech/polkadot-sdk/bridges/testing" # show backtraces ENV RUST_BACKTRACE 1 @@ -45,7 +45,7 @@ RUN mkdir -p /home/nonroot/bridges-polkadot-sdk COPY ./artifacts/bridges-polkadot-sdk /home/nonroot/bridges-polkadot-sdk # also prepare `generate_hex_encoded_call` for running RUN set -eux; \ - cd /home/nonroot/bridges-polkadot-sdk/cumulus/scripts/generate_hex_encoded_call; \ + cd /home/nonroot/bridges-polkadot-sdk/bridges/testing/utils/generate_hex_encoded_call; \ npm install # check if executable works in this container -- GitLab From fde44474e4e416d15f191aaf79e85eaacad0a9fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Thu, 15 Feb 2024 20:13:35 +0100 Subject: [PATCH 067/133] Implements a % cap on staking rewards from era inflation (#1660) This PR implements an (optional) cap of the era inflation that is allocated to staking rewards. The remaining is minted directly into the [`RewardRemainder`](https://github.com/paritytech/polkadot-sdk/blob/fb0fd3e62445eb2dee2b2456a0c8574d1ecdcc73/substrate/frame/staking/src/pallet/mod.rs#L160) account, which is the treasury pot account in Polkadot and Kusama. The staking pallet now has a percent storage item, `MaxStakersRewards`, which defines the max percentage of the era inflation that should be allocated to staking rewards. The remaining era inflation (i.e. `remaining = max_era_payout - staking_payout.min(staking_payout * MaxStakersRewards))` is minted directly into the treasury. The `MaxStakersRewards` can be set by a privileged origin through the `set_staking_configs` extrinsic. **To finish** - [x] run benchmarks for westend-runtime Replaces https://github.com/paritytech/polkadot-sdk/pull/1483 Closes https://github.com/paritytech/polkadot-sdk/issues/403 --------- Co-authored-by: command-bot <> --- .../westend/src/weights/pallet_staking.rs | 206 +++++----- prdoc/pr_1660.prdoc | 12 + .../nomination-pools/test-staking/src/mock.rs | 1 + substrate/frame/staking/src/benchmarking.rs | 7 +- substrate/frame/staking/src/lib.rs | 14 +- substrate/frame/staking/src/pallet/impls.rs | 11 +- substrate/frame/staking/src/pallet/mod.rs | 8 + substrate/frame/staking/src/tests.rs | 88 +++- substrate/frame/staking/src/weights.rs | 380 +++++++++--------- 9 files changed, 427 insertions(+), 300 deletions(-) create mode 100644 prdoc/pr_1660.prdoc diff --git a/polkadot/runtime/westend/src/weights/pallet_staking.rs b/polkadot/runtime/westend/src/weights/pallet_staking.rs index 1ecd44747ef..7a641e36a12 100644 --- a/polkadot/runtime/westend/src/weights/pallet_staking.rs +++ b/polkadot/runtime/westend/src/weights/pallet_staking.rs @@ -16,10 +16,10 @@ //! Autogenerated weights for `pallet_staking` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-12-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-01-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-itmxxexx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-8idpd4bs-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: @@ -62,8 +62,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `894` // Estimated: `4764` - // Minimum execution time: 38_316_000 picoseconds. - Weight::from_parts(40_022_000, 0) + // Minimum execution time: 37_340_000 picoseconds. + Weight::from_parts(38_930_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(4)) @@ -84,8 +84,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1921` // Estimated: `8877` - // Minimum execution time: 81_027_000 picoseconds. - Weight::from_parts(83_964_000, 0) + // Minimum execution time: 80_630_000 picoseconds. + Weight::from_parts(82_196_000, 0) .saturating_add(Weight::from_parts(0, 8877)) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(7)) @@ -112,8 +112,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `2128` // Estimated: `8877` - // Minimum execution time: 85_585_000 picoseconds. - Weight::from_parts(87_256_000, 0) + // Minimum execution time: 83_523_000 picoseconds. + Weight::from_parts(86_639_000, 0) .saturating_add(Weight::from_parts(0, 8877)) .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(7)) @@ -133,11 +133,11 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1075` // Estimated: `4764` - // Minimum execution time: 39_520_000 picoseconds. - Weight::from_parts(41_551_548, 0) + // Minimum execution time: 38_636_000 picoseconds. + Weight::from_parts(40_399_283, 0) .saturating_add(Weight::from_parts(0, 4764)) - // Standard Error: 1_094 - .saturating_add(Weight::from_parts(50_426, 0).saturating_mul(s.into())) + // Standard Error: 869 + .saturating_add(Weight::from_parts(37_752, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -174,11 +174,11 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `2127 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 82_915_000 picoseconds. - Weight::from_parts(89_597_160, 0) + // Minimum execution time: 81_301_000 picoseconds. + Weight::from_parts(88_609_205, 0) .saturating_add(Weight::from_parts(0, 6248)) - // Standard Error: 3_146 - .saturating_add(Weight::from_parts(1_228_061, 0).saturating_mul(s.into())) + // Standard Error: 3_388 + .saturating_add(Weight::from_parts(1_253_692, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(13)) .saturating_add(T::DbWeight::get().writes(11)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -210,8 +210,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1301` // Estimated: `4556` - // Minimum execution time: 48_070_000 picoseconds. - Weight::from_parts(49_226_000, 0) + // Minimum execution time: 47_292_000 picoseconds. + Weight::from_parts(48_566_000, 0) .saturating_add(Weight::from_parts(0, 4556)) .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(5)) @@ -225,11 +225,11 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1243 + k * (569 ±0)` // Estimated: `4556 + k * (3033 ±0)` - // Minimum execution time: 29_140_000 picoseconds. - Weight::from_parts(30_225_579, 0) + // Minimum execution time: 28_840_000 picoseconds. + Weight::from_parts(27_510_817, 0) .saturating_add(Weight::from_parts(0, 4556)) - // Standard Error: 5_394 - .saturating_add(Weight::from_parts(6_401_367, 0).saturating_mul(k.into())) + // Standard Error: 6_603 + .saturating_add(Weight::from_parts(6_268_853, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) @@ -262,11 +262,11 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1797 + n * (102 ±0)` // Estimated: `6248 + n * (2520 ±0)` - // Minimum execution time: 59_287_000 picoseconds. - Weight::from_parts(58_285_052, 0) + // Minimum execution time: 57_537_000 picoseconds. + Weight::from_parts(55_854_233, 0) .saturating_add(Weight::from_parts(0, 6248)) - // Standard Error: 14_556 - .saturating_add(Weight::from_parts(3_863_008, 0).saturating_mul(n.into())) + // Standard Error: 14_427 + .saturating_add(Weight::from_parts(3_844_957, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(6)) @@ -290,8 +290,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1581` // Estimated: `6248` - // Minimum execution time: 51_035_000 picoseconds. - Weight::from_parts(52_163_000, 0) + // Minimum execution time: 49_997_000 picoseconds. + Weight::from_parts(51_266_000, 0) .saturating_add(Weight::from_parts(0, 6248)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(6)) @@ -306,8 +306,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `865` // Estimated: `4556` - // Minimum execution time: 15_809_000 picoseconds. - Weight::from_parts(16_451_000, 0) + // Minimum execution time: 15_342_000 picoseconds. + Weight::from_parts(15_970_000, 0) .saturating_add(Weight::from_parts(0, 4556)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -322,8 +322,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `932` // Estimated: `4556` - // Minimum execution time: 21_695_000 picoseconds. - Weight::from_parts(22_351_000, 0) + // Minimum execution time: 20_719_000 picoseconds. + Weight::from_parts(21_373_000, 0) .saturating_add(Weight::from_parts(0, 4556)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -336,8 +336,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `865` // Estimated: `4556` - // Minimum execution time: 18_548_000 picoseconds. - Weight::from_parts(19_205_000, 0) + // Minimum execution time: 18_237_000 picoseconds. + Weight::from_parts(18_896_000, 0) .saturating_add(Weight::from_parts(0, 4556)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) @@ -348,8 +348,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_193_000 picoseconds. - Weight::from_parts(2_408_000, 0) + // Minimum execution time: 1_946_000 picoseconds. + Weight::from_parts(2_131_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -359,8 +359,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_475_000 picoseconds. - Weight::from_parts(7_874_000, 0) + // Minimum execution time: 6_840_000 picoseconds. + Weight::from_parts(7_208_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -370,8 +370,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_393_000 picoseconds. - Weight::from_parts(7_643_000, 0) + // Minimum execution time: 6_812_000 picoseconds. + Weight::from_parts(7_254_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -381,8 +381,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_474_000 picoseconds. - Weight::from_parts(7_814_000, 0) + // Minimum execution time: 6_787_000 picoseconds. + Weight::from_parts(7_206_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -393,11 +393,11 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_358_000 picoseconds. - Weight::from_parts(2_589_423, 0) + // Minimum execution time: 2_045_000 picoseconds. + Weight::from_parts(2_281_841, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 81 - .saturating_add(Weight::from_parts(13_612, 0).saturating_mul(v.into())) + // Standard Error: 70 + .saturating_add(Weight::from_parts(11_592, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Staking::Ledger` (r:751 w:1502) @@ -411,11 +411,11 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `668 + i * (148 ±0)` // Estimated: `990 + i * (3566 ±0)` - // Minimum execution time: 1_934_000 picoseconds. - Weight::from_parts(2_070_000, 0) + // Minimum execution time: 1_657_000 picoseconds. + Weight::from_parts(1_702_000, 0) .saturating_add(Weight::from_parts(0, 990)) - // Standard Error: 19_129 - .saturating_add(Weight::from_parts(13_231_580, 0).saturating_mul(i.into())) + // Standard Error: 20_041 + .saturating_add(Weight::from_parts(13_165_254, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(i.into()))) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(i.into()))) .saturating_add(Weight::from_parts(0, 3566).saturating_mul(i.into())) @@ -453,11 +453,11 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `2127 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 80_290_000 picoseconds. - Weight::from_parts(87_901_664, 0) + // Minimum execution time: 78_774_000 picoseconds. + Weight::from_parts(85_770_713, 0) .saturating_add(Weight::from_parts(0, 6248)) - // Standard Error: 2_960 - .saturating_add(Weight::from_parts(1_195_050, 0).saturating_mul(s.into())) + // Standard Error: 2_815 + .saturating_add(Weight::from_parts(1_244_494, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(13)) .saturating_add(T::DbWeight::get().writes(12)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -470,11 +470,11 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `66639` // Estimated: `70104` - // Minimum execution time: 132_682_000 picoseconds. - Weight::from_parts(932_504_297, 0) + // Minimum execution time: 129_905_000 picoseconds. + Weight::from_parts(932_195_554, 0) .saturating_add(Weight::from_parts(0, 70104)) - // Standard Error: 57_593 - .saturating_add(Weight::from_parts(4_829_705, 0).saturating_mul(s.into())) + // Standard Error: 57_492 + .saturating_add(Weight::from_parts(4_826_754, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -510,12 +510,12 @@ impl pallet_staking::WeightInfo for WeightInfo { fn payout_stakers_alive_staked(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `8249 + n * (396 ±0)` - // Estimated: `10779 + n * (3774 ±3)` - // Minimum execution time: 129_091_000 picoseconds. - Weight::from_parts(166_186_167, 0) + // Estimated: `10779 + n * (3774 ±0)` + // Minimum execution time: 127_094_000 picoseconds. + Weight::from_parts(160_088_053, 0) .saturating_add(Weight::from_parts(0, 10779)) - // Standard Error: 36_242 - .saturating_add(Weight::from_parts(40_467_481, 0).saturating_mul(n.into())) + // Standard Error: 32_978 + .saturating_add(Weight::from_parts(39_845_710, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(14)) .saturating_add(T::DbWeight::get().reads((6_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(4)) @@ -539,11 +539,11 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1922 + l * (5 ±0)` // Estimated: `8877` - // Minimum execution time: 77_461_000 picoseconds. - Weight::from_parts(80_118_021, 0) + // Minimum execution time: 75_672_000 picoseconds. + Weight::from_parts(78_708_335, 0) .saturating_add(Weight::from_parts(0, 8877)) - // Standard Error: 4_343 - .saturating_add(Weight::from_parts(59_113, 0).saturating_mul(l.into())) + // Standard Error: 3_387 + .saturating_add(Weight::from_parts(37_084, 0).saturating_mul(l.into())) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(7)) } @@ -578,11 +578,11 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `2127 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 89_366_000 picoseconds. - Weight::from_parts(91_964_557, 0) + // Minimum execution time: 87_991_000 picoseconds. + Weight::from_parts(90_272_005, 0) .saturating_add(Weight::from_parts(0, 6248)) - // Standard Error: 2_799 - .saturating_add(Weight::from_parts(1_206_123, 0).saturating_mul(s.into())) + // Standard Error: 2_815 + .saturating_add(Weight::from_parts(1_232_322, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(11)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -627,14 +627,14 @@ impl pallet_staking::WeightInfo for WeightInfo { fn new_era(v: u32, n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0 + n * (716 ±0) + v * (3594 ±0)` - // Estimated: `456136 + n * (3566 ±4) + v * (3566 ±40)` - // Minimum execution time: 520_430_000 picoseconds. - Weight::from_parts(527_125_000, 0) + // Estimated: `456136 + n * (3566 ±0) + v * (3566 ±0)` + // Minimum execution time: 528_862_000 picoseconds. + Weight::from_parts(534_620_000, 0) .saturating_add(Weight::from_parts(0, 456136)) - // Standard Error: 1_974_092 - .saturating_add(Weight::from_parts(64_885_491, 0).saturating_mul(v.into())) - // Standard Error: 196_707 - .saturating_add(Weight::from_parts(18_100_326, 0).saturating_mul(n.into())) + // Standard Error: 2_005_553 + .saturating_add(Weight::from_parts(65_586_008, 0).saturating_mul(v.into())) + // Standard Error: 199_842 + .saturating_add(Weight::from_parts(18_155_389, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(184)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -665,13 +665,13 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `3108 + n * (907 ±0) + v * (391 ±0)` // Estimated: `456136 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 33_917_323_000 picoseconds. - Weight::from_parts(34_173_565_000, 0) + // Minimum execution time: 33_532_110_000 picoseconds. + Weight::from_parts(33_926_321_000, 0) .saturating_add(Weight::from_parts(0, 456136)) - // Standard Error: 367_135 - .saturating_add(Weight::from_parts(4_696_840, 0).saturating_mul(v.into())) - // Standard Error: 367_135 - .saturating_add(Weight::from_parts(3_889_075, 0).saturating_mul(n.into())) + // Standard Error: 374_134 + .saturating_add(Weight::from_parts(4_627_629, 0).saturating_mul(v.into())) + // Standard Error: 374_134 + .saturating_add(Weight::from_parts(4_068_168, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(179)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -688,11 +688,11 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `946 + v * (50 ±0)` // Estimated: `3510 + v * (2520 ±0)` - // Minimum execution time: 2_447_197_000 picoseconds. - Weight::from_parts(13_003_614, 0) + // Minimum execution time: 2_395_956_000 picoseconds. + Weight::from_parts(88_416_870, 0) .saturating_add(Weight::from_parts(0, 3510)) - // Standard Error: 9_738 - .saturating_add(Weight::from_parts(4_953_442, 0).saturating_mul(v.into())) + // Standard Error: 8_731 + .saturating_add(Weight::from_parts(4_750_956, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(v.into()))) .saturating_add(Weight::from_parts(0, 2520).saturating_mul(v.into())) @@ -703,6 +703,8 @@ impl pallet_staking::WeightInfo for WeightInfo { /// Proof: `Staking::MinValidatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `Staking::MaxValidatorsCount` (r:0 w:1) /// Proof: `Staking::MaxValidatorsCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::MaxStakedRewards` (r:0 w:1) + /// Proof: `Staking::MaxStakedRewards` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) /// Storage: `Staking::ChillThreshold` (r:0 w:1) /// Proof: `Staking::ChillThreshold` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) /// Storage: `Staking::MaxNominatorsCount` (r:0 w:1) @@ -713,10 +715,10 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_714_000 picoseconds. - Weight::from_parts(3_956_000, 0) + // Minimum execution time: 3_761_000 picoseconds. + Weight::from_parts(4_013_000, 0) .saturating_add(Weight::from_parts(0, 0)) - .saturating_add(T::DbWeight::get().writes(6)) + .saturating_add(T::DbWeight::get().writes(7)) } /// Storage: `Staking::MinCommission` (r:0 w:1) /// Proof: `Staking::MinCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -724,6 +726,8 @@ impl pallet_staking::WeightInfo for WeightInfo { /// Proof: `Staking::MinValidatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `Staking::MaxValidatorsCount` (r:0 w:1) /// Proof: `Staking::MaxValidatorsCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::MaxStakedRewards` (r:0 w:1) + /// Proof: `Staking::MaxStakedRewards` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) /// Storage: `Staking::ChillThreshold` (r:0 w:1) /// Proof: `Staking::ChillThreshold` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) /// Storage: `Staking::MaxNominatorsCount` (r:0 w:1) @@ -734,10 +738,10 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_361_000 picoseconds. - Weight::from_parts(3_632_000, 0) + // Minimum execution time: 3_325_000 picoseconds. + Weight::from_parts(3_519_000, 0) .saturating_add(Weight::from_parts(0, 0)) - .saturating_add(T::DbWeight::get().writes(6)) + .saturating_add(T::DbWeight::get().writes(7)) } /// Storage: `Staking::Bonded` (r:1 w:0) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) @@ -765,8 +769,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1870` // Estimated: `6248` - // Minimum execution time: 65_329_000 picoseconds. - Weight::from_parts(67_247_000, 0) + // Minimum execution time: 63_583_000 picoseconds. + Weight::from_parts(65_917_000, 0) .saturating_add(Weight::from_parts(0, 6248)) .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(6)) @@ -779,8 +783,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `658` // Estimated: `3510` - // Minimum execution time: 11_760_000 picoseconds. - Weight::from_parts(12_095_000, 0) + // Minimum execution time: 10_975_000 picoseconds. + Weight::from_parts(11_328_000, 0) .saturating_add(Weight::from_parts(0, 3510)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -791,8 +795,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_256_000 picoseconds. - Weight::from_parts(2_378_000, 0) + // Minimum execution time: 1_954_000 picoseconds. + Weight::from_parts(2_081_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/prdoc/pr_1660.prdoc b/prdoc/pr_1660.prdoc new file mode 100644 index 00000000000..dda38c95495 --- /dev/null +++ b/prdoc/pr_1660.prdoc @@ -0,0 +1,12 @@ +title: Implements a percentage cap on staking rewards from era inflation + +doc: + - audience: Runtime Dev + description: | + The `pallet-staking` exposes a new perbill configuration, `MaxStakersRewards`, which caps the + amount of era inflation that is distributed to the stakers. The remainder of the era + inflation is minted directly into `T::RewardRemainder` account. This allows the runtime to be + configured to assign a minimum inflation value per era to a specific account (e.g. treasury). + +crates: + - name: pallet-staking diff --git a/substrate/frame/nomination-pools/test-staking/src/mock.rs b/substrate/frame/nomination-pools/test-staking/src/mock.rs index d1b50b9f7ae..ce97e13d640 100644 --- a/substrate/frame/nomination-pools/test-staking/src/mock.rs +++ b/substrate/frame/nomination-pools/test-staking/src/mock.rs @@ -236,6 +236,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pallet_staking::ConfigOp::Noop, pallet_staking::ConfigOp::Noop, pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, )); }); diff --git a/substrate/frame/staking/src/benchmarking.rs b/substrate/frame/staking/src/benchmarking.rs index 7bcc68cdfe6..a8306087397 100644 --- a/substrate/frame/staking/src/benchmarking.rs +++ b/substrate/frame/staking/src/benchmarking.rs @@ -855,7 +855,8 @@ benchmarks! { ConfigOp::Set(u32::MAX), ConfigOp::Set(u32::MAX), ConfigOp::Set(Percent::max_value()), - ConfigOp::Set(Perbill::max_value()) + ConfigOp::Set(Perbill::max_value()), + ConfigOp::Set(Percent::max_value()) ) verify { assert_eq!(MinNominatorBond::::get(), BalanceOf::::max_value()); assert_eq!(MinValidatorBond::::get(), BalanceOf::::max_value()); @@ -863,6 +864,7 @@ benchmarks! { assert_eq!(MaxValidatorsCount::::get(), Some(u32::MAX)); assert_eq!(ChillThreshold::::get(), Some(Percent::from_percent(100))); assert_eq!(MinCommission::::get(), Perbill::from_percent(100)); + assert_eq!(MaxStakedRewards::::get(), Some(Percent::from_percent(100))); } set_staking_configs_all_remove { @@ -873,6 +875,7 @@ benchmarks! { ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Remove, + ConfigOp::Remove, ConfigOp::Remove ) verify { assert!(!MinNominatorBond::::exists()); @@ -881,6 +884,7 @@ benchmarks! { assert!(!MaxValidatorsCount::::exists()); assert!(!ChillThreshold::::exists()); assert!(!MinCommission::::exists()); + assert!(!MaxStakedRewards::::exists()); } chill_other { @@ -904,6 +908,7 @@ benchmarks! { ConfigOp::Set(0), ConfigOp::Set(Percent::from_percent(0)), ConfigOp::Set(Zero::zero()), + ConfigOp::Noop, )?; let caller = whitelisted_caller(); diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index 09185a690be..5a92b6c855f 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -202,6 +202,12 @@ //! ```nocompile //! remaining_payout = max_yearly_inflation * total_tokens / era_per_year - staker_payout //! ``` +//! +//! Note, however, that it is possible to set a cap on the total `staker_payout` for the era through +//! the `MaxStakersRewards` storage type. The `era_payout` implementor must ensure that the +//! `max_payout = remaining_payout + (staker_payout * max_stakers_rewards)`. The excess payout that +//! is not allocated for stakers is the era remaining reward. +//! //! The remaining reward is send to the configurable end-point [`Config::RewardRemainder`]. //! //! ### Reward Calculation @@ -897,8 +903,10 @@ impl EraPayout for () { /// Adaptor to turn a `PiecewiseLinear` curve definition into an `EraPayout` impl, used for /// backwards compatibility. pub struct ConvertCurve(sp_std::marker::PhantomData); -impl>> - EraPayout for ConvertCurve +impl EraPayout for ConvertCurve +where + Balance: AtLeast32BitUnsigned + Clone + Copy, + T: Get<&'static PiecewiseLinear<'static>>, { fn era_payout( total_staked: Balance, @@ -912,7 +920,7 @@ impl Pallet { .saturated_into::(); let staked = Self::eras_total_stake(&active_era.index); let issuance = T::Currency::total_issuance(); + let (validator_payout, remainder) = T::EraPayout::era_payout(staked, issuance, era_duration); + let total_payout = validator_payout.saturating_add(remainder); + let max_staked_rewards = + MaxStakedRewards::::get().unwrap_or(Percent::from_percent(100)); + + // apply cap to validators payout and add difference to remainder. + let validator_payout = validator_payout.min(max_staked_rewards * total_payout); + let remainder = total_payout.saturating_sub(validator_payout); + Self::deposit_event(Event::::EraPaid { era_index: active_era.index, validator_payout, diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index 65e981058e6..e0213efd507 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -564,6 +564,12 @@ pub mod pallet { #[pallet::getter(fn force_era)] pub type ForceEra = StorageValue<_, Forcing, ValueQuery>; + /// Maximum staked rewards, i.e. the percentage of the era inflation that + /// is used for stake rewards. + /// See [Era payout](./index.html#era-payout). + #[pallet::storage] + pub type MaxStakedRewards = StorageValue<_, Percent, OptionQuery>; + /// The percentage of the slash that is distributed to reporters. /// /// The rest of the slashed value is handled by the `Slash`. @@ -1717,6 +1723,7 @@ pub mod pallet { max_validator_count: ConfigOp, chill_threshold: ConfigOp, min_commission: ConfigOp, + max_staked_rewards: ConfigOp, ) -> DispatchResult { ensure_root(origin)?; @@ -1736,6 +1743,7 @@ pub mod pallet { config_op_exp!(MaxValidatorsCount, max_validator_count); config_op_exp!(ChillThreshold, chill_threshold); config_op_exp!(MinCommission, min_commission); + config_op_exp!(MaxStakedRewards, max_staked_rewards); Ok(()) } /// Declare a `controller` to stop participating as either a validator or nominator. diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index 85ee7dd3bf5..3f4e28b1f6a 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -55,6 +55,7 @@ fn set_staking_configs_works() { ConfigOp::Set(10), ConfigOp::Set(20), ConfigOp::Set(Percent::from_percent(75)), + ConfigOp::Set(Zero::zero()), ConfigOp::Set(Zero::zero()) )); assert_eq!(MinNominatorBond::::get(), 1_500); @@ -63,6 +64,7 @@ fn set_staking_configs_works() { assert_eq!(MaxValidatorsCount::::get(), Some(20)); assert_eq!(ChillThreshold::::get(), Some(Percent::from_percent(75))); assert_eq!(MinCommission::::get(), Perbill::from_percent(0)); + assert_eq!(MaxStakedRewards::::get(), Some(Percent::from_percent(0))); // noop does nothing assert_storage_noop!(assert_ok!(Staking::set_staking_configs( @@ -72,6 +74,7 @@ fn set_staking_configs_works() { ConfigOp::Noop, ConfigOp::Noop, ConfigOp::Noop, + ConfigOp::Noop, ConfigOp::Noop ))); @@ -83,6 +86,7 @@ fn set_staking_configs_works() { ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Remove, + ConfigOp::Remove, ConfigOp::Remove )); assert_eq!(MinNominatorBond::::get(), 0); @@ -91,6 +95,7 @@ fn set_staking_configs_works() { assert_eq!(MaxValidatorsCount::::get(), None); assert_eq!(ChillThreshold::::get(), None); assert_eq!(MinCommission::::get(), Perbill::from_percent(0)); + assert_eq!(MaxStakedRewards::::get(), None); }); } @@ -1739,6 +1744,74 @@ fn rebond_emits_right_value_in_event() { }); } +#[test] +fn max_staked_rewards_default_works() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(>::get(), None); + + let default_stakers_payout = current_total_payout_for_duration(reward_time_per_era()); + assert!(default_stakers_payout > 0); + start_active_era(1); + + // the final stakers reward is the same as the reward before applied the cap. + assert_eq!(ErasValidatorReward::::get(0).unwrap(), default_stakers_payout); + + // which is the same behaviour if the `MaxStakedRewards` is set to 100%. + >::set(Some(Percent::from_parts(100))); + + let default_stakers_payout = current_total_payout_for_duration(reward_time_per_era()); + assert_eq!(ErasValidatorReward::::get(0).unwrap(), default_stakers_payout); + }) +} + +#[test] +fn max_staked_rewards_works() { + ExtBuilder::default().nominate(true).build_and_execute(|| { + let max_staked_rewards = 10; + + // sets new max staked rewards through set_staking_configs. + assert_ok!(Staking::set_staking_configs( + RuntimeOrigin::root(), + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Set(Percent::from_percent(max_staked_rewards)), + )); + + assert_eq!(>::get(), Some(Percent::from_percent(10))); + + // check validators account state. + assert_eq!(Session::validators().len(), 2); + assert!(Session::validators().contains(&11) & Session::validators().contains(&21)); + // balance of the mock treasury account is 0 + assert_eq!(RewardRemainderUnbalanced::get(), 0); + + let max_stakers_payout = current_total_payout_for_duration(reward_time_per_era()); + + start_active_era(1); + + let treasury_payout = RewardRemainderUnbalanced::get(); + let validators_payout = ErasValidatorReward::::get(0).unwrap(); + let total_payout = treasury_payout + validators_payout; + + // max stakers payout (without max staked rewards cap applied) is larger than the final + // validator rewards. The final payment and remainder should be adjusted by redestributing + // the era inflation to apply the cap... + assert!(max_stakers_payout > validators_payout); + + // .. which means that the final validator payout is 10% of the total payout.. + assert_eq!(validators_payout, Percent::from_percent(max_staked_rewards) * total_payout); + // .. and the remainder 90% goes to the treasury. + assert_eq!( + treasury_payout, + Percent::from_percent(100 - max_staked_rewards) * (treasury_payout + validators_payout) + ); + }) +} + #[test] fn reward_to_stake_works() { ExtBuilder::default() @@ -5543,7 +5616,8 @@ fn chill_other_works() { ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Remove, - ConfigOp::Remove + ConfigOp::Remove, + ConfigOp::Noop, )); // Still can't chill these users @@ -5564,7 +5638,8 @@ fn chill_other_works() { ConfigOp::Set(10), ConfigOp::Set(10), ConfigOp::Noop, - ConfigOp::Noop + ConfigOp::Noop, + ConfigOp::Noop, )); // Still can't chill these users @@ -5585,7 +5660,8 @@ fn chill_other_works() { ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Noop, - ConfigOp::Noop + ConfigOp::Noop, + ConfigOp::Noop, )); // Still can't chill these users @@ -5606,7 +5682,8 @@ fn chill_other_works() { ConfigOp::Set(10), ConfigOp::Set(10), ConfigOp::Set(Percent::from_percent(75)), - ConfigOp::Noop + ConfigOp::Noop, + ConfigOp::Noop, )); // 16 people total because tests start with 2 active one @@ -5652,6 +5729,7 @@ fn capped_stakers_works() { ConfigOp::Set(max), ConfigOp::Remove, ConfigOp::Remove, + ConfigOp::Noop, )); // can create `max - validator_count` validators @@ -5722,6 +5800,7 @@ fn capped_stakers_works() { ConfigOp::Remove, ConfigOp::Noop, ConfigOp::Noop, + ConfigOp::Noop, )); assert_ok!(Staking::nominate(RuntimeOrigin::signed(last_nominator), vec![1])); assert_ok!(Staking::validate( @@ -5757,6 +5836,7 @@ fn min_commission_works() { ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Set(Perbill::from_percent(10)), + ConfigOp::Noop, )); // can't make it less than 10 now diff --git a/substrate/frame/staking/src/weights.rs b/substrate/frame/staking/src/weights.rs index 7c9a0500164..6f729e08ba5 100644 --- a/substrate/frame/staking/src/weights.rs +++ b/substrate/frame/staking/src/weights.rs @@ -18,9 +18,9 @@ //! Autogenerated weights for `pallet_staking` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-12-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-01-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-itmxxexx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-q7z7ruxr-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: @@ -99,8 +99,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `927` // Estimated: `4764` - // Minimum execution time: 42_491_000 picoseconds. - Weight::from_parts(44_026_000, 4764) + // Minimum execution time: 42_042_000 picoseconds. + Weight::from_parts(43_292_000, 4764) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -120,8 +120,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1990` // Estimated: `8877` - // Minimum execution time: 88_756_000 picoseconds. - Weight::from_parts(91_000_000, 8877) + // Minimum execution time: 85_050_000 picoseconds. + Weight::from_parts(87_567_000, 8877) .saturating_add(T::DbWeight::get().reads(9_u64)) .saturating_add(T::DbWeight::get().writes(7_u64)) } @@ -147,8 +147,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `2195` // Estimated: `8877` - // Minimum execution time: 91_331_000 picoseconds. - Weight::from_parts(94_781_000, 8877) + // Minimum execution time: 89_076_000 picoseconds. + Weight::from_parts(92_715_000, 8877) .saturating_add(T::DbWeight::get().reads(12_u64)) .saturating_add(T::DbWeight::get().writes(7_u64)) } @@ -167,10 +167,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1115` // Estimated: `4764` - // Minimum execution time: 42_495_000 picoseconds. - Weight::from_parts(44_189_470, 4764) - // Standard Error: 1_389 - .saturating_add(Weight::from_parts(47_484, 0).saturating_mul(s.into())) + // Minimum execution time: 42_067_000 picoseconds. + Weight::from_parts(43_239_807, 4764) + // Standard Error: 831 + .saturating_add(Weight::from_parts(46_257, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -207,10 +207,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `2196 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 89_004_000 picoseconds. - Weight::from_parts(96_677_570, 6248) - // Standard Error: 4_635 - .saturating_add(Weight::from_parts(1_387_718, 0).saturating_mul(s.into())) + // Minimum execution time: 86_490_000 picoseconds. + Weight::from_parts(95_358_751, 6248) + // Standard Error: 3_952 + .saturating_add(Weight::from_parts(1_294_907, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(13_u64)) .saturating_add(T::DbWeight::get().writes(11_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -242,8 +242,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1372` // Estimated: `4556` - // Minimum execution time: 51_532_000 picoseconds. - Weight::from_parts(53_308_000, 4556) + // Minimum execution time: 50_326_000 picoseconds. + Weight::from_parts(52_253_000, 4556) .saturating_add(T::DbWeight::get().reads(11_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -256,10 +256,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1280 + k * (569 ±0)` // Estimated: `4556 + k * (3033 ±0)` - // Minimum execution time: 28_955_000 picoseconds. - Weight::from_parts(29_609_869, 4556) - // Standard Error: 6_793 - .saturating_add(Weight::from_parts(6_412_124, 0).saturating_mul(k.into())) + // Minimum execution time: 29_305_000 picoseconds. + Weight::from_parts(32_199_604, 4556) + // Standard Error: 7_150 + .saturating_add(Weight::from_parts(6_437_124, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) @@ -292,10 +292,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1866 + n * (102 ±0)` // Estimated: `6248 + n * (2520 ±0)` - // Minimum execution time: 64_080_000 picoseconds. - Weight::from_parts(61_985_382, 6248) - // Standard Error: 13_320 - .saturating_add(Weight::from_parts(4_030_513, 0).saturating_mul(n.into())) + // Minimum execution time: 63_267_000 picoseconds. + Weight::from_parts(61_741_404, 6248) + // Standard Error: 12_955 + .saturating_add(Weight::from_parts(3_811_743, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(12_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(6_u64)) @@ -319,8 +319,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1650` // Estimated: `6248` - // Minimum execution time: 54_194_000 picoseconds. - Weight::from_parts(55_578_000, 6248) + // Minimum execution time: 52_862_000 picoseconds. + Weight::from_parts(54_108_000, 6248) .saturating_add(T::DbWeight::get().reads(8_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -334,8 +334,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `902` // Estimated: `4556` - // Minimum execution time: 16_597_000 picoseconds. - Weight::from_parts(16_980_000, 4556) + // Minimum execution time: 16_350_000 picoseconds. + Weight::from_parts(16_802_000, 4556) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -349,8 +349,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `969` // Estimated: `4556` - // Minimum execution time: 20_626_000 picoseconds. - Weight::from_parts(21_242_000, 4556) + // Minimum execution time: 19_981_000 picoseconds. + Weight::from_parts(20_539_000, 4556) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -362,8 +362,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `902` // Estimated: `4556` - // Minimum execution time: 19_972_000 picoseconds. - Weight::from_parts(20_470_000, 4556) + // Minimum execution time: 19_304_000 picoseconds. + Weight::from_parts(20_000_000, 4556) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -373,8 +373,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_571_000 picoseconds. - Weight::from_parts(2_720_000, 0) + // Minimum execution time: 2_568_000 picoseconds. + Weight::from_parts(2_708_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -383,8 +383,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_056_000 picoseconds. - Weight::from_parts(8_413_000, 0) + // Minimum execution time: 7_950_000 picoseconds. + Weight::from_parts(8_348_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -393,8 +393,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_162_000 picoseconds. - Weight::from_parts(8_497_000, 0) + // Minimum execution time: 7_967_000 picoseconds. + Weight::from_parts(8_222_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -403,8 +403,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_320_000 picoseconds. - Weight::from_parts(8_564_000, 0) + // Minimum execution time: 8_006_000 picoseconds. + Weight::from_parts(8_440_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::Invulnerables` (r:0 w:1) @@ -414,10 +414,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_470_000 picoseconds. - Weight::from_parts(3_110_242, 0) - // Standard Error: 63 - .saturating_add(Weight::from_parts(11_786, 0).saturating_mul(v.into())) + // Minimum execution time: 2_524_000 picoseconds. + Weight::from_parts(3_123_608, 0) + // Standard Error: 59 + .saturating_add(Weight::from_parts(11_596, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::Ledger` (r:5900 w:11800) @@ -431,10 +431,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1356 + i * (151 ±0)` // Estimated: `990 + i * (3566 ±0)` - // Minimum execution time: 2_101_000 picoseconds. - Weight::from_parts(2_238_000, 990) - // Standard Error: 56_753 - .saturating_add(Weight::from_parts(18_404_902, 0).saturating_mul(i.into())) + // Minimum execution time: 2_092_000 picoseconds. + Weight::from_parts(2_258_000, 990) + // Standard Error: 32_695 + .saturating_add(Weight::from_parts(16_669_219, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(i.into()))) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(i.into()))) .saturating_add(Weight::from_parts(0, 3566).saturating_mul(i.into())) @@ -472,10 +472,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `2196 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 86_765_000 picoseconds. - Weight::from_parts(95_173_565, 6248) - // Standard Error: 4_596 - .saturating_add(Weight::from_parts(1_354_849, 0).saturating_mul(s.into())) + // Minimum execution time: 84_275_000 picoseconds. + Weight::from_parts(92_512_416, 6248) + // Standard Error: 3_633 + .saturating_add(Weight::from_parts(1_315_923, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(13_u64)) .saturating_add(T::DbWeight::get().writes(12_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -488,10 +488,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `66672` // Estimated: `70137` - // Minimum execution time: 104_490_000 picoseconds. - Weight::from_parts(1_162_956_951, 70137) - // Standard Error: 76_760 - .saturating_add(Weight::from_parts(6_485_569, 0).saturating_mul(s.into())) + // Minimum execution time: 101_707_000 picoseconds. + Weight::from_parts(912_819_462, 70137) + // Standard Error: 57_547 + .saturating_add(Weight::from_parts(4_856_799, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -528,10 +528,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `33297 + n * (377 ±0)` // Estimated: `30944 + n * (3774 ±0)` - // Minimum execution time: 144_790_000 picoseconds. - Weight::from_parts(36_764_791, 30944) - // Standard Error: 89_592 - .saturating_add(Weight::from_parts(49_620_105, 0).saturating_mul(n.into())) + // Minimum execution time: 138_657_000 picoseconds. + Weight::from_parts(167_173_445, 30944) + // Standard Error: 25_130 + .saturating_add(Weight::from_parts(44_566_012, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(14_u64)) .saturating_add(T::DbWeight::get().reads((6_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(4_u64)) @@ -555,10 +555,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1991 + l * (7 ±0)` // Estimated: `8877` - // Minimum execution time: 81_768_000 picoseconds. - Weight::from_parts(85_332_982, 8877) - // Standard Error: 5_380 - .saturating_add(Weight::from_parts(70_298, 0).saturating_mul(l.into())) + // Minimum execution time: 80_061_000 picoseconds. + Weight::from_parts(82_836_434, 8877) + // Standard Error: 4_348 + .saturating_add(Weight::from_parts(75_744, 0).saturating_mul(l.into())) .saturating_add(T::DbWeight::get().reads(9_u64)) .saturating_add(T::DbWeight::get().writes(7_u64)) } @@ -593,10 +593,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `2196 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 96_123_000 picoseconds. - Weight::from_parts(100_278_672, 6248) - // Standard Error: 3_487 - .saturating_add(Weight::from_parts(1_326_503, 0).saturating_mul(s.into())) + // Minimum execution time: 92_560_000 picoseconds. + Weight::from_parts(97_684_741, 6248) + // Standard Error: 3_361 + .saturating_add(Weight::from_parts(1_292_732, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(12_u64)) .saturating_add(T::DbWeight::get().writes(11_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -642,12 +642,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0 + n * (720 ±0) + v * (3598 ±0)` // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 572_893_000 picoseconds. - Weight::from_parts(578_010_000, 512390) - // Standard Error: 2_094_268 - .saturating_add(Weight::from_parts(68_419_710, 0).saturating_mul(v.into())) - // Standard Error: 208_682 - .saturating_add(Weight::from_parts(18_826_175, 0).saturating_mul(n.into())) + // Minimum execution time: 564_963_000 picoseconds. + Weight::from_parts(569_206_000, 512390) + // Standard Error: 2_033_235 + .saturating_add(Weight::from_parts(68_025_841, 0).saturating_mul(v.into())) + // Standard Error: 202_600 + .saturating_add(Weight::from_parts(17_916_770, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(206_u64)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -678,12 +678,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `3175 + n * (911 ±0) + v * (395 ±0)` // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 33_836_205_000 picoseconds. - Weight::from_parts(34_210_443_000, 512390) - // Standard Error: 441_692 - .saturating_add(Weight::from_parts(6_122_533, 0).saturating_mul(v.into())) - // Standard Error: 441_692 - .saturating_add(Weight::from_parts(4_418_264, 0).saturating_mul(n.into())) + // Minimum execution time: 32_196_540_000 picoseconds. + Weight::from_parts(32_341_871_000, 512390) + // Standard Error: 354_657 + .saturating_add(Weight::from_parts(5_143_440, 0).saturating_mul(v.into())) + // Standard Error: 354_657 + .saturating_add(Weight::from_parts(3_328_189, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(201_u64)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -700,10 +700,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `979 + v * (50 ±0)` // Estimated: `3510 + v * (2520 ±0)` - // Minimum execution time: 2_454_689_000 picoseconds. - Weight::from_parts(161_771_064, 3510) - // Standard Error: 31_022 - .saturating_add(Weight::from_parts(4_820_158, 0).saturating_mul(v.into())) + // Minimum execution time: 2_381_903_000 picoseconds. + Weight::from_parts(32_693_059, 3510) + // Standard Error: 10_000 + .saturating_add(Weight::from_parts(4_736_173, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(v.into()))) .saturating_add(Weight::from_parts(0, 2520).saturating_mul(v.into())) @@ -724,8 +724,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_073_000 picoseconds. - Weight::from_parts(5_452_000, 0) + // Minimum execution time: 5_434_000 picoseconds. + Weight::from_parts(5_742_000, 0) .saturating_add(T::DbWeight::get().writes(6_u64)) } /// Storage: `Staking::MinCommission` (r:0 w:1) @@ -744,8 +744,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_465_000 picoseconds. - Weight::from_parts(4_832_000, 0) + // Minimum execution time: 4_588_000 picoseconds. + Weight::from_parts(4_854_000, 0) .saturating_add(T::DbWeight::get().writes(6_u64)) } /// Storage: `Staking::Bonded` (r:1 w:0) @@ -774,8 +774,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1939` // Estimated: `6248` - // Minimum execution time: 71_239_000 picoseconds. - Weight::from_parts(74_649_000, 6248) + // Minimum execution time: 68_780_000 picoseconds. + Weight::from_parts(71_479_000, 6248) .saturating_add(T::DbWeight::get().reads(12_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -787,8 +787,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `691` // Estimated: `3510` - // Minimum execution time: 12_525_000 picoseconds. - Weight::from_parts(13_126_000, 3510) + // Minimum execution time: 12_268_000 picoseconds. + Weight::from_parts(12_661_000, 3510) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -798,8 +798,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_918_000 picoseconds. - Weight::from_parts(3_176_000, 0) + // Minimum execution time: 3_071_000 picoseconds. + Weight::from_parts(3_334_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } } @@ -820,8 +820,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `927` // Estimated: `4764` - // Minimum execution time: 42_491_000 picoseconds. - Weight::from_parts(44_026_000, 4764) + // Minimum execution time: 42_042_000 picoseconds. + Weight::from_parts(43_292_000, 4764) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -841,8 +841,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1990` // Estimated: `8877` - // Minimum execution time: 88_756_000 picoseconds. - Weight::from_parts(91_000_000, 8877) + // Minimum execution time: 85_050_000 picoseconds. + Weight::from_parts(87_567_000, 8877) .saturating_add(RocksDbWeight::get().reads(9_u64)) .saturating_add(RocksDbWeight::get().writes(7_u64)) } @@ -868,8 +868,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `2195` // Estimated: `8877` - // Minimum execution time: 91_331_000 picoseconds. - Weight::from_parts(94_781_000, 8877) + // Minimum execution time: 89_076_000 picoseconds. + Weight::from_parts(92_715_000, 8877) .saturating_add(RocksDbWeight::get().reads(12_u64)) .saturating_add(RocksDbWeight::get().writes(7_u64)) } @@ -888,10 +888,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1115` // Estimated: `4764` - // Minimum execution time: 42_495_000 picoseconds. - Weight::from_parts(44_189_470, 4764) - // Standard Error: 1_389 - .saturating_add(Weight::from_parts(47_484, 0).saturating_mul(s.into())) + // Minimum execution time: 42_067_000 picoseconds. + Weight::from_parts(43_239_807, 4764) + // Standard Error: 831 + .saturating_add(Weight::from_parts(46_257, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -928,10 +928,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `2196 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 89_004_000 picoseconds. - Weight::from_parts(96_677_570, 6248) - // Standard Error: 4_635 - .saturating_add(Weight::from_parts(1_387_718, 0).saturating_mul(s.into())) + // Minimum execution time: 86_490_000 picoseconds. + Weight::from_parts(95_358_751, 6248) + // Standard Error: 3_952 + .saturating_add(Weight::from_parts(1_294_907, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(13_u64)) .saturating_add(RocksDbWeight::get().writes(11_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -963,8 +963,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1372` // Estimated: `4556` - // Minimum execution time: 51_532_000 picoseconds. - Weight::from_parts(53_308_000, 4556) + // Minimum execution time: 50_326_000 picoseconds. + Weight::from_parts(52_253_000, 4556) .saturating_add(RocksDbWeight::get().reads(11_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -977,10 +977,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1280 + k * (569 ±0)` // Estimated: `4556 + k * (3033 ±0)` - // Minimum execution time: 28_955_000 picoseconds. - Weight::from_parts(29_609_869, 4556) - // Standard Error: 6_793 - .saturating_add(Weight::from_parts(6_412_124, 0).saturating_mul(k.into())) + // Minimum execution time: 29_305_000 picoseconds. + Weight::from_parts(32_199_604, 4556) + // Standard Error: 7_150 + .saturating_add(Weight::from_parts(6_437_124, 0).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(k.into()))) @@ -1013,10 +1013,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1866 + n * (102 ±0)` // Estimated: `6248 + n * (2520 ±0)` - // Minimum execution time: 64_080_000 picoseconds. - Weight::from_parts(61_985_382, 6248) - // Standard Error: 13_320 - .saturating_add(Weight::from_parts(4_030_513, 0).saturating_mul(n.into())) + // Minimum execution time: 63_267_000 picoseconds. + Weight::from_parts(61_741_404, 6248) + // Standard Error: 12_955 + .saturating_add(Weight::from_parts(3_811_743, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(12_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(6_u64)) @@ -1040,8 +1040,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1650` // Estimated: `6248` - // Minimum execution time: 54_194_000 picoseconds. - Weight::from_parts(55_578_000, 6248) + // Minimum execution time: 52_862_000 picoseconds. + Weight::from_parts(54_108_000, 6248) .saturating_add(RocksDbWeight::get().reads(8_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -1055,8 +1055,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `902` // Estimated: `4556` - // Minimum execution time: 16_597_000 picoseconds. - Weight::from_parts(16_980_000, 4556) + // Minimum execution time: 16_350_000 picoseconds. + Weight::from_parts(16_802_000, 4556) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1070,8 +1070,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `969` // Estimated: `4556` - // Minimum execution time: 20_626_000 picoseconds. - Weight::from_parts(21_242_000, 4556) + // Minimum execution time: 19_981_000 picoseconds. + Weight::from_parts(20_539_000, 4556) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1083,8 +1083,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `902` // Estimated: `4556` - // Minimum execution time: 19_972_000 picoseconds. - Weight::from_parts(20_470_000, 4556) + // Minimum execution time: 19_304_000 picoseconds. + Weight::from_parts(20_000_000, 4556) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1094,8 +1094,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_571_000 picoseconds. - Weight::from_parts(2_720_000, 0) + // Minimum execution time: 2_568_000 picoseconds. + Weight::from_parts(2_708_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -1104,8 +1104,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_056_000 picoseconds. - Weight::from_parts(8_413_000, 0) + // Minimum execution time: 7_950_000 picoseconds. + Weight::from_parts(8_348_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -1114,8 +1114,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_162_000 picoseconds. - Weight::from_parts(8_497_000, 0) + // Minimum execution time: 7_967_000 picoseconds. + Weight::from_parts(8_222_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -1124,8 +1124,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_320_000 picoseconds. - Weight::from_parts(8_564_000, 0) + // Minimum execution time: 8_006_000 picoseconds. + Weight::from_parts(8_440_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::Invulnerables` (r:0 w:1) @@ -1135,10 +1135,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_470_000 picoseconds. - Weight::from_parts(3_110_242, 0) - // Standard Error: 63 - .saturating_add(Weight::from_parts(11_786, 0).saturating_mul(v.into())) + // Minimum execution time: 2_524_000 picoseconds. + Weight::from_parts(3_123_608, 0) + // Standard Error: 59 + .saturating_add(Weight::from_parts(11_596, 0).saturating_mul(v.into())) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::Ledger` (r:5900 w:11800) @@ -1152,10 +1152,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1356 + i * (151 ±0)` // Estimated: `990 + i * (3566 ±0)` - // Minimum execution time: 2_101_000 picoseconds. - Weight::from_parts(2_238_000, 990) - // Standard Error: 56_753 - .saturating_add(Weight::from_parts(18_404_902, 0).saturating_mul(i.into())) + // Minimum execution time: 2_092_000 picoseconds. + Weight::from_parts(2_258_000, 990) + // Standard Error: 32_695 + .saturating_add(Weight::from_parts(16_669_219, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(i.into()))) .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(i.into()))) .saturating_add(Weight::from_parts(0, 3566).saturating_mul(i.into())) @@ -1193,10 +1193,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `2196 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 86_765_000 picoseconds. - Weight::from_parts(95_173_565, 6248) - // Standard Error: 4_596 - .saturating_add(Weight::from_parts(1_354_849, 0).saturating_mul(s.into())) + // Minimum execution time: 84_275_000 picoseconds. + Weight::from_parts(92_512_416, 6248) + // Standard Error: 3_633 + .saturating_add(Weight::from_parts(1_315_923, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(13_u64)) .saturating_add(RocksDbWeight::get().writes(12_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -1209,10 +1209,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `66672` // Estimated: `70137` - // Minimum execution time: 104_490_000 picoseconds. - Weight::from_parts(1_162_956_951, 70137) - // Standard Error: 76_760 - .saturating_add(Weight::from_parts(6_485_569, 0).saturating_mul(s.into())) + // Minimum execution time: 101_707_000 picoseconds. + Weight::from_parts(912_819_462, 70137) + // Standard Error: 57_547 + .saturating_add(Weight::from_parts(4_856_799, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1249,10 +1249,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `33297 + n * (377 ±0)` // Estimated: `30944 + n * (3774 ±0)` - // Minimum execution time: 144_790_000 picoseconds. - Weight::from_parts(36_764_791, 30944) - // Standard Error: 89_592 - .saturating_add(Weight::from_parts(49_620_105, 0).saturating_mul(n.into())) + // Minimum execution time: 138_657_000 picoseconds. + Weight::from_parts(167_173_445, 30944) + // Standard Error: 25_130 + .saturating_add(Weight::from_parts(44_566_012, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(14_u64)) .saturating_add(RocksDbWeight::get().reads((6_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(4_u64)) @@ -1276,10 +1276,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1991 + l * (7 ±0)` // Estimated: `8877` - // Minimum execution time: 81_768_000 picoseconds. - Weight::from_parts(85_332_982, 8877) - // Standard Error: 5_380 - .saturating_add(Weight::from_parts(70_298, 0).saturating_mul(l.into())) + // Minimum execution time: 80_061_000 picoseconds. + Weight::from_parts(82_836_434, 8877) + // Standard Error: 4_348 + .saturating_add(Weight::from_parts(75_744, 0).saturating_mul(l.into())) .saturating_add(RocksDbWeight::get().reads(9_u64)) .saturating_add(RocksDbWeight::get().writes(7_u64)) } @@ -1314,10 +1314,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `2196 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 96_123_000 picoseconds. - Weight::from_parts(100_278_672, 6248) - // Standard Error: 3_487 - .saturating_add(Weight::from_parts(1_326_503, 0).saturating_mul(s.into())) + // Minimum execution time: 92_560_000 picoseconds. + Weight::from_parts(97_684_741, 6248) + // Standard Error: 3_361 + .saturating_add(Weight::from_parts(1_292_732, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(12_u64)) .saturating_add(RocksDbWeight::get().writes(11_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -1363,12 +1363,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0 + n * (720 ±0) + v * (3598 ±0)` // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 572_893_000 picoseconds. - Weight::from_parts(578_010_000, 512390) - // Standard Error: 2_094_268 - .saturating_add(Weight::from_parts(68_419_710, 0).saturating_mul(v.into())) - // Standard Error: 208_682 - .saturating_add(Weight::from_parts(18_826_175, 0).saturating_mul(n.into())) + // Minimum execution time: 564_963_000 picoseconds. + Weight::from_parts(569_206_000, 512390) + // Standard Error: 2_033_235 + .saturating_add(Weight::from_parts(68_025_841, 0).saturating_mul(v.into())) + // Standard Error: 202_600 + .saturating_add(Weight::from_parts(17_916_770, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(206_u64)) .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -1399,12 +1399,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `3175 + n * (911 ±0) + v * (395 ±0)` // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 33_836_205_000 picoseconds. - Weight::from_parts(34_210_443_000, 512390) - // Standard Error: 441_692 - .saturating_add(Weight::from_parts(6_122_533, 0).saturating_mul(v.into())) - // Standard Error: 441_692 - .saturating_add(Weight::from_parts(4_418_264, 0).saturating_mul(n.into())) + // Minimum execution time: 32_196_540_000 picoseconds. + Weight::from_parts(32_341_871_000, 512390) + // Standard Error: 354_657 + .saturating_add(Weight::from_parts(5_143_440, 0).saturating_mul(v.into())) + // Standard Error: 354_657 + .saturating_add(Weight::from_parts(3_328_189, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(201_u64)) .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -1421,10 +1421,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `979 + v * (50 ±0)` // Estimated: `3510 + v * (2520 ±0)` - // Minimum execution time: 2_454_689_000 picoseconds. - Weight::from_parts(161_771_064, 3510) - // Standard Error: 31_022 - .saturating_add(Weight::from_parts(4_820_158, 0).saturating_mul(v.into())) + // Minimum execution time: 2_381_903_000 picoseconds. + Weight::from_parts(32_693_059, 3510) + // Standard Error: 10_000 + .saturating_add(Weight::from_parts(4_736_173, 0).saturating_mul(v.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(v.into()))) .saturating_add(Weight::from_parts(0, 2520).saturating_mul(v.into())) @@ -1445,8 +1445,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_073_000 picoseconds. - Weight::from_parts(5_452_000, 0) + // Minimum execution time: 5_434_000 picoseconds. + Weight::from_parts(5_742_000, 0) .saturating_add(RocksDbWeight::get().writes(6_u64)) } /// Storage: `Staking::MinCommission` (r:0 w:1) @@ -1465,8 +1465,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_465_000 picoseconds. - Weight::from_parts(4_832_000, 0) + // Minimum execution time: 4_588_000 picoseconds. + Weight::from_parts(4_854_000, 0) .saturating_add(RocksDbWeight::get().writes(6_u64)) } /// Storage: `Staking::Bonded` (r:1 w:0) @@ -1495,8 +1495,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1939` // Estimated: `6248` - // Minimum execution time: 71_239_000 picoseconds. - Weight::from_parts(74_649_000, 6248) + // Minimum execution time: 68_780_000 picoseconds. + Weight::from_parts(71_479_000, 6248) .saturating_add(RocksDbWeight::get().reads(12_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -1508,8 +1508,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `691` // Estimated: `3510` - // Minimum execution time: 12_525_000 picoseconds. - Weight::from_parts(13_126_000, 3510) + // Minimum execution time: 12_268_000 picoseconds. + Weight::from_parts(12_661_000, 3510) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1519,8 +1519,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_918_000 picoseconds. - Weight::from_parts(3_176_000, 0) + // Minimum execution time: 3_071_000 picoseconds. + Weight::from_parts(3_334_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } } -- GitLab From ad68a05079b8da1be8a801cc0db91a401863ae56 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Fri, 16 Feb 2024 10:49:15 +0100 Subject: [PATCH 068/133] [ci] allow fail build-runtimes-polkavm job (#3354) PR allows `build-runtimes-polkavm` to fail. Also added DAG to run the job immediately. cc https://github.com/paritytech/ci_cd/issues/945 cc https://github.com/paritytech/polkadot-sdk/issues/3352 --- .gitlab/pipeline/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab/pipeline/build.yml b/.gitlab/pipeline/build.yml index 53c47e5ff5a..c6682f7974b 100644 --- a/.gitlab/pipeline/build.yml +++ b/.gitlab/pipeline/build.yml @@ -335,11 +335,13 @@ build-runtimes-polkavm: extends: - .docker-env - .common-refs + - .run-immediately script: - SUBSTRATE_RUNTIME_TARGET=riscv cargo check -p minimal-runtime - SUBSTRATE_RUNTIME_TARGET=riscv cargo check -p westend-runtime - SUBSTRATE_RUNTIME_TARGET=riscv cargo check -p rococo-runtime - SUBSTRATE_RUNTIME_TARGET=riscv cargo check -p polkadot-test-runtime + allow_failure: true .build-subkey: stage: build -- GitLab From 9346019dad763a957c640602d2e61eac29ddce93 Mon Sep 17 00:00:00 2001 From: georgepisaltu <52418509+georgepisaltu@users.noreply.github.com> Date: Fri, 16 Feb 2024 12:59:10 +0200 Subject: [PATCH 069/133] Add retry mechanics to `pallet-scheduler` (#3060) Fixes #3014 This PR adds retry mechanics to `pallet-scheduler`, as described in the issue above. Users can now set a retry configuration for a task so that, in case its scheduled run fails, it will be retried after a number of blocks, for a specified number of times or until it succeeds. If a retried task runs successfully before running out of retries, its remaining retry counter will be reset to the initial value. If a retried task runs out of retries, it will be removed from the schedule. Tasks which need to be scheduled for a retry are still subject to weight metering and agenda space, same as a regular task. Periodic tasks will have their periodic schedule put on hold while the task is retrying. --------- Signed-off-by: georgepisaltu Co-authored-by: command-bot <> --- .../src/weights/pallet_scheduler.rs | 214 +++- .../rococo/src/weights/pallet_scheduler.rs | 236 ++-- .../westend/src/weights/pallet_scheduler.rs | 225 ++-- prdoc/pr_3060.prdoc | 15 + substrate/frame/scheduler/src/benchmarking.rs | 111 +- substrate/frame/scheduler/src/lib.rs | 267 +++- substrate/frame/scheduler/src/mock.rs | 40 + substrate/frame/scheduler/src/tests.rs | 1103 ++++++++++++++++- substrate/frame/scheduler/src/weights.rs | 458 ++++--- 9 files changed, 2292 insertions(+), 377 deletions(-) create mode 100644 prdoc/pr_3060.prdoc diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_scheduler.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_scheduler.rs index cf5610df665..42e37b967e4 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_scheduler.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_scheduler.rs @@ -1,42 +1,41 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 +// This file is part of Cumulus. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . //! Autogenerated weights for `pallet_scheduler` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-01-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-polkadot-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-grjcggob-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet -// --chain=collectives-polkadot-dev -// --wasm-execution=compiled -// --pallet=pallet_scheduler -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/collectives/collectives-polkadot/src/weights/ +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_scheduler +// --chain=collectives-westend-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,8 +54,8 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `31` // Estimated: `1489` - // Minimum execution time: 3_441_000 picoseconds. - Weight::from_parts(3_604_000, 0) + // Minimum execution time: 2_475_000 picoseconds. + Weight::from_parts(2_644_000, 0) .saturating_add(Weight::from_parts(0, 1489)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -68,11 +67,11 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `77 + s * (177 ±0)` // Estimated: `159279` - // Minimum execution time: 2_879_000 picoseconds. - Weight::from_parts(2_963_000, 0) + // Minimum execution time: 2_898_000 picoseconds. + Weight::from_parts(1_532_342, 0) .saturating_add(Weight::from_parts(0, 159279)) - // Standard Error: 3_764 - .saturating_add(Weight::from_parts(909_557, 0).saturating_mul(s.into())) + // Standard Error: 4_736 + .saturating_add(Weight::from_parts(412_374, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -80,25 +79,27 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_172_000 picoseconds. - Weight::from_parts(5_294_000, 0) + // Minimum execution time: 3_171_000 picoseconds. + Weight::from_parts(3_349_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `Preimage::PreimageFor` (r:1 w:1) /// Proof: `Preimage::PreimageFor` (`max_values`: None, `max_size`: Some(4194344), added: 4196819, mode: `Measured`) - /// Storage: `Preimage::StatusFor` (r:1 w:1) + /// Storage: `Preimage::StatusFor` (r:1 w:0) /// Proof: `Preimage::StatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) + /// Storage: `Preimage::RequestStatusFor` (r:1 w:1) + /// Proof: `Preimage::RequestStatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) /// The range of component `s` is `[128, 4194304]`. fn service_task_fetched(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `213 + s * (1 ±0)` - // Estimated: `3678 + s * (1 ±0)` - // Minimum execution time: 19_704_000 picoseconds. - Weight::from_parts(19_903_000, 0) - .saturating_add(Weight::from_parts(0, 3678)) - // Standard Error: 5 - .saturating_add(Weight::from_parts(1_394, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(2)) + // Measured: `246 + s * (1 ±0)` + // Estimated: `3711 + s * (1 ±0)` + // Minimum execution time: 17_329_000 picoseconds. + Weight::from_parts(17_604_000, 0) + .saturating_add(Weight::from_parts(0, 3711)) + // Standard Error: 1 + .saturating_add(Weight::from_parts(1_256, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(s.into())) } @@ -108,8 +109,8 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_359_000 picoseconds. - Weight::from_parts(6_599_000, 0) + // Minimum execution time: 4_503_000 picoseconds. + Weight::from_parts(4_677_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -117,24 +118,24 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_217_000 picoseconds. - Weight::from_parts(5_333_000, 0) + // Minimum execution time: 3_145_000 picoseconds. + Weight::from_parts(3_252_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn execute_dispatch_signed() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_406_000 picoseconds. - Weight::from_parts(2_541_000, 0) + // Minimum execution time: 1_804_000 picoseconds. + Weight::from_parts(1_891_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn execute_dispatch_unsigned() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_370_000 picoseconds. - Weight::from_parts(2_561_000, 0) + // Minimum execution time: 1_706_000 picoseconds. + Weight::from_parts(1_776_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `Scheduler::Agenda` (r:1 w:1) @@ -144,11 +145,11 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `77 + s * (177 ±0)` // Estimated: `159279` - // Minimum execution time: 11_784_000 picoseconds. - Weight::from_parts(5_574_404, 0) + // Minimum execution time: 8_629_000 picoseconds. + Weight::from_parts(6_707_232, 0) .saturating_add(Weight::from_parts(0, 159279)) - // Standard Error: 7_217 - .saturating_add(Weight::from_parts(1_035_248, 0).saturating_mul(s.into())) + // Standard Error: 5_580 + .saturating_add(Weight::from_parts(471_827, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -161,11 +162,11 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `77 + s * (177 ±0)` // Estimated: `159279` - // Minimum execution time: 16_373_000 picoseconds. - Weight::from_parts(3_088_135, 0) + // Minimum execution time: 12_675_000 picoseconds. + Weight::from_parts(7_791_682, 0) .saturating_add(Weight::from_parts(0, 159279)) - // Standard Error: 7_095 - .saturating_add(Weight::from_parts(1_745_270, 0).saturating_mul(s.into())) + // Standard Error: 5_381 + .saturating_add(Weight::from_parts(653_023, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -178,11 +179,11 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `468 + s * (179 ±0)` // Estimated: `159279` - // Minimum execution time: 14_822_000 picoseconds. - Weight::from_parts(9_591_402, 0) + // Minimum execution time: 11_908_000 picoseconds. + Weight::from_parts(11_833_059, 0) .saturating_add(Weight::from_parts(0, 159279)) - // Standard Error: 7_151 - .saturating_add(Weight::from_parts(1_058_408, 0).saturating_mul(s.into())) + // Standard Error: 5_662 + .saturating_add(Weight::from_parts(482_816, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -195,12 +196,91 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `509 + s * (179 ±0)` // Estimated: `159279` - // Minimum execution time: 18_541_000 picoseconds. - Weight::from_parts(6_522_239, 0) + // Minimum execution time: 15_506_000 picoseconds. + Weight::from_parts(11_372_975, 0) .saturating_add(Weight::from_parts(0, 159279)) - // Standard Error: 8_349 - .saturating_add(Weight::from_parts(1_760_431, 0).saturating_mul(s.into())) + // Standard Error: 5_765 + .saturating_add(Weight::from_parts(656_322, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } + /// Storage: `Scheduler::Retries` (r:1 w:2) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(155814), added: 158289, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 200]`. + fn schedule_retry(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `159` + // Estimated: `159279` + // Minimum execution time: 14_069_000 picoseconds. + Weight::from_parts(14_868_345, 0) + .saturating_add(Weight::from_parts(0, 159279)) + // Standard Error: 425 + .saturating_add(Weight::from_parts(33_468, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(155814), added: 158289, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn set_retry() -> Weight { + // Proof Size summary in bytes: + // Measured: `77 + s * (177 ±0)` + // Estimated: `159279` + // Minimum execution time: 7_550_000 picoseconds. + Weight::from_parts(6_735_955, 0) + .saturating_add(Weight::from_parts(0, 159279)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:0) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(155814), added: 158289, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn set_retry_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `513 + s * (179 ±0)` + // Estimated: `159279` + // Minimum execution time: 11_017_000 picoseconds. + Weight::from_parts(11_749_385, 0) + .saturating_add(Weight::from_parts(0, 159279)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(155814), added: 158289, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn cancel_retry() -> Weight { + // Proof Size summary in bytes: + // Measured: `77 + s * (177 ±0)` + // Estimated: `159279` + // Minimum execution time: 7_550_000 picoseconds. + Weight::from_parts(6_735_955, 0) + .saturating_add(Weight::from_parts(0, 159279)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:0) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(155814), added: 158289, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn cancel_retry_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `513 + s * (179 ±0)` + // Estimated: `159279` + // Minimum execution time: 11_017_000 picoseconds. + Weight::from_parts(11_749_385, 0) + .saturating_add(Weight::from_parts(0, 159279)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } } diff --git a/polkadot/runtime/rococo/src/weights/pallet_scheduler.rs b/polkadot/runtime/rococo/src/weights/pallet_scheduler.rs index e4732a2d17d..0f36dbd384d 100644 --- a/polkadot/runtime/rococo/src/weights/pallet_scheduler.rs +++ b/polkadot/runtime/rococo/src/weights/pallet_scheduler.rs @@ -17,24 +17,25 @@ //! Autogenerated weights for `pallet_scheduler` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-26, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-01-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm5`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("rococo-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-grjcggob-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot +// target/production/polkadot // benchmark // pallet -// --chain=rococo-dev // --steps=50 // --repeat=20 -// --pallet=pallet_scheduler // --extrinsic=* -// --execution=wasm // --wasm-execution=compiled -// --header=./file_header.txt -// --output=./runtime/rococo/src/weights/ +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_scheduler +// --chain=rococo-dev +// --header=./polkadot/file_header.txt +// --output=./polkadot/runtime/rococo/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -47,30 +48,30 @@ use core::marker::PhantomData; /// Weight functions for `pallet_scheduler`. pub struct WeightInfo(PhantomData); impl pallet_scheduler::WeightInfo for WeightInfo { - /// Storage: Scheduler IncompleteSince (r:1 w:1) - /// Proof: Scheduler IncompleteSince (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: `Scheduler::IncompleteSince` (r:1 w:1) + /// Proof: `Scheduler::IncompleteSince` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn service_agendas_base() -> Weight { // Proof Size summary in bytes: - // Measured: `69` + // Measured: `68` // Estimated: `1489` - // Minimum execution time: 4_741_000 picoseconds. - Weight::from_parts(4_939_000, 0) + // Minimum execution time: 2_869_000 picoseconds. + Weight::from_parts(3_109_000, 0) .saturating_add(Weight::from_parts(0, 1489)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 50]`. fn service_agenda_base(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `116 + s * (177 ±0)` + // Measured: `115 + s * (177 ±0)` // Estimated: `42428` - // Minimum execution time: 4_504_000 picoseconds. - Weight::from_parts(7_569_333, 0) + // Minimum execution time: 3_326_000 picoseconds. + Weight::from_parts(5_818_563, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 1_818 - .saturating_add(Weight::from_parts(771_180, 0).saturating_mul(s.into())) + // Standard Error: 1_261 + .saturating_add(Weight::from_parts(336_446, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -78,36 +79,38 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_709_000 picoseconds. - Weight::from_parts(5_929_000, 0) + // Minimum execution time: 3_007_000 picoseconds. + Weight::from_parts(3_197_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: Preimage PreimageFor (r:1 w:1) - /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: Measured) - /// Storage: Preimage StatusFor (r:1 w:1) - /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: `Preimage::PreimageFor` (r:1 w:1) + /// Proof: `Preimage::PreimageFor` (`max_values`: None, `max_size`: Some(4194344), added: 4196819, mode: `Measured`) + /// Storage: `Preimage::StatusFor` (r:1 w:0) + /// Proof: `Preimage::StatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) + /// Storage: `Preimage::RequestStatusFor` (r:1 w:1) + /// Proof: `Preimage::RequestStatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) /// The range of component `s` is `[128, 4194304]`. fn service_task_fetched(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `251 + s * (1 ±0)` // Estimated: `3716 + s * (1 ±0)` - // Minimum execution time: 20_710_000 picoseconds. - Weight::from_parts(20_918_000, 0) + // Minimum execution time: 16_590_000 picoseconds. + Weight::from_parts(16_869_000, 0) .saturating_add(Weight::from_parts(0, 3716)) // Standard Error: 9 - .saturating_add(Weight::from_parts(1_257, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(Weight::from_parts(1_308, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(s.into())) } - /// Storage: Scheduler Lookup (r:0 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn service_task_named() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_262_000 picoseconds. - Weight::from_parts(7_412_000, 0) + // Minimum execution time: 4_320_000 picoseconds. + Weight::from_parts(4_594_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -115,90 +118,173 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_774_000 picoseconds. - Weight::from_parts(5_887_000, 0) + // Minimum execution time: 2_956_000 picoseconds. + Weight::from_parts(3_216_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn execute_dispatch_signed() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_777_000 picoseconds. - Weight::from_parts(2_865_000, 0) + // Minimum execution time: 1_824_000 picoseconds. + Weight::from_parts(1_929_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn execute_dispatch_unsigned() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_739_000 picoseconds. - Weight::from_parts(2_827_000, 0) + // Minimum execution time: 1_749_000 picoseconds. + Weight::from_parts(1_916_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 49]`. fn schedule(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `116 + s * (177 ±0)` + // Measured: `115 + s * (177 ±0)` // Estimated: `42428` - // Minimum execution time: 14_788_000 picoseconds. - Weight::from_parts(17_705_748, 0) + // Minimum execution time: 9_086_000 picoseconds. + Weight::from_parts(11_733_696, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 1_703 - .saturating_add(Weight::from_parts(760_991, 0).saturating_mul(s.into())) + // Standard Error: 1_362 + .saturating_add(Weight::from_parts(375_266, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - /// Storage: Scheduler Lookup (r:0 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// The range of component `s` is `[1, 50]`. fn cancel(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `116 + s * (177 ±0)` + // Measured: `115 + s * (177 ±0)` // Estimated: `42428` - // Minimum execution time: 18_716_000 picoseconds. - Weight::from_parts(18_220_022, 0) + // Minimum execution time: 12_716_000 picoseconds. + Weight::from_parts(12_529_180, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 1_508 - .saturating_add(Weight::from_parts(1_357_835, 0).saturating_mul(s.into())) + // Standard Error: 867 + .saturating_add(Weight::from_parts(548_188, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Scheduler Lookup (r:1 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:1 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 49]`. fn schedule_named(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `293 + s * (185 ±0)` + // Measured: `292 + s * (185 ±0)` // Estimated: `42428` - // Minimum execution time: 17_719_000 picoseconds. - Weight::from_parts(21_657_806, 0) + // Minimum execution time: 12_053_000 picoseconds. + Weight::from_parts(15_358_056, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 2_645 - .saturating_add(Weight::from_parts(794_184, 0).saturating_mul(s.into())) + // Standard Error: 3_176 + .saturating_add(Weight::from_parts(421_589, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Scheduler Lookup (r:1 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:1 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) /// The range of component `s` is `[1, 50]`. fn cancel_named(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `319 + s * (185 ±0)` + // Measured: `318 + s * (185 ±0)` // Estimated: `42428` - // Minimum execution time: 20_225_000 picoseconds. - Weight::from_parts(20_494_405, 0) + // Minimum execution time: 14_803_000 picoseconds. + Weight::from_parts(15_805_714, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 1_890 - .saturating_add(Weight::from_parts(1_379_025, 0).saturating_mul(s.into())) + // Standard Error: 2_597 + .saturating_add(Weight::from_parts(611_053, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } + /// Storage: `Scheduler::Retries` (r:1 w:2) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 50]`. + fn schedule_retry(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `196` + // Estimated: `42428` + // Minimum execution time: 13_156_000 picoseconds. + Weight::from_parts(13_801_287, 0) + .saturating_add(Weight::from_parts(0, 42428)) + // Standard Error: 568 + .saturating_add(Weight::from_parts(35_441, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 50]`. + fn set_retry() -> Weight { + // Proof Size summary in bytes: + // Measured: `115 + s * (177 ±0)` + // Estimated: `42428` + // Minimum execution time: 7_912_000 picoseconds. + Weight::from_parts(8_081_460, 0) + .saturating_add(Weight::from_parts(0, 42428)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:0) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 50]`. + fn set_retry_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `324 + s * (185 ±0)` + // Estimated: `42428` + // Minimum execution time: 10_673_000 picoseconds. + Weight::from_parts(12_212_185, 0) + .saturating_add(Weight::from_parts(0, 42428)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 50]`. + fn cancel_retry() -> Weight { + // Proof Size summary in bytes: + // Measured: `115 + s * (177 ±0)` + // Estimated: `42428` + // Minimum execution time: 7_912_000 picoseconds. + Weight::from_parts(8_081_460, 0) + .saturating_add(Weight::from_parts(0, 42428)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:0) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 50]`. + fn cancel_retry_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `324 + s * (185 ±0)` + // Estimated: `42428` + // Minimum execution time: 10_673_000 picoseconds. + Weight::from_parts(12_212_185, 0) + .saturating_add(Weight::from_parts(0, 42428)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } } diff --git a/polkadot/runtime/westend/src/weights/pallet_scheduler.rs b/polkadot/runtime/westend/src/weights/pallet_scheduler.rs index 7291b980933..beef3796dea 100644 --- a/polkadot/runtime/westend/src/weights/pallet_scheduler.rs +++ b/polkadot/runtime/westend/src/weights/pallet_scheduler.rs @@ -17,27 +17,25 @@ //! Autogenerated weights for `pallet_scheduler` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-06-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-01-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner--ss9ysm1-project-163-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-grjcggob-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot +// target/production/polkadot // benchmark // pallet -// --chain=westend-dev // --steps=50 // --repeat=20 -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --pallet=pallet_scheduler // --extrinsic=* -// --execution=wasm // --wasm-execution=compiled -// --header=./file_header.txt -// --output=./runtime/westend/src/weights/ +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_scheduler +// --chain=westend-dev +// --header=./polkadot/file_header.txt +// --output=./polkadot/runtime/westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -50,30 +48,30 @@ use core::marker::PhantomData; /// Weight functions for `pallet_scheduler`. pub struct WeightInfo(PhantomData); impl pallet_scheduler::WeightInfo for WeightInfo { - /// Storage: Scheduler IncompleteSince (r:1 w:1) - /// Proof: Scheduler IncompleteSince (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: `Scheduler::IncompleteSince` (r:1 w:1) + /// Proof: `Scheduler::IncompleteSince` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn service_agendas_base() -> Weight { // Proof Size summary in bytes: // Measured: `69` // Estimated: `1489` - // Minimum execution time: 3_991_000 picoseconds. - Weight::from_parts(4_160_000, 0) + // Minimum execution time: 3_220_000 picoseconds. + Weight::from_parts(3_512_000, 0) .saturating_add(Weight::from_parts(0, 1489)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 50]`. fn service_agenda_base(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `116 + s * (177 ±0)` // Estimated: `42428` - // Minimum execution time: 3_647_000 picoseconds. - Weight::from_parts(6_608_270, 0) + // Minimum execution time: 3_565_000 picoseconds. + Weight::from_parts(6_102_216, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 2_516 - .saturating_add(Weight::from_parts(892_866, 0).saturating_mul(s.into())) + // Standard Error: 1_413 + .saturating_add(Weight::from_parts(339_016, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -81,36 +79,38 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_552_000 picoseconds. - Weight::from_parts(5_836_000, 0) + // Minimum execution time: 2_940_000 picoseconds. + Weight::from_parts(3_070_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: Preimage PreimageFor (r:1 w:1) - /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: Measured) - /// Storage: Preimage StatusFor (r:1 w:1) - /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: `Preimage::PreimageFor` (r:1 w:1) + /// Proof: `Preimage::PreimageFor` (`max_values`: None, `max_size`: Some(4194344), added: 4196819, mode: `Measured`) + /// Storage: `Preimage::StatusFor` (r:1 w:0) + /// Proof: `Preimage::StatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) + /// Storage: `Preimage::RequestStatusFor` (r:1 w:1) + /// Proof: `Preimage::RequestStatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) /// The range of component `s` is `[128, 4194304]`. fn service_task_fetched(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `217 + s * (1 ±0)` // Estimated: `3682 + s * (1 ±0)` - // Minimum execution time: 20_583_000 picoseconds. - Weight::from_parts(20_771_000, 0) + // Minimum execution time: 16_602_000 picoseconds. + Weight::from_parts(16_834_000, 0) .saturating_add(Weight::from_parts(0, 3682)) - // Standard Error: 11 - .saturating_add(Weight::from_parts(2_250, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(2)) + // Standard Error: 10 + .saturating_add(Weight::from_parts(1_307, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(s.into())) } - /// Storage: Scheduler Lookup (r:0 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn service_task_named() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_271_000 picoseconds. - Weight::from_parts(7_447_000, 0) + // Minimum execution time: 4_202_000 picoseconds. + Weight::from_parts(4_383_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -118,90 +118,169 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_547_000 picoseconds. - Weight::from_parts(5_776_000, 0) + // Minimum execution time: 2_917_000 picoseconds. + Weight::from_parts(3_043_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn execute_dispatch_signed() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_480_000 picoseconds. - Weight::from_parts(2_628_000, 0) + // Minimum execution time: 1_707_000 picoseconds. + Weight::from_parts(1_802_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn execute_dispatch_unsigned() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_479_000 picoseconds. - Weight::from_parts(2_626_000, 0) + // Minimum execution time: 1_671_000 picoseconds. + Weight::from_parts(1_796_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 49]`. fn schedule(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `116 + s * (177 ±0)` // Estimated: `42428` - // Minimum execution time: 13_350_000 picoseconds. - Weight::from_parts(15_289_847, 0) + // Minimum execution time: 9_313_000 picoseconds. + Weight::from_parts(12_146_613, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 5_375 - .saturating_add(Weight::from_parts(974_567, 0).saturating_mul(s.into())) + // Standard Error: 1_381 + .saturating_add(Weight::from_parts(360_418, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - /// Storage: Scheduler Lookup (r:0 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// The range of component `s` is `[1, 50]`. fn cancel(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `116 + s * (177 ±0)` // Estimated: `42428` - // Minimum execution time: 17_646_000 picoseconds. - Weight::from_parts(15_858_434, 0) + // Minimum execution time: 13_079_000 picoseconds. + Weight::from_parts(12_921_017, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 5_354 - .saturating_add(Weight::from_parts(1_697_642, 0).saturating_mul(s.into())) + // Standard Error: 1_112 + .saturating_add(Weight::from_parts(538_089, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Scheduler Lookup (r:1 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:1 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 49]`. fn schedule_named(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `293 + s * (185 ±0)` // Estimated: `42428` - // Minimum execution time: 16_419_000 picoseconds. - Weight::from_parts(19_868_760, 0) + // Minimum execution time: 12_458_000 picoseconds. + Weight::from_parts(16_009_539, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 6_915 - .saturating_add(Weight::from_parts(1_010_225, 0).saturating_mul(s.into())) + // Standard Error: 2_260 + .saturating_add(Weight::from_parts(399_245, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Scheduler Lookup (r:1 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:1 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) /// The range of component `s` is `[1, 50]`. fn cancel_named(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `319 + s * (185 ±0)` // Estimated: `42428` - // Minimum execution time: 19_574_000 picoseconds. - Weight::from_parts(18_453_197, 0) + // Minimum execution time: 15_173_000 picoseconds. + Weight::from_parts(15_602_728, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 6_009 - .saturating_add(Weight::from_parts(1_707_130, 0).saturating_mul(s.into())) + // Standard Error: 1_302 + .saturating_add(Weight::from_parts(557_878, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } + /// Storage: `Scheduler::Retries` (r:1 w:2) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 50]`. + fn schedule_retry(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `197` + // Estimated: `42428` + // Minimum execution time: 13_531_000 picoseconds. + Weight::from_parts(13_985_249, 0) + .saturating_add(Weight::from_parts(0, 42428)) + // Standard Error: 619 + .saturating_add(Weight::from_parts(39_068, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn set_retry() -> Weight { + // Proof Size summary in bytes: + // Measured: `116 + s * (177 ±0)` + // Estimated: `42428` + // Minimum execution time: 8_050_000 picoseconds. + Weight::from_parts(8_440_627, 0) + .saturating_add(Weight::from_parts(0, 42428)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:0) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn set_retry_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `325 + s * (185 ±0)` + // Estimated: `42428` + // Minimum execution time: 10_876_000 picoseconds. + Weight::from_parts(11_708_172, 0) + .saturating_add(Weight::from_parts(0, 42428)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn cancel_retry() -> Weight { + // Proof Size summary in bytes: + // Measured: `116 + s * (177 ±0)` + // Estimated: `42428` + // Minimum execution time: 8_050_000 picoseconds. + Weight::from_parts(8_440_627, 0) + .saturating_add(Weight::from_parts(0, 42428)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:0) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn cancel_retry_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `325 + s * (185 ±0)` + // Estimated: `42428` + // Minimum execution time: 10_876_000 picoseconds. + Weight::from_parts(11_708_172, 0) + .saturating_add(Weight::from_parts(0, 42428)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } } diff --git a/prdoc/pr_3060.prdoc b/prdoc/pr_3060.prdoc new file mode 100644 index 00000000000..4cd6674ebb2 --- /dev/null +++ b/prdoc/pr_3060.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Add retry mechanics to `pallet-scheduler` + +doc: + - audience: Runtime Dev + description: | + This PR adds retry mechanics to pallet-scheduler, as described in the issue above. + Users can now set a retry configuration for a task so that, in case its scheduled run fails, it will be retried after a number of blocks, for a specified number of times or until it succeeds. + If a retried task runs successfully before running out of retries, its remaining retry counter will be reset to the initial value. If a retried task runs out of retries, it will be removed from the schedule. + Tasks which need to be scheduled for a retry are still subject to weight metering and agenda space, same as a regular task. Periodic tasks will have their periodic schedule put on hold while the task is retrying. + +crates: + - name: pallet-scheduler diff --git a/substrate/frame/scheduler/src/benchmarking.rs b/substrate/frame/scheduler/src/benchmarking.rs index cc86a179737..18441d54b39 100644 --- a/substrate/frame/scheduler/src/benchmarking.rs +++ b/substrate/frame/scheduler/src/benchmarking.rs @@ -22,12 +22,13 @@ use frame_benchmarking::v1::{account, benchmarks, BenchmarkError}; use frame_support::{ ensure, traits::{schedule::Priority, BoundedInline}, + weights::WeightMeter, }; use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; use sp_std::{prelude::*, vec}; use crate::Pallet as Scheduler; -use frame_system::Call as SystemCall; +use frame_system::{Call as SystemCall, EventRecord}; const SEED: u32 = 0; @@ -35,6 +36,14 @@ const BLOCK_NUMBER: u32 = 2; type SystemOrigin = ::RuntimeOrigin; +fn assert_last_event(generic_event: ::RuntimeEvent) { + let events = frame_system::Pallet::::events(); + let system_event: ::RuntimeEvent = generic_event.into(); + // compare to the last event record + let EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); +} + /// Add `n` items to the schedule. /// /// For `resolved`: @@ -306,5 +315,105 @@ benchmarks! { ); } + schedule_retry { + let s in 1 .. T::MaxScheduledPerBlock::get(); + let when = BLOCK_NUMBER.into(); + + fill_schedule::(when, s)?; + let name = u32_to_name(s - 1); + let address = Lookup::::get(name).unwrap(); + let period: BlockNumberFor = 1u32.into(); + let root: ::PalletsOrigin = frame_system::RawOrigin::Root.into(); + let retry_config = RetryConfig { total_retries: 10, remaining: 10, period }; + Retries::::insert(address, retry_config); + let (mut when, index) = address; + let task = Agenda::::get(when)[index as usize].clone().unwrap(); + let mut weight_counter = WeightMeter::with_limit(T::MaximumWeight::get()); + }: { + Scheduler::::schedule_retry(&mut weight_counter, when, when, index, &task, retry_config); + } verify { + when = when + BlockNumberFor::::one(); + assert_eq!( + Retries::::get((when, 0)), + Some(RetryConfig { total_retries: 10, remaining: 9, period }) + ); + } + + set_retry { + let s = T::MaxScheduledPerBlock::get(); + let when = BLOCK_NUMBER.into(); + + fill_schedule::(when, s)?; + let name = u32_to_name(s - 1); + let address = Lookup::::get(name).unwrap(); + let (when, index) = address; + let period = BlockNumberFor::::one(); + }: _(RawOrigin::Root, (when, index), 10, period) + verify { + assert_eq!( + Retries::::get((when, index)), + Some(RetryConfig { total_retries: 10, remaining: 10, period }) + ); + assert_last_event::( + Event::RetrySet { task: address, id: None, period, retries: 10 }.into(), + ); + } + + set_retry_named { + let s = T::MaxScheduledPerBlock::get(); + let when = BLOCK_NUMBER.into(); + + fill_schedule::(when, s)?; + let name = u32_to_name(s - 1); + let address = Lookup::::get(name).unwrap(); + let (when, index) = address; + let period = BlockNumberFor::::one(); + }: _(RawOrigin::Root, name, 10, period) + verify { + assert_eq!( + Retries::::get((when, index)), + Some(RetryConfig { total_retries: 10, remaining: 10, period }) + ); + assert_last_event::( + Event::RetrySet { task: address, id: Some(name), period, retries: 10 }.into(), + ); + } + + cancel_retry { + let s = T::MaxScheduledPerBlock::get(); + let when = BLOCK_NUMBER.into(); + + fill_schedule::(when, s)?; + let name = u32_to_name(s - 1); + let address = Lookup::::get(name).unwrap(); + let (when, index) = address; + let period = BlockNumberFor::::one(); + assert!(Scheduler::::set_retry(RawOrigin::Root.into(), (when, index), 10, period).is_ok()); + }: _(RawOrigin::Root, (when, index)) + verify { + assert!(!Retries::::contains_key((when, index))); + assert_last_event::( + Event::RetryCancelled { task: address, id: None }.into(), + ); + } + + cancel_retry_named { + let s = T::MaxScheduledPerBlock::get(); + let when = BLOCK_NUMBER.into(); + + fill_schedule::(when, s)?; + let name = u32_to_name(s - 1); + let address = Lookup::::get(name).unwrap(); + let (when, index) = address; + let period = BlockNumberFor::::one(); + assert!(Scheduler::::set_retry_named(RawOrigin::Root.into(), name, 10, period).is_ok()); + }: _(RawOrigin::Root, name) + verify { + assert!(!Retries::::contains_key((when, index))); + assert_last_event::( + Event::RetryCancelled { task: address, id: Some(name) }.into(), + ); + } + impl_benchmark_test_suite!(Scheduler, crate::mock::new_test_ext(), crate::mock::Test); } diff --git a/substrate/frame/scheduler/src/lib.rs b/substrate/frame/scheduler/src/lib.rs index e94f154eee3..daebebdee99 100644 --- a/substrate/frame/scheduler/src/lib.rs +++ b/substrate/frame/scheduler/src/lib.rs @@ -122,6 +122,17 @@ pub type CallOrHashOf = pub type BoundedCallOf = Bounded<::RuntimeCall, ::Hashing>; +/// The configuration of the retry mechanism for a given task along with its current state. +#[derive(Clone, Copy, RuntimeDebug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] +pub struct RetryConfig { + /// Initial amount of retries allowed. + total_retries: u8, + /// Amount of retries left. + remaining: u8, + /// Period of time between retry attempts. + period: Period, +} + #[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))] #[derive(Clone, RuntimeDebug, Encode, Decode)] struct ScheduledV1 { @@ -148,6 +159,26 @@ pub struct Scheduled { _phantom: PhantomData, } +impl + Scheduled +where + Call: Clone, + PalletsOrigin: Clone, +{ + /// Create a new task to be used for retry attempts of the original one. The cloned task will + /// have the same `priority`, `call` and `origin`, but will always be non-periodic and unnamed. + pub fn as_retry(&self) -> Self { + Self { + maybe_id: None, + priority: self.priority, + call: self.call.clone(), + maybe_periodic: None, + origin: self.origin.clone(), + _phantom: Default::default(), + } + } +} + use crate::{Scheduled as ScheduledV3, Scheduled as ScheduledV2}; pub type ScheduledV2Of = ScheduledV2< @@ -273,6 +304,16 @@ pub mod pallet { ValueQuery, >; + /// Retry configurations for items to be executed, indexed by task address. + #[pallet::storage] + pub type Retries = StorageMap< + _, + Blake2_128Concat, + TaskAddress>, + RetryConfig>, + OptionQuery, + >; + /// Lookup from a name to the block number and index of the task. /// /// For v3 -> v4 the previously unbounded identities are Blake2-256 hashed to form the v4 @@ -295,10 +336,22 @@ pub mod pallet { id: Option, result: DispatchResult, }, + /// Set a retry configuration for some task. + RetrySet { + task: TaskAddress>, + id: Option, + period: BlockNumberFor, + retries: u8, + }, + /// Cancel a retry configuration for some task. + RetryCancelled { task: TaskAddress>, id: Option }, /// The call for the provided hash was not found so the task has been aborted. CallUnavailable { task: TaskAddress>, id: Option }, /// The given task was unable to be renewed since the agenda is full at that block. PeriodicFailed { task: TaskAddress>, id: Option }, + /// The given task was unable to be retried since the agenda is full at that block or there + /// was not enough weight to reschedule it. + RetryFailed { task: TaskAddress>, id: Option }, /// The given task can never be executed since it is overweight. PermanentlyOverweight { task: TaskAddress>, id: Option }, } @@ -440,6 +493,111 @@ pub mod pallet { )?; Ok(()) } + + /// Set a retry configuration for a task so that, in case its scheduled run fails, it will + /// be retried after `period` blocks, for a total amount of `retries` retries or until it + /// succeeds. + /// + /// Tasks which need to be scheduled for a retry are still subject to weight metering and + /// agenda space, same as a regular task. If a periodic task fails, it will be scheduled + /// normally while the task is retrying. + /// + /// Tasks scheduled as a result of a retry for a periodic task are unnamed, non-periodic + /// clones of the original task. Their retry configuration will be derived from the + /// original task's configuration, but will have a lower value for `remaining` than the + /// original `total_retries`. + #[pallet::call_index(6)] + #[pallet::weight(::WeightInfo::set_retry())] + pub fn set_retry( + origin: OriginFor, + task: TaskAddress>, + retries: u8, + period: BlockNumberFor, + ) -> DispatchResult { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + let origin = ::RuntimeOrigin::from(origin); + let (when, index) = task; + let agenda = Agenda::::get(when); + let scheduled = agenda + .get(index as usize) + .and_then(Option::as_ref) + .ok_or(Error::::NotFound)?; + Self::ensure_privilege(origin.caller(), &scheduled.origin)?; + Retries::::insert( + (when, index), + RetryConfig { total_retries: retries, remaining: retries, period }, + ); + Self::deposit_event(Event::RetrySet { task, id: None, period, retries }); + Ok(()) + } + + /// Set a retry configuration for a named task so that, in case its scheduled run fails, it + /// will be retried after `period` blocks, for a total amount of `retries` retries or until + /// it succeeds. + /// + /// Tasks which need to be scheduled for a retry are still subject to weight metering and + /// agenda space, same as a regular task. If a periodic task fails, it will be scheduled + /// normally while the task is retrying. + /// + /// Tasks scheduled as a result of a retry for a periodic task are unnamed, non-periodic + /// clones of the original task. Their retry configuration will be derived from the + /// original task's configuration, but will have a lower value for `remaining` than the + /// original `total_retries`. + #[pallet::call_index(7)] + #[pallet::weight(::WeightInfo::set_retry_named())] + pub fn set_retry_named( + origin: OriginFor, + id: TaskName, + retries: u8, + period: BlockNumberFor, + ) -> DispatchResult { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + let origin = ::RuntimeOrigin::from(origin); + let (when, agenda_index) = Lookup::::get(&id).ok_or(Error::::NotFound)?; + let agenda = Agenda::::get(when); + let scheduled = agenda + .get(agenda_index as usize) + .and_then(Option::as_ref) + .ok_or(Error::::NotFound)?; + Self::ensure_privilege(origin.caller(), &scheduled.origin)?; + Retries::::insert( + (when, agenda_index), + RetryConfig { total_retries: retries, remaining: retries, period }, + ); + Self::deposit_event(Event::RetrySet { + task: (when, agenda_index), + id: Some(id), + period, + retries, + }); + Ok(()) + } + + /// Removes the retry configuration of a task. + #[pallet::call_index(8)] + #[pallet::weight(::WeightInfo::cancel_retry())] + pub fn cancel_retry( + origin: OriginFor, + task: TaskAddress>, + ) -> DispatchResult { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + let origin = ::RuntimeOrigin::from(origin); + Self::do_cancel_retry(origin.caller(), task)?; + Self::deposit_event(Event::RetryCancelled { task, id: None }); + Ok(()) + } + + /// Cancel the retry configuration of a named task. + #[pallet::call_index(9)] + #[pallet::weight(::WeightInfo::cancel_retry_named())] + pub fn cancel_retry_named(origin: OriginFor, id: TaskName) -> DispatchResult { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + let origin = ::RuntimeOrigin::from(origin); + let task = Lookup::::get(&id).ok_or(Error::::NotFound)?; + Self::do_cancel_retry(origin.caller(), task)?; + Self::deposit_event(Event::RetryCancelled { task, id: Some(id) }); + Ok(()) + } } } @@ -838,12 +996,7 @@ impl Pallet { Ok(None), |s| -> Result>, DispatchError> { if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) { - if matches!( - T::OriginPrivilegeCmp::cmp_privilege(o, &s.origin), - Some(Ordering::Less) | None - ) { - return Err(BadOrigin.into()) - } + Self::ensure_privilege(o, &s.origin)?; }; Ok(s.take()) }, @@ -854,6 +1007,7 @@ impl Pallet { if let Some(id) = s.maybe_id { Lookup::::remove(id); } + Retries::::remove((when, index)); Self::cleanup_agenda(when); Self::deposit_event(Event::Canceled { when, index }); Ok(()) @@ -931,12 +1085,8 @@ impl Pallet { Agenda::::try_mutate(when, |agenda| -> DispatchResult { if let Some(s) = agenda.get_mut(i) { if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) { - if matches!( - T::OriginPrivilegeCmp::cmp_privilege(o, &s.origin), - Some(Ordering::Less) | None - ) { - return Err(BadOrigin.into()) - } + Self::ensure_privilege(o, &s.origin)?; + Retries::::remove((when, index)); T::Preimages::drop(&s.call); } *s = None; @@ -973,6 +1123,20 @@ impl Pallet { Self::deposit_event(Event::Canceled { when, index }); Self::place_task(new_time, task).map_err(|x| x.0) } + + fn do_cancel_retry( + origin: &T::PalletsOrigin, + (when, index): TaskAddress>, + ) -> Result<(), DispatchError> { + let agenda = Agenda::::get(when); + let scheduled = agenda + .get(index as usize) + .and_then(Option::as_ref) + .ok_or(Error::::NotFound)?; + Self::ensure_privilege(origin, &scheduled.origin)?; + Retries::::remove((when, index)); + Ok(()) + } } enum ServiceTaskError { @@ -1124,11 +1288,21 @@ impl Pallet { }, Err(()) => Err((Overweight, Some(task))), Ok(result) => { + let failed = result.is_err(); + let maybe_retry_config = Retries::::take((when, agenda_index)); Self::deposit_event(Event::Dispatched { task: (when, agenda_index), id: task.maybe_id, result, }); + + match maybe_retry_config { + Some(retry_config) if failed => { + Self::schedule_retry(weight, now, when, agenda_index, &task, retry_config); + }, + _ => {}, + } + if let &Some((period, count)) = &task.maybe_periodic { if count > 1 { task.maybe_periodic = Some((period, count - 1)); @@ -1137,7 +1311,10 @@ impl Pallet { } let wake = now.saturating_add(period); match Self::place_task(wake, task) { - Ok(_) => {}, + Ok(new_address) => + if let Some(retry_config) = maybe_retry_config { + Retries::::insert(new_address, retry_config); + }, Err((_, task)) => { // TODO: Leave task in storage somewhere for it to be rescheduled // manually. @@ -1192,6 +1369,70 @@ impl Pallet { let _ = weight.try_consume(call_weight); Ok(result) } + + /// Check if a task has a retry configuration in place and, if so, try to reschedule it. + /// + /// Possible causes for failure to schedule a retry for a task: + /// - there wasn't enough weight to run the task reschedule logic + /// - there was no retry configuration in place + /// - there were no more retry attempts left + /// - the agenda was full. + fn schedule_retry( + weight: &mut WeightMeter, + now: BlockNumberFor, + when: BlockNumberFor, + agenda_index: u32, + task: &ScheduledOf, + retry_config: RetryConfig>, + ) { + if weight + .try_consume(T::WeightInfo::schedule_retry(T::MaxScheduledPerBlock::get())) + .is_err() + { + Self::deposit_event(Event::RetryFailed { + task: (when, agenda_index), + id: task.maybe_id, + }); + return; + } + + let RetryConfig { total_retries, mut remaining, period } = retry_config; + remaining = match remaining.checked_sub(1) { + Some(n) => n, + None => return, + }; + let wake = now.saturating_add(period); + match Self::place_task(wake, task.as_retry()) { + Ok(address) => { + // Reinsert the retry config to the new address of the task after it was + // placed. + Retries::::insert(address, RetryConfig { total_retries, remaining, period }); + }, + Err((_, task)) => { + // TODO: Leave task in storage somewhere for it to be + // rescheduled manually. + T::Preimages::drop(&task.call); + Self::deposit_event(Event::RetryFailed { + task: (when, agenda_index), + id: task.maybe_id, + }); + }, + } + } + + /// Ensure that `left` has at least the same level of privilege or higher than `right`. + /// + /// Returns an error if `left` has a lower level of privilege or the two cannot be compared. + fn ensure_privilege( + left: &::PalletsOrigin, + right: &::PalletsOrigin, + ) -> Result<(), DispatchError> { + if matches!(T::OriginPrivilegeCmp::cmp_privilege(left, right), Some(Ordering::Less) | None) + { + return Err(BadOrigin.into()); + } + Ok(()) + } } impl schedule::v2::Anon, ::RuntimeCall, T::PalletsOrigin> diff --git a/substrate/frame/scheduler/src/mock.rs b/substrate/frame/scheduler/src/mock.rs index d22b9fcf8d9..dbdc430a833 100644 --- a/substrate/frame/scheduler/src/mock.rs +++ b/substrate/frame/scheduler/src/mock.rs @@ -51,6 +51,17 @@ pub mod logger { #[pallet::pallet] pub struct Pallet(_); + #[pallet::storage] + pub type Threshold = StorageValue<_, (BlockNumberFor, BlockNumberFor)>; + + #[pallet::error] + pub enum Error { + /// Under the threshold. + TooEarly, + /// Over the threshold. + TooLate, + } + #[pallet::hooks] impl Hooks> for Pallet {} @@ -89,6 +100,20 @@ pub mod logger { }); Ok(()) } + + #[pallet::call_index(2)] + #[pallet::weight(*weight)] + pub fn timed_log(origin: OriginFor, i: u32, weight: Weight) -> DispatchResult { + let now = frame_system::Pallet::::block_number(); + let (start, end) = Threshold::::get().unwrap_or((0u32.into(), u32::MAX.into())); + ensure!(now >= start, Error::::TooEarly); + ensure!(now <= end, Error::::TooLate); + Self::deposit_event(Event::Logged(i, weight)); + Log::mutate(|log| { + log.push((origin.caller().clone(), i)); + }); + Ok(()) + } } } @@ -198,6 +223,21 @@ impl WeightInfo for TestWeightInfo { fn cancel_named(_s: u32) -> Weight { Weight::from_parts(50, 0) } + fn schedule_retry(_s: u32) -> Weight { + Weight::from_parts(100000, 0) + } + fn set_retry() -> Weight { + Weight::from_parts(50, 0) + } + fn set_retry_named() -> Weight { + Weight::from_parts(50, 0) + } + fn cancel_retry() -> Weight { + Weight::from_parts(50, 0) + } + fn cancel_retry_named() -> Weight { + Weight::from_parts(50, 0) + } } parameter_types! { pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * diff --git a/substrate/frame/scheduler/src/tests.rs b/substrate/frame/scheduler/src/tests.rs index 1bf8b3e5f3a..1ed2ca9e2f3 100644 --- a/substrate/frame/scheduler/src/tests.rs +++ b/substrate/frame/scheduler/src/tests.rs @@ -19,7 +19,8 @@ use super::*; use crate::mock::{ - logger, new_test_ext, root, run_to_block, LoggerCall, RuntimeCall, Scheduler, Test, *, + logger::{self, Threshold}, + new_test_ext, root, run_to_block, LoggerCall, RuntimeCall, Scheduler, Test, *, }; use frame_support::{ assert_err, assert_noop, assert_ok, @@ -179,6 +180,865 @@ fn periodic_scheduling_works() { }); } +#[test] +fn retry_scheduling_works() { + new_test_ext().execute_with(|| { + // task fails until block 8 is reached + Threshold::::put((8, 100)); + // task 42 at #4 + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + assert!(Agenda::::get(4)[0].is_some()); + // retry 10 times every 3 blocks + assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 3)); + assert_eq!(Retries::::iter().count(), 1); + run_to_block(3); + assert!(logger::log().is_empty()); + assert!(Agenda::::get(4)[0].is_some()); + // task should be retried in block 7 + run_to_block(4); + assert!(Agenda::::get(4).is_empty()); + assert!(Agenda::::get(7)[0].is_some()); + assert!(logger::log().is_empty()); + run_to_block(6); + assert!(Agenda::::get(7)[0].is_some()); + assert!(logger::log().is_empty()); + // task still fails, should be retried in block 10 + run_to_block(7); + assert!(Agenda::::get(7).is_empty()); + assert!(Agenda::::get(10)[0].is_some()); + assert!(logger::log().is_empty()); + run_to_block(8); + assert!(Agenda::::get(10)[0].is_some()); + assert!(logger::log().is_empty()); + run_to_block(9); + assert!(logger::log().is_empty()); + assert_eq!(Retries::::iter().count(), 1); + // finally it should succeed + run_to_block(10); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + assert_eq!(Retries::::iter().count(), 0); + run_to_block(11); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(12); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); +} + +#[test] +fn named_retry_scheduling_works() { + new_test_ext().execute_with(|| { + // task fails until block 8 is reached + Threshold::::put((8, 100)); + // task 42 at #4 + let call = RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0), + }); + assert_eq!( + Scheduler::do_schedule_named( + [1u8; 32], + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(call).unwrap(), + ) + .unwrap(), + (4, 0) + ); + assert!(Agenda::::get(4)[0].is_some()); + // retry 10 times every 3 blocks + assert_ok!(Scheduler::set_retry_named(root().into(), [1u8; 32], 10, 3)); + assert_eq!(Retries::::iter().count(), 1); + run_to_block(3); + assert!(logger::log().is_empty()); + assert!(Agenda::::get(4)[0].is_some()); + // task should be retried in block 7 + run_to_block(4); + assert!(Agenda::::get(4).is_empty()); + assert!(Agenda::::get(7)[0].is_some()); + assert!(logger::log().is_empty()); + run_to_block(6); + assert!(Agenda::::get(7)[0].is_some()); + assert!(logger::log().is_empty()); + // task still fails, should be retried in block 10 + run_to_block(7); + assert!(Agenda::::get(7).is_empty()); + assert!(Agenda::::get(10)[0].is_some()); + assert!(logger::log().is_empty()); + run_to_block(8); + assert!(Agenda::::get(10)[0].is_some()); + assert!(logger::log().is_empty()); + run_to_block(9); + assert!(logger::log().is_empty()); + assert_eq!(Retries::::iter().count(), 1); + // finally it should succeed + run_to_block(10); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + assert_eq!(Retries::::iter().count(), 0); + run_to_block(11); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(12); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); +} + +#[test] +fn retry_scheduling_multiple_tasks_works() { + new_test_ext().execute_with(|| { + // task fails until block 8 is reached + Threshold::::put((8, 100)); + // task 20 at #4 + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 20, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + // task 42 at #4 + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + + assert_eq!(Agenda::::get(4).len(), 2); + // task 20 will be retried 3 times every block + assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 3, 1)); + // task 42 will be retried 10 times every 3 blocks + assert_ok!(Scheduler::set_retry(root().into(), (4, 1), 10, 3)); + assert_eq!(Retries::::iter().count(), 2); + run_to_block(3); + assert!(logger::log().is_empty()); + assert_eq!(Agenda::::get(4).len(), 2); + // both tasks fail + run_to_block(4); + assert!(Agenda::::get(4).is_empty()); + // 20 is rescheduled for next block + assert_eq!(Agenda::::get(5).len(), 1); + // 42 is rescheduled for block 7 + assert_eq!(Agenda::::get(7).len(), 1); + assert!(logger::log().is_empty()); + // 20 still fails + run_to_block(5); + // 20 rescheduled for next block + assert_eq!(Agenda::::get(6).len(), 1); + assert_eq!(Agenda::::get(7).len(), 1); + assert_eq!(Retries::::iter().count(), 2); + assert!(logger::log().is_empty()); + // 20 still fails + run_to_block(6); + // rescheduled for next block together with 42 + assert_eq!(Agenda::::get(7).len(), 2); + assert_eq!(Retries::::iter().count(), 2); + assert!(logger::log().is_empty()); + // both tasks will fail, for 20 it was the last retry so it's dropped + run_to_block(7); + assert!(Agenda::::get(7).is_empty()); + assert!(Agenda::::get(8).is_empty()); + // 42 is rescheduled for block 10 + assert_eq!(Agenda::::get(10).len(), 1); + assert_eq!(Retries::::iter().count(), 1); + assert!(logger::log().is_empty()); + run_to_block(8); + assert_eq!(Agenda::::get(10).len(), 1); + assert!(logger::log().is_empty()); + run_to_block(9); + assert!(logger::log().is_empty()); + assert_eq!(Retries::::iter().count(), 1); + // 42 runs successfully + run_to_block(10); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + assert_eq!(Retries::::iter().count(), 0); + run_to_block(11); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(12); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); +} + +#[test] +fn retry_scheduling_multiple_named_tasks_works() { + new_test_ext().execute_with(|| { + // task fails until we reach block 8 + Threshold::::put((8, 100)); + // task 20 at #4 + assert_ok!(Scheduler::do_schedule_named( + [20u8; 32], + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 20, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + // task 42 at #4 + assert_ok!(Scheduler::do_schedule_named( + [42u8; 32], + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + + assert_eq!(Agenda::::get(4).len(), 2); + // task 20 will be retried 3 times every block + assert_ok!(Scheduler::set_retry_named(root().into(), [20u8; 32], 3, 1)); + // task 42 will be retried 10 times every 3 block + assert_ok!(Scheduler::set_retry_named(root().into(), [42u8; 32], 10, 3)); + assert_eq!(Retries::::iter().count(), 2); + run_to_block(3); + assert!(logger::log().is_empty()); + assert_eq!(Agenda::::get(4).len(), 2); + // both tasks fail + run_to_block(4); + assert!(Agenda::::get(4).is_empty()); + // 42 is rescheduled for block 7 + assert_eq!(Agenda::::get(7).len(), 1); + // 20 is rescheduled for next block + assert_eq!(Agenda::::get(5).len(), 1); + assert!(logger::log().is_empty()); + // 20 still fails + run_to_block(5); + // 20 rescheduled for next block + assert_eq!(Agenda::::get(6).len(), 1); + assert_eq!(Agenda::::get(7).len(), 1); + assert_eq!(Retries::::iter().count(), 2); + assert!(logger::log().is_empty()); + // 20 still fails + run_to_block(6); + // 20 rescheduled for next block together with 42 + assert_eq!(Agenda::::get(7).len(), 2); + assert_eq!(Retries::::iter().count(), 2); + assert!(logger::log().is_empty()); + // both tasks will fail, for 20 it was the last retry so it's dropped + run_to_block(7); + assert!(Agenda::::get(7).is_empty()); + assert!(Agenda::::get(8).is_empty()); + // 42 is rescheduled for block 10 + assert_eq!(Agenda::::get(10).len(), 1); + assert_eq!(Retries::::iter().count(), 1); + assert!(logger::log().is_empty()); + run_to_block(8); + assert_eq!(Agenda::::get(10).len(), 1); + assert!(logger::log().is_empty()); + run_to_block(9); + assert!(logger::log().is_empty()); + assert_eq!(Retries::::iter().count(), 1); + // 42 runs successfully + run_to_block(10); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + assert_eq!(Retries::::iter().count(), 0); + run_to_block(11); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(12); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); +} + +#[test] +fn retry_scheduling_with_period_works() { + new_test_ext().execute_with(|| { + // tasks fail until we reach block 4 and after we're past block 8 + Threshold::::put((4, 8)); + // task 42 at #4, every 3 blocks, 6 times + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + Some((3, 6)), + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + + assert!(Agenda::::get(4)[0].is_some()); + // 42 will be retried 10 times every 2 blocks + assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 2)); + assert_eq!(Retries::::iter().count(), 1); + run_to_block(3); + assert!(logger::log().is_empty()); + assert!(Agenda::::get(4)[0].is_some()); + // 42 runs successfully once, it will run again at block 7 + run_to_block(4); + assert!(Agenda::::get(4).is_empty()); + assert!(Agenda::::get(7)[0].is_some()); + assert_eq!(Retries::::iter().count(), 1); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + // nothing changed + run_to_block(6); + assert!(Agenda::::get(7)[0].is_some()); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + // 42 runs successfully again, it will run again at block 10 + run_to_block(7); + assert!(Agenda::::get(7).is_empty()); + assert!(Agenda::::get(10)[0].is_some()); + assert_eq!(Retries::::iter().count(), 1); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + run_to_block(9); + assert!(Agenda::::get(10)[0].is_some()); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + // 42 has 10 retries left out of a total of 10 + assert_eq!(Retries::::get((10, 0)).unwrap().remaining, 10); + // 42 will fail because we're outside the set threshold (block number in `4..8`), so it + // should be retried in 2 blocks (at block 12) + run_to_block(10); + // should be queued for the normal period of 3 blocks + assert!(Agenda::::get(13)[0].is_some()); + // should also be queued to be retried in 2 blocks + assert!(Agenda::::get(12)[0].is_some()); + // 42 has consumed one retry attempt + assert_eq!(Retries::::get((12, 0)).unwrap().remaining, 9); + assert_eq!(Retries::::get((13, 0)).unwrap().remaining, 10); + assert_eq!(Retries::::iter().count(), 2); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + // 42 will fail again + run_to_block(12); + // should still be queued for the normal period + assert!(Agenda::::get(13)[0].is_some()); + // should be queued to be retried in 2 blocks + assert!(Agenda::::get(14)[0].is_some()); + // 42 has consumed another retry attempt + assert_eq!(Retries::::get((14, 0)).unwrap().remaining, 8); + assert_eq!(Retries::::get((13, 0)).unwrap().remaining, 10); + assert_eq!(Retries::::iter().count(), 2); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + // 42 will fail for the regular periodic run + run_to_block(13); + // should still be queued for the normal period + assert!(Agenda::::get(16)[0].is_some()); + // should still be queued to be retried next block + assert!(Agenda::::get(14)[0].is_some()); + // 42 consumed another periodic run, which failed, so another retry is queued for block 15 + assert!(Agenda::::get(16)[0].as_ref().unwrap().maybe_periodic.is_some()); + assert!(Agenda::::get(15)[0].as_ref().unwrap().maybe_periodic.is_none()); + assert!(Agenda::::get(14)[0].as_ref().unwrap().maybe_periodic.is_none()); + assert_eq!(Retries::::iter().count(), 3); + assert!(Retries::::get((14, 0)).unwrap().remaining == 8); + assert!(Retries::::get((15, 0)).unwrap().remaining == 9); + assert!(Retries::::get((16, 0)).unwrap().remaining == 10); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + // change the threshold to allow the task to succeed + Threshold::::put((14, 100)); + // first retry should now succeed + run_to_block(14); + assert!(Agenda::::get(15)[0].as_ref().unwrap().maybe_periodic.is_none()); + assert_eq!(Agenda::::get(16).iter().filter(|entry| entry.is_some()).count(), 1); + assert!(Agenda::::get(16)[0].is_some()); + assert_eq!(Retries::::get((15, 0)).unwrap().remaining, 9); + assert_eq!(Retries::::get((16, 0)).unwrap().remaining, 10); + assert_eq!(Retries::::iter().count(), 2); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); + // second retry should also succeed + run_to_block(15); + assert_eq!(Agenda::::get(16).iter().filter(|entry| entry.is_some()).count(), 1); + assert!(Agenda::::get(16)[0].is_some()); + assert!(Agenda::::get(17).is_empty()); + assert_eq!(Retries::::get((16, 0)).unwrap().remaining, 10); + assert_eq!(Retries::::iter().count(), 1); + assert_eq!( + logger::log(), + vec![(root(), 42u32), (root(), 42u32), (root(), 42u32), (root(), 42u32)] + ); + // normal periodic run on block 16 will succeed + run_to_block(16); + // next periodic run at block 19 + assert!(Agenda::::get(19)[0].is_some()); + assert!(Agenda::::get(18).is_empty()); + assert!(Agenda::::get(17).is_empty()); + assert_eq!(Retries::::get((19, 0)).unwrap().remaining, 10); + assert_eq!(Retries::::iter().count(), 1); + assert_eq!( + logger::log(), + vec![ + (root(), 42u32), + (root(), 42u32), + (root(), 42u32), + (root(), 42u32), + (root(), 42u32) + ] + ); + // final periodic run on block 19 will succeed + run_to_block(19); + // next periodic run at block 19 + assert_eq!(Agenda::::iter().count(), 0); + assert_eq!(Retries::::iter().count(), 0); + assert_eq!( + logger::log(), + vec![ + (root(), 42u32), + (root(), 42u32), + (root(), 42u32), + (root(), 42u32), + (root(), 42u32), + (root(), 42u32) + ] + ); + }); +} + +#[test] +fn named_retry_scheduling_with_period_works() { + new_test_ext().execute_with(|| { + // tasks fail until we reach block 4 and after we're past block 8 + Threshold::::put((4, 8)); + // task 42 at #4, every 3 blocks, 6 times + assert_ok!(Scheduler::do_schedule_named( + [42u8; 32], + DispatchTime::At(4), + Some((3, 6)), + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + + assert!(Agenda::::get(4)[0].is_some()); + // 42 will be retried 10 times every 2 blocks + assert_ok!(Scheduler::set_retry_named(root().into(), [42u8; 32], 10, 2)); + assert_eq!(Retries::::iter().count(), 1); + run_to_block(3); + assert!(logger::log().is_empty()); + assert!(Agenda::::get(4)[0].is_some()); + // 42 runs successfully once, it will run again at block 7 + run_to_block(4); + assert!(Agenda::::get(4).is_empty()); + assert!(Agenda::::get(7)[0].is_some()); + assert_eq!(Retries::::iter().count(), 1); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + // nothing changed + run_to_block(6); + assert!(Agenda::::get(7)[0].is_some()); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + // 42 runs successfully again, it will run again at block 10 + run_to_block(7); + assert!(Agenda::::get(7).is_empty()); + assert!(Agenda::::get(10)[0].is_some()); + assert_eq!(Retries::::iter().count(), 1); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + run_to_block(9); + assert!(Agenda::::get(10)[0].is_some()); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + // 42 has 10 retries left out of a total of 10 + assert_eq!(Retries::::get((10, 0)).unwrap().remaining, 10); + // 42 will fail because we're outside the set threshold (block number in `4..8`), so it + // should be retried in 2 blocks (at block 12) + run_to_block(10); + // should be queued for the normal period of 3 blocks + assert!(Agenda::::get(13)[0].is_some()); + // should also be queued to be retried in 2 blocks + assert!(Agenda::::get(12)[0].is_some()); + // 42 has consumed one retry attempt + assert_eq!(Retries::::get((12, 0)).unwrap().remaining, 9); + assert_eq!(Retries::::get((13, 0)).unwrap().remaining, 10); + assert_eq!(Retries::::iter().count(), 2); + assert_eq!(Lookup::::get([42u8; 32]).unwrap(), (13, 0)); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + // 42 will fail again + run_to_block(12); + // should still be queued for the normal period + assert!(Agenda::::get(13)[0].is_some()); + // should be queued to be retried in 2 blocks + assert!(Agenda::::get(14)[0].is_some()); + // 42 has consumed another retry attempt + assert_eq!(Retries::::get((14, 0)).unwrap().remaining, 8); + assert_eq!(Retries::::get((13, 0)).unwrap().remaining, 10); + assert_eq!(Retries::::iter().count(), 2); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + // 42 will fail for the regular periodic run + run_to_block(13); + // should still be queued for the normal period + assert!(Agenda::::get(16)[0].is_some()); + // should still be queued to be retried next block + assert!(Agenda::::get(14)[0].is_some()); + // 42 consumed another periodic run, which failed, so another retry is queued for block 15 + assert!(Agenda::::get(16)[0].as_ref().unwrap().maybe_periodic.is_some()); + assert!(Agenda::::get(15)[0].as_ref().unwrap().maybe_periodic.is_none()); + assert!(Agenda::::get(14)[0].as_ref().unwrap().maybe_periodic.is_none()); + assert_eq!(Retries::::iter().count(), 3); + assert!(Retries::::get((14, 0)).unwrap().remaining == 8); + assert!(Retries::::get((15, 0)).unwrap().remaining == 9); + assert!(Retries::::get((16, 0)).unwrap().remaining == 10); + assert_eq!(Lookup::::get([42u8; 32]).unwrap(), (16, 0)); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + // change the threshold to allow the task to succeed + Threshold::::put((14, 100)); + // first retry should now succeed + run_to_block(14); + assert!(Agenda::::get(15)[0].as_ref().unwrap().maybe_periodic.is_none()); + assert_eq!(Agenda::::get(16).iter().filter(|entry| entry.is_some()).count(), 1); + assert!(Agenda::::get(16)[0].is_some()); + assert_eq!(Retries::::get((15, 0)).unwrap().remaining, 9); + assert_eq!(Retries::::get((16, 0)).unwrap().remaining, 10); + assert_eq!(Retries::::iter().count(), 2); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); + // second retry should also succeed + run_to_block(15); + assert_eq!(Agenda::::get(16).iter().filter(|entry| entry.is_some()).count(), 1); + assert!(Agenda::::get(16)[0].is_some()); + assert!(Agenda::::get(17).is_empty()); + assert_eq!(Retries::::get((16, 0)).unwrap().remaining, 10); + assert_eq!(Retries::::iter().count(), 1); + assert_eq!(Lookup::::get([42u8; 32]).unwrap(), (16, 0)); + assert_eq!( + logger::log(), + vec![(root(), 42u32), (root(), 42u32), (root(), 42u32), (root(), 42u32)] + ); + // normal periodic run on block 16 will succeed + run_to_block(16); + // next periodic run at block 19 + assert!(Agenda::::get(19)[0].is_some()); + assert!(Agenda::::get(18).is_empty()); + assert!(Agenda::::get(17).is_empty()); + assert_eq!(Retries::::get((19, 0)).unwrap().remaining, 10); + assert_eq!(Retries::::iter().count(), 1); + assert_eq!(Lookup::::get([42u8; 32]).unwrap(), (19, 0)); + assert_eq!( + logger::log(), + vec![ + (root(), 42u32), + (root(), 42u32), + (root(), 42u32), + (root(), 42u32), + (root(), 42u32) + ] + ); + // final periodic run on block 19 will succeed + run_to_block(19); + // next periodic run at block 19 + assert_eq!(Agenda::::iter().count(), 0); + assert_eq!(Retries::::iter().count(), 0); + assert_eq!(Lookup::::iter().count(), 0); + assert_eq!( + logger::log(), + vec![ + (root(), 42u32), + (root(), 42u32), + (root(), 42u32), + (root(), 42u32), + (root(), 42u32), + (root(), 42u32) + ] + ); + }); +} + +#[test] +fn retry_scheduling_expires() { + new_test_ext().execute_with(|| { + // task will fail if we're past block 3 + Threshold::::put((1, 3)); + // task 42 at #4 + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + assert!(Agenda::::get(4)[0].is_some()); + // task 42 will be retried 3 times every block + assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 3, 1)); + assert_eq!(Retries::::iter().count(), 1); + run_to_block(3); + assert!(logger::log().is_empty()); + // task 42 is scheduled for next block + assert!(Agenda::::get(4)[0].is_some()); + // task fails because we're past block 3 + run_to_block(4); + // task is scheduled for next block + assert!(Agenda::::get(4).is_empty()); + assert!(Agenda::::get(5)[0].is_some()); + // one retry attempt is consumed + assert_eq!(Retries::::get((5, 0)).unwrap().remaining, 2); + assert!(logger::log().is_empty()); + // task fails again + run_to_block(5); + // task is scheduled for next block + assert!(Agenda::::get(5).is_empty()); + assert!(Agenda::::get(6)[0].is_some()); + // another retry attempt is consumed + assert_eq!(Retries::::get((6, 0)).unwrap().remaining, 1); + assert!(logger::log().is_empty()); + // task fails again + run_to_block(6); + // task is scheduled for next block + assert!(Agenda::::get(6).is_empty()); + assert!(Agenda::::get(7)[0].is_some()); + // another retry attempt is consumed + assert_eq!(Retries::::get((7, 0)).unwrap().remaining, 0); + assert!(logger::log().is_empty()); + // task fails again + run_to_block(7); + // task ran out of retries so it gets dropped + assert_eq!(Agenda::::iter().count(), 0); + assert_eq!(Retries::::iter().count(), 0); + assert!(logger::log().is_empty()); + }); +} + +#[test] +fn set_retry_bad_origin() { + new_test_ext().execute_with(|| { + // task 42 at #4 with account 101 as origin + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + 101.into(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + + assert!(Agenda::::get(4)[0].is_some()); + // try to change the retry config with a different (non-root) account + let res: Result<(), DispatchError> = + Scheduler::set_retry(RuntimeOrigin::signed(102), (4, 0), 10, 2); + assert_eq!(res, Err(BadOrigin.into())); + }); +} + +#[test] +fn set_named_retry_bad_origin() { + new_test_ext().execute_with(|| { + // task 42 at #4 with account 101 as origin + assert_ok!(Scheduler::do_schedule_named( + [42u8; 32], + DispatchTime::At(4), + None, + 127, + 101.into(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + + assert!(Agenda::::get(4)[0].is_some()); + // try to change the retry config with a different (non-root) account + let res: Result<(), DispatchError> = + Scheduler::set_retry_named(RuntimeOrigin::signed(102), [42u8; 32], 10, 2); + assert_eq!(res, Err(BadOrigin.into())); + }); +} + +#[test] +fn set_retry_works() { + new_test_ext().execute_with(|| { + // task 42 at #4 + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + + assert!(Agenda::::get(4)[0].is_some()); + // make sure the retry configuration was stored + assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 2)); + assert_eq!( + Retries::::get((4, 0)), + Some(RetryConfig { total_retries: 10, remaining: 10, period: 2 }) + ); + }); +} + +#[test] +fn set_named_retry_works() { + new_test_ext().execute_with(|| { + // task 42 at #4 with account 101 as origin + assert_ok!(Scheduler::do_schedule_named( + [42u8; 32], + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + + assert!(Agenda::::get(4)[0].is_some()); + // make sure the retry configuration was stored + assert_ok!(Scheduler::set_retry_named(root().into(), [42u8; 32], 10, 2)); + let address = Lookup::::get([42u8; 32]).unwrap(); + assert_eq!( + Retries::::get(address), + Some(RetryConfig { total_retries: 10, remaining: 10, period: 2 }) + ); + }); +} + +#[test] +fn retry_periodic_full_cycle() { + new_test_ext().execute_with(|| { + // tasks fail after we pass block 1000 + Threshold::::put((1, 1000)); + // task 42 at #4, every 100 blocks, 4 times + assert_ok!(Scheduler::do_schedule_named( + [42u8; 32], + DispatchTime::At(10), + Some((100, 4)), + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + + assert!(Agenda::::get(10)[0].is_some()); + // 42 will be retried 2 times every block + assert_ok!(Scheduler::set_retry_named(root().into(), [42u8; 32], 2, 1)); + assert_eq!(Retries::::iter().count(), 1); + run_to_block(9); + assert!(logger::log().is_empty()); + assert!(Agenda::::get(10)[0].is_some()); + // 42 runs successfully once, it will run again at block 110 + run_to_block(10); + assert!(Agenda::::get(10).is_empty()); + assert!(Agenda::::get(110)[0].is_some()); + assert_eq!(Retries::::iter().count(), 1); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + // nothing changed + run_to_block(109); + assert!(Agenda::::get(110)[0].is_some()); + // original task still has 2 remaining retries + assert_eq!(Retries::::get((110, 0)).unwrap().remaining, 2); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + // make 42 fail next block + Threshold::::put((1, 2)); + // 42 will fail because we're outside the set threshold (block number in `1..2`), so it + // should be retried next block (at block 111) + run_to_block(110); + // should be queued for the normal period of 100 blocks + assert!(Agenda::::get(210)[0].is_some()); + // should also be queued to be retried next block + assert!(Agenda::::get(111)[0].is_some()); + // 42 retry clone has consumed one retry attempt + assert_eq!(Retries::::get((111, 0)).unwrap().remaining, 1); + // 42 original task still has the original remaining attempts + assert_eq!(Retries::::get((210, 0)).unwrap().remaining, 2); + assert_eq!(Retries::::iter().count(), 2); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + // 42 retry will fail again + run_to_block(111); + // should still be queued for the normal period + assert!(Agenda::::get(210)[0].is_some()); + // should be queued to be retried next block + assert!(Agenda::::get(112)[0].is_some()); + // 42 has consumed another retry attempt + assert_eq!(Retries::::get((210, 0)).unwrap().remaining, 2); + assert_eq!(Retries::::get((112, 0)).unwrap().remaining, 0); + assert_eq!(Retries::::iter().count(), 2); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + // 42 retry will fail again + run_to_block(112); + // should still be queued for the normal period + assert!(Agenda::::get(210)[0].is_some()); + // 42 retry clone ran out of retries, must have been evicted + assert_eq!(Agenda::::iter().count(), 1); + + // advance + run_to_block(209); + // should still be queued for the normal period + assert!(Agenda::::get(210)[0].is_some()); + // 42 retry clone ran out of retries, must have been evicted + assert_eq!(Agenda::::iter().count(), 1); + // 42 should fail again and should spawn another retry clone + run_to_block(210); + // should be queued for the normal period of 100 blocks + assert!(Agenda::::get(310)[0].is_some()); + // should also be queued to be retried next block + assert!(Agenda::::get(211)[0].is_some()); + // 42 retry clone has consumed one retry attempt + assert_eq!(Retries::::get((211, 0)).unwrap().remaining, 1); + // 42 original task still has the original remaining attempts + assert_eq!(Retries::::get((310, 0)).unwrap().remaining, 2); + assert_eq!(Retries::::iter().count(), 2); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + // make 42 run successfully again + Threshold::::put((1, 1000)); + // 42 retry clone should now succeed + run_to_block(211); + // should be queued for the normal period of 100 blocks + assert!(Agenda::::get(310)[0].is_some()); + // retry was successful, retry task should have been discarded + assert_eq!(Agenda::::iter().count(), 1); + // 42 original task still has the original remaining attempts + assert_eq!(Retries::::get((310, 0)).unwrap().remaining, 2); + assert_eq!(Retries::::iter().count(), 1); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + + // fast forward to the last periodic run of 42 + run_to_block(310); + // 42 was successful, the period ended as this was the 4th scheduled periodic run so 42 must + // have been discarded + assert_eq!(Agenda::::iter().count(), 0); + // agenda is empty so no retries should exist + assert_eq!(Retries::::iter().count(), 0); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); + }); +} + #[test] fn reschedule_works() { new_test_ext().execute_with(|| { @@ -430,6 +1290,117 @@ fn scheduler_respects_weight_limits() { }); } +#[test] +fn retry_respects_weight_limits() { + let max_weight: Weight = ::MaximumWeight::get(); + new_test_ext().execute_with(|| { + // schedule 42 + let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: max_weight / 3 * 2 }); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(8), + None, + 127, + root(), + Preimage::bound(call).unwrap(), + )); + // schedule 20 with a call that will fail until we reach block 8 + Threshold::::put((8, 100)); + let call = RuntimeCall::Logger(LoggerCall::timed_log { i: 20, weight: max_weight / 3 * 2 }); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(call).unwrap(), + )); + // set a retry config for 20 for 10 retries every block + assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 1)); + // 20 should fail and be retried later + run_to_block(4); + assert!(Agenda::::get(5)[0].is_some()); + assert!(Agenda::::get(8)[0].is_some()); + assert_eq!(Retries::::iter().count(), 1); + assert!(logger::log().is_empty()); + // 20 still fails but is scheduled next block together with 42 + run_to_block(7); + assert_eq!(Agenda::::get(8).len(), 2); + assert_eq!(Retries::::iter().count(), 1); + assert!(logger::log().is_empty()); + // 20 and 42 do not fit together + // 42 is executed as it was first in the queue + // 20 is still on the 8th block's agenda + run_to_block(8); + assert!(Agenda::::get(8)[0].is_none()); + assert!(Agenda::::get(8)[1].is_some()); + assert_eq!(Retries::::iter().count(), 1); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + // 20 is executed and the schedule is cleared + run_to_block(9); + assert_eq!(Agenda::::iter().count(), 0); + assert_eq!(Retries::::iter().count(), 0); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 20u32)]); + }); +} + +#[test] +fn try_schedule_retry_respects_weight_limits() { + let max_weight: Weight = ::MaximumWeight::get(); + new_test_ext().execute_with(|| { + let service_agendas_weight = ::WeightInfo::service_agendas_base(); + let service_agenda_weight = ::WeightInfo::service_agenda_base( + ::MaxScheduledPerBlock::get(), + ); + let actual_service_agenda_weight = ::WeightInfo::service_agenda_base(1); + // Some weight for `service_agenda` will be refunded, so we need to make sure the weight + // `try_schedule_retry` is going to ask for is greater than this difference, and we take a + // safety factor of 10 to make sure we're over that limit. + let meter = WeightMeter::with_limit( + ::WeightInfo::schedule_retry( + ::MaxScheduledPerBlock::get(), + ) / 10, + ); + assert!(meter.can_consume(service_agenda_weight - actual_service_agenda_weight)); + + let reference_call = + RuntimeCall::Logger(LoggerCall::timed_log { i: 20, weight: max_weight / 3 * 2 }); + let bounded = ::Preimages::bound(reference_call).unwrap(); + let base_weight = ::WeightInfo::service_task( + bounded.lookup_len().map(|x| x as usize), + false, + false, + ); + // we make the call cost enough so that all checks have enough weight to run aside from + // `try_schedule_retry` + let call_weight = max_weight - service_agendas_weight - service_agenda_weight - base_weight; + let call = RuntimeCall::Logger(LoggerCall::timed_log { i: 20, weight: call_weight }); + // schedule 20 with a call that will fail until we reach block 8 + Threshold::::put((8, 100)); + + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(call).unwrap(), + )); + // set a retry config for 20 for 10 retries every block + assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 1)); + // 20 should fail and, because of insufficient weight, it should not be scheduled again + run_to_block(4); + // nothing else should be scheduled + assert_eq!(Agenda::::iter().count(), 0); + assert_eq!(Retries::::iter().count(), 0); + assert_eq!(logger::log(), vec![]); + // check the `RetryFailed` event happened + let events = frame_system::Pallet::::events(); + let system_event: ::RuntimeEvent = + Event::RetryFailed { task: (4, 0), id: None }.into(); + // compare to the last event record + let frame_system::EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); + }); +} + /// Permanently overweight calls are not deleted but also not executed. #[test] fn scheduler_does_not_delete_permanently_overweight_call() { @@ -877,6 +1848,134 @@ fn should_check_origin_for_cancel() { }); } +#[test] +fn cancel_removes_retry_entry() { + new_test_ext().execute_with(|| { + // task fails until block 99 is reached + Threshold::::put((99, 100)); + // task 20 at #4 + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 20, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + // named task 42 at #4 + assert_ok!(Scheduler::do_schedule_named( + [1u8; 32], + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + + assert_eq!(Agenda::::get(4).len(), 2); + // task 20 will be retried 3 times every block + assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 1)); + // task 42 will be retried 10 times every 3 blocks + assert_ok!(Scheduler::set_retry_named(root().into(), [1u8; 32], 10, 1)); + assert_eq!(Retries::::iter().count(), 2); + run_to_block(3); + assert!(logger::log().is_empty()); + assert_eq!(Agenda::::get(4).len(), 2); + // both tasks fail + run_to_block(4); + assert!(Agenda::::get(4).is_empty()); + // 42 and 20 are rescheduled for next block + assert_eq!(Agenda::::get(5).len(), 2); + assert!(logger::log().is_empty()); + // 42 and 20 still fail + run_to_block(5); + // 42 and 20 rescheduled for next block + assert_eq!(Agenda::::get(6).len(), 2); + assert_eq!(Retries::::iter().count(), 2); + assert!(logger::log().is_empty()); + + // even though 42 is being retried, the tasks scheduled for retries are not named + assert_eq!(Lookup::::iter().count(), 0); + assert!(Scheduler::cancel(root().into(), 6, 0).is_ok()); + + // 20 is removed, 42 still fails + run_to_block(6); + // 42 rescheduled for next block + assert_eq!(Agenda::::get(7).len(), 1); + // 20's retry entry is removed + assert!(!Retries::::contains_key((4, 0))); + assert_eq!(Retries::::iter().count(), 1); + assert!(logger::log().is_empty()); + + assert!(Scheduler::cancel(root().into(), 7, 0).is_ok()); + + // both tasks are canceled, everything is removed now + run_to_block(7); + assert!(Agenda::::get(8).is_empty()); + assert_eq!(Retries::::iter().count(), 0); + }); +} + +#[test] +fn cancel_retries_works() { + new_test_ext().execute_with(|| { + // task fails until block 99 is reached + Threshold::::put((99, 100)); + // task 20 at #4 + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 20, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + // named task 42 at #4 + assert_ok!(Scheduler::do_schedule_named( + [1u8; 32], + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + + assert_eq!(Agenda::::get(4).len(), 2); + // task 20 will be retried 3 times every block + assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 1)); + // task 42 will be retried 10 times every 3 blocks + assert_ok!(Scheduler::set_retry_named(root().into(), [1u8; 32], 10, 1)); + assert_eq!(Retries::::iter().count(), 2); + run_to_block(3); + assert!(logger::log().is_empty()); + assert_eq!(Agenda::::get(4).len(), 2); + // cancel the retry config for 20 + assert_ok!(Scheduler::cancel_retry(root().into(), (4, 0))); + assert_eq!(Retries::::iter().count(), 1); + // cancel the retry config for 42 + assert_ok!(Scheduler::cancel_retry_named(root().into(), [1u8; 32])); + assert_eq!(Retries::::iter().count(), 0); + run_to_block(4); + // both tasks failed and there are no more retries, so they are evicted + assert_eq!(Agenda::::get(4).len(), 0); + assert_eq!(Retries::::iter().count(), 0); + }); +} + #[test] fn migration_to_v4_works() { new_test_ext().execute_with(|| { @@ -1054,6 +2153,8 @@ fn test_migrate_origin() { match self { 3u32 => system::RawOrigin::Root.into(), 2u32 => system::RawOrigin::None.into(), + 101u32 => system::RawOrigin::Signed(101).into(), + 102u32 => system::RawOrigin::Signed(102).into(), _ => unreachable!("test make no use of it"), } } diff --git a/substrate/frame/scheduler/src/weights.rs b/substrate/frame/scheduler/src/weights.rs index 58d71186259..9b7e5405a1b 100644 --- a/substrate/frame/scheduler/src/weights.rs +++ b/substrate/frame/scheduler/src/weights.rs @@ -15,32 +15,29 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Autogenerated weights for pallet_scheduler +//! Autogenerated weights for `pallet_scheduler` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-01-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-grjcggob-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/production/substrate +// target/production/substrate-node // benchmark // pallet -// --chain=dev // --steps=50 // --repeat=20 -// --pallet=pallet_scheduler -// --no-storage-info -// --no-median-slopes -// --no-min-squares // --extrinsic=* -// --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --output=./frame/scheduler/src/weights.rs -// --header=./HEADER-APACHE2 -// --template=./.maintain/frame-weight-template.hbs +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_scheduler +// --chain=dev +// --header=./substrate/HEADER-APACHE2 +// --output=./substrate/frame/scheduler/src/weights.rs +// --template=./substrate/.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -50,7 +47,7 @@ use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; use core::marker::PhantomData; -/// Weight functions needed for pallet_scheduler. +/// Weight functions needed for `pallet_scheduler`. pub trait WeightInfo { fn service_agendas_base() -> Weight; fn service_agenda_base(s: u32, ) -> Weight; @@ -64,33 +61,38 @@ pub trait WeightInfo { fn cancel(s: u32, ) -> Weight; fn schedule_named(s: u32, ) -> Weight; fn cancel_named(s: u32, ) -> Weight; + fn schedule_retry(s: u32, ) -> Weight; + fn set_retry() -> Weight; + fn set_retry_named() -> Weight; + fn cancel_retry() -> Weight; + fn cancel_retry_named() -> Weight; } -/// Weights for pallet_scheduler using the Substrate node and recommended hardware. +/// Weights for `pallet_scheduler` using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - /// Storage: Scheduler IncompleteSince (r:1 w:1) - /// Proof: Scheduler IncompleteSince (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: `Scheduler::IncompleteSince` (r:1 w:1) + /// Proof: `Scheduler::IncompleteSince` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn service_agendas_base() -> Weight { // Proof Size summary in bytes: // Measured: `31` // Estimated: `1489` - // Minimum execution time: 3_991_000 picoseconds. - Weight::from_parts(4_174_000, 1489) + // Minimum execution time: 3_040_000 picoseconds. + Weight::from_parts(3_202_000, 1489) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 512]`. fn service_agenda_base(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `81 + s * (177 ±0)` // Estimated: `110487` - // Minimum execution time: 3_581_000 picoseconds. - Weight::from_parts(7_413_174, 110487) - // Standard Error: 971 - .saturating_add(Weight::from_parts(348_077, 0).saturating_mul(s.into())) + // Minimum execution time: 3_462_000 picoseconds. + Weight::from_parts(6_262_125, 110487) + // Standard Error: 536 + .saturating_add(Weight::from_parts(332_570, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -98,145 +100,226 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_250_000 picoseconds. - Weight::from_parts(5_549_000, 0) + // Minimum execution time: 3_425_000 picoseconds. + Weight::from_parts(3_680_000, 0) } - /// Storage: Preimage PreimageFor (r:1 w:1) - /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: Measured) - /// Storage: Preimage StatusFor (r:1 w:1) - /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: `Preimage::PreimageFor` (r:1 w:1) + /// Proof: `Preimage::PreimageFor` (`max_values`: None, `max_size`: Some(4194344), added: 4196819, mode: `Measured`) + /// Storage: `Preimage::StatusFor` (r:1 w:0) + /// Proof: `Preimage::StatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) + /// Storage: `Preimage::RequestStatusFor` (r:1 w:1) + /// Proof: `Preimage::RequestStatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) /// The range of component `s` is `[128, 4194304]`. fn service_task_fetched(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `179 + s * (1 ±0)` - // Estimated: `3644 + s * (1 ±0)` - // Minimum execution time: 20_089_000 picoseconds. - Weight::from_parts(20_376_000, 3644) - // Standard Error: 3 - .saturating_add(Weight::from_parts(1_170, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(2_u64)) + // Measured: `246 + s * (1 ±0)` + // Estimated: `3711 + s * (1 ±0)` + // Minimum execution time: 17_564_000 picoseconds. + Weight::from_parts(17_887_000, 3711) + // Standard Error: 1 + .saturating_add(Weight::from_parts(1_253, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(s.into())) } - /// Storage: Scheduler Lookup (r:0 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn service_task_named() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_998_000 picoseconds. - Weight::from_parts(7_303_000, 0) + // Minimum execution time: 4_934_000 picoseconds. + Weight::from_parts(5_275_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } fn service_task_periodic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_078_000 picoseconds. - Weight::from_parts(5_315_000, 0) + // Minimum execution time: 3_348_000 picoseconds. + Weight::from_parts(3_561_000, 0) } + /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) + /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `TxPause::PausedCalls` (r:1 w:0) + /// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`) fn execute_dispatch_signed() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 2_228_000 picoseconds. - Weight::from_parts(2_352_000, 0) + // Measured: `145` + // Estimated: `3997` + // Minimum execution time: 6_395_000 picoseconds. + Weight::from_parts(6_642_000, 3997) + .saturating_add(T::DbWeight::get().reads(2_u64)) } fn execute_dispatch_unsigned() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_226_000 picoseconds. - Weight::from_parts(2_371_000, 0) + // Minimum execution time: 2_167_000 picoseconds. + Weight::from_parts(2_266_000, 0) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 511]`. fn schedule(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `81 + s * (177 ±0)` // Estimated: `110487` - // Minimum execution time: 12_683_000 picoseconds. - Weight::from_parts(16_951_846, 110487) - // Standard Error: 1_046 - .saturating_add(Weight::from_parts(380_842, 0).saturating_mul(s.into())) + // Minimum execution time: 10_009_000 picoseconds. + Weight::from_parts(13_565_985, 110487) + // Standard Error: 575 + .saturating_add(Weight::from_parts(354_760, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) - /// Storage: Scheduler Lookup (r:0 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// The range of component `s` is `[1, 512]`. fn cancel(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `81 + s * (177 ±0)` // Estimated: `110487` - // Minimum execution time: 16_201_000 picoseconds. - Weight::from_parts(18_259_422, 110487) - // Standard Error: 1_344 - .saturating_add(Weight::from_parts(545_863, 0).saturating_mul(s.into())) + // Minimum execution time: 14_048_000 picoseconds. + Weight::from_parts(15_141_696, 110487) + // Standard Error: 1_082 + .saturating_add(Weight::from_parts(533_390, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } - /// Storage: Scheduler Lookup (r:1 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:1 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 511]`. fn schedule_named(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `596 + s * (178 ±0)` // Estimated: `110487` - // Minimum execution time: 16_180_000 picoseconds. - Weight::from_parts(25_128_925, 110487) - // Standard Error: 1_118 - .saturating_add(Weight::from_parts(375_631, 0).saturating_mul(s.into())) + // Minimum execution time: 12_902_000 picoseconds. + Weight::from_parts(18_957_156, 110487) + // Standard Error: 792 + .saturating_add(Weight::from_parts(361_909, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } - /// Storage: Scheduler Lookup (r:1 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:1 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) /// The range of component `s` is `[1, 512]`. fn cancel_named(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `709 + s * (177 ±0)` // Estimated: `110487` - // Minimum execution time: 18_244_000 picoseconds. - Weight::from_parts(21_439_366, 110487) - // Standard Error: 1_084 - .saturating_add(Weight::from_parts(557_691, 0).saturating_mul(s.into())) + // Minimum execution time: 15_933_000 picoseconds. + Weight::from_parts(18_091_415, 110487) + // Standard Error: 779 + .saturating_add(Weight::from_parts(534_402, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } + /// Storage: `Scheduler::Retries` (r:1 w:2) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 512]`. + fn schedule_retry(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `159` + // Estimated: `110487` + // Minimum execution time: 14_155_000 picoseconds. + Weight::from_parts(16_447_031, 110487) + // Standard Error: 233 + .saturating_add(Weight::from_parts(8_424, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn set_retry() -> Weight { + // Proof Size summary in bytes: + // Measured: `81 + s * (177 ±0)` + // Estimated: `110487` + // Minimum execution time: 8_130_000 picoseconds. + Weight::from_parts(9_047_554, 110487) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:0) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn set_retry_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `647 + s * (178 ±0)` + // Estimated: `110487` + // Minimum execution time: 10_838_000 picoseconds. + Weight::from_parts(12_804_076, 110487) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn cancel_retry() -> Weight { + // Proof Size summary in bytes: + // Measured: `81 + s * (177 ±0)` + // Estimated: `110487` + // Minimum execution time: 8_130_000 picoseconds. + Weight::from_parts(9_047_554, 110487) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:0) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn cancel_retry_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `647 + s * (178 ±0)` + // Estimated: `110487` + // Minimum execution time: 10_838_000 picoseconds. + Weight::from_parts(12_804_076, 110487) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } } -// For backwards compatibility and tests +// For backwards compatibility and tests. impl WeightInfo for () { - /// Storage: Scheduler IncompleteSince (r:1 w:1) - /// Proof: Scheduler IncompleteSince (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: `Scheduler::IncompleteSince` (r:1 w:1) + /// Proof: `Scheduler::IncompleteSince` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn service_agendas_base() -> Weight { // Proof Size summary in bytes: // Measured: `31` // Estimated: `1489` - // Minimum execution time: 3_991_000 picoseconds. - Weight::from_parts(4_174_000, 1489) + // Minimum execution time: 3_040_000 picoseconds. + Weight::from_parts(3_202_000, 1489) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 512]`. fn service_agenda_base(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `81 + s * (177 ±0)` // Estimated: `110487` - // Minimum execution time: 3_581_000 picoseconds. - Weight::from_parts(7_413_174, 110487) - // Standard Error: 971 - .saturating_add(Weight::from_parts(348_077, 0).saturating_mul(s.into())) + // Minimum execution time: 3_462_000 picoseconds. + Weight::from_parts(6_262_125, 110487) + // Standard Error: 536 + .saturating_add(Weight::from_parts(332_570, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -244,117 +327,198 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_250_000 picoseconds. - Weight::from_parts(5_549_000, 0) + // Minimum execution time: 3_425_000 picoseconds. + Weight::from_parts(3_680_000, 0) } - /// Storage: Preimage PreimageFor (r:1 w:1) - /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: Measured) - /// Storage: Preimage StatusFor (r:1 w:1) - /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: `Preimage::PreimageFor` (r:1 w:1) + /// Proof: `Preimage::PreimageFor` (`max_values`: None, `max_size`: Some(4194344), added: 4196819, mode: `Measured`) + /// Storage: `Preimage::StatusFor` (r:1 w:0) + /// Proof: `Preimage::StatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) + /// Storage: `Preimage::RequestStatusFor` (r:1 w:1) + /// Proof: `Preimage::RequestStatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) /// The range of component `s` is `[128, 4194304]`. fn service_task_fetched(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `179 + s * (1 ±0)` - // Estimated: `3644 + s * (1 ±0)` - // Minimum execution time: 20_089_000 picoseconds. - Weight::from_parts(20_376_000, 3644) - // Standard Error: 3 - .saturating_add(Weight::from_parts(1_170, 0).saturating_mul(s.into())) - .saturating_add(RocksDbWeight::get().reads(2_u64)) + // Measured: `246 + s * (1 ±0)` + // Estimated: `3711 + s * (1 ±0)` + // Minimum execution time: 17_564_000 picoseconds. + Weight::from_parts(17_887_000, 3711) + // Standard Error: 1 + .saturating_add(Weight::from_parts(1_253, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(s.into())) } - /// Storage: Scheduler Lookup (r:0 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn service_task_named() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_998_000 picoseconds. - Weight::from_parts(7_303_000, 0) + // Minimum execution time: 4_934_000 picoseconds. + Weight::from_parts(5_275_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } fn service_task_periodic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_078_000 picoseconds. - Weight::from_parts(5_315_000, 0) + // Minimum execution time: 3_348_000 picoseconds. + Weight::from_parts(3_561_000, 0) } + /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) + /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `TxPause::PausedCalls` (r:1 w:0) + /// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`) fn execute_dispatch_signed() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 2_228_000 picoseconds. - Weight::from_parts(2_352_000, 0) + // Measured: `145` + // Estimated: `3997` + // Minimum execution time: 6_395_000 picoseconds. + Weight::from_parts(6_642_000, 3997) + .saturating_add(RocksDbWeight::get().reads(2_u64)) } fn execute_dispatch_unsigned() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_226_000 picoseconds. - Weight::from_parts(2_371_000, 0) + // Minimum execution time: 2_167_000 picoseconds. + Weight::from_parts(2_266_000, 0) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 511]`. fn schedule(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `81 + s * (177 ±0)` // Estimated: `110487` - // Minimum execution time: 12_683_000 picoseconds. - Weight::from_parts(16_951_846, 110487) - // Standard Error: 1_046 - .saturating_add(Weight::from_parts(380_842, 0).saturating_mul(s.into())) + // Minimum execution time: 10_009_000 picoseconds. + Weight::from_parts(13_565_985, 110487) + // Standard Error: 575 + .saturating_add(Weight::from_parts(354_760, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) - /// Storage: Scheduler Lookup (r:0 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// The range of component `s` is `[1, 512]`. fn cancel(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `81 + s * (177 ±0)` // Estimated: `110487` - // Minimum execution time: 16_201_000 picoseconds. - Weight::from_parts(18_259_422, 110487) - // Standard Error: 1_344 - .saturating_add(Weight::from_parts(545_863, 0).saturating_mul(s.into())) + // Minimum execution time: 14_048_000 picoseconds. + Weight::from_parts(15_141_696, 110487) + // Standard Error: 1_082 + .saturating_add(Weight::from_parts(533_390, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } - /// Storage: Scheduler Lookup (r:1 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:1 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 511]`. fn schedule_named(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `596 + s * (178 ±0)` // Estimated: `110487` - // Minimum execution time: 16_180_000 picoseconds. - Weight::from_parts(25_128_925, 110487) - // Standard Error: 1_118 - .saturating_add(Weight::from_parts(375_631, 0).saturating_mul(s.into())) + // Minimum execution time: 12_902_000 picoseconds. + Weight::from_parts(18_957_156, 110487) + // Standard Error: 792 + .saturating_add(Weight::from_parts(361_909, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } - /// Storage: Scheduler Lookup (r:1 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:1 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) /// The range of component `s` is `[1, 512]`. fn cancel_named(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `709 + s * (177 ±0)` // Estimated: `110487` - // Minimum execution time: 18_244_000 picoseconds. - Weight::from_parts(21_439_366, 110487) - // Standard Error: 1_084 - .saturating_add(Weight::from_parts(557_691, 0).saturating_mul(s.into())) + // Minimum execution time: 15_933_000 picoseconds. + Weight::from_parts(18_091_415, 110487) + // Standard Error: 779 + .saturating_add(Weight::from_parts(534_402, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } + /// Storage: `Scheduler::Retries` (r:1 w:2) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 512]`. + fn schedule_retry(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `159` + // Estimated: `110487` + // Minimum execution time: 14_155_000 picoseconds. + Weight::from_parts(16_447_031, 110487) + // Standard Error: 233 + .saturating_add(Weight::from_parts(8_424, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn set_retry() -> Weight { + // Proof Size summary in bytes: + // Measured: `81 + s * (177 ±0)` + // Estimated: `110487` + // Minimum execution time: 8_130_000 picoseconds. + Weight::from_parts(9_047_554, 110487) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:0) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn set_retry_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `647 + s * (178 ±0)` + // Estimated: `110487` + // Minimum execution time: 10_838_000 picoseconds. + Weight::from_parts(12_804_076, 110487) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn cancel_retry() -> Weight { + // Proof Size summary in bytes: + // Measured: `81 + s * (177 ±0)` + // Estimated: `110487` + // Minimum execution time: 8_130_000 picoseconds. + Weight::from_parts(9_047_554, 110487) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:0) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn cancel_retry_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `647 + s * (178 ±0)` + // Estimated: `110487` + // Minimum execution time: 10_838_000 picoseconds. + Weight::from_parts(12_804_076, 110487) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } } -- GitLab From e75e0eb5ee98a42220e390bab9f94f9ac7c49540 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Fri, 16 Feb 2024 12:14:35 +0100 Subject: [PATCH 070/133] Document LocalKeystore insert method (#3336) Refer to https://github.com/paritytech/polkadot-sdk/issues/3320 discussion --- substrate/client/keystore/src/local.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/substrate/client/keystore/src/local.rs b/substrate/client/keystore/src/local.rs index 4a04a48518b..8b922c11cbc 100644 --- a/substrate/client/keystore/src/local.rs +++ b/substrate/client/keystore/src/local.rs @@ -143,6 +143,13 @@ impl LocalKeystore { } impl Keystore for LocalKeystore { + /// Insert a new secret key. + /// + /// WARNING: if the secret keypair has been manually generated using a password + /// (e.g. using methods such as [`sp_core::crypto::Pair::from_phrase`]) then such + /// a password must match the one used to open the keystore via [`LocalKeystore::open`]. + /// If the passwords doesn't match then the inserted key ends up being unusable under + /// the current keystore instance. fn insert( &self, key_type: KeyTypeId, -- GitLab From 4b484e6a84c715ddefa74b6ed59983360bf0e38b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Feb 2024 12:47:28 +0100 Subject: [PATCH 071/133] Bump the known_good_semver group with 6 updates (#3347) Bumps the known_good_semver group with 6 updates: | Package | From | To | | --- | --- | --- | | [serde](https://github.com/serde-rs/serde) | `1.0.195` | `1.0.196` | | [serde_json](https://github.com/serde-rs/json) | `1.0.111` | `1.0.113` | | [clap](https://github.com/clap-rs/clap) | `4.4.18` | `4.5.0` | | [syn](https://github.com/dtolnay/syn) | `2.0.48` | `2.0.49` | | [serde_yaml](https://github.com/dtolnay/serde-yaml) | `0.9.30` | `0.9.31` | | [serde_derive](https://github.com/serde-rs/serde) | `1.0.195` | `1.0.196` | Updates `serde` from 1.0.195 to 1.0.196

Release notes

Sourced from serde's releases.

v1.0.196

  • Improve formatting of "invalid type" error messages involving floats (#2682)
Commits
  • ede9762 Release 1.0.196
  • d438c2d Merge pull request #2682 from dtolnay/decimalpoint
  • bef110b Format Unexpected::Float with decimal point
  • b971ef1 Merge pull request #2681 from dtolnay/workspacedeps
  • 29d9f69 Fix workspace.dependencies default-features future compat warning
  • aecb408 Sort workspace dependencies
  • 1c675ab Merge pull request #2678 from rodoufu/workspaceDependencies
  • dd61963 Adding workspace dependencies
  • 111803a Merge pull request #2673 from Sky9x/msrv-badge
  • 0024f74 Use shields.io's MSRV badges
  • See full diff in compare view

Updates `serde_json` from 1.0.111 to 1.0.113
Release notes

Sourced from serde_json's releases.

v1.0.113

  • Add swap_remove and shift_remove methods on Map (#1109)

v1.0.112

  • Improve formatting of "invalid type" error messages involving floats (#1107)
Commits
  • 09d865b Release 1.0.113
  • 5aeab4e Merge pull request #1109 from serde-rs/remove
  • ca3c2ca Add swap_remove and shift_remove methods on Map
  • 7fece96 Release 1.0.112
  • 6a6d2bb Merge pull request #1107 from serde-rs/unexpectedfloat
  • 83d7bad Format f64 in error messages using ryu
  • 107c2d1 Merge pull request #1106 from serde-rs/invalidvalue
  • 62ca3e4 Handle Unexpected::Unit in Error::invalid_value
  • 296fafb Factor out JSON-specific Display impl for serde::de::Unexpected
  • e56cc69 Merge pull request #1105 from keienWang/master
  • Additional commits viewable in compare view

Updates `clap` from 4.4.18 to 4.5.0
Changelog

Sourced from clap's changelog.

[4.5.0] - 2024-02-08

Compatibility

  • Update MSRV to 1.74
Commits

Updates `syn` from 2.0.48 to 2.0.49
Release notes

Sourced from syn's releases.

2.0.49

  • Improve error location when parsing from an empty string literal using LitStr::parse (#1590)
Commits
  • e64c063 Release 2.0.49
  • 981359c Merge pull request #1590 from dtolnay/streof
  • 51298d4 Improve error location at eof in LitStr::parse
  • 270c633 Update test suite to nightly-2024-02-13
  • dc9cf16 Remove FilterAttrs trait when unused
  • 7dcfac7 Ignore dead_code warning in test
  • 9831844 Update signature of Emitter::emit_diagnostic in nightly-2024-02-07
  • 9e8033f Update test suite to nightly-2024-02-07
  • cb3348c Update test suite to nightly-2024-01-23
  • 15b9dbc Update test suite to nightly-2024-01-19
  • Additional commits viewable in compare view

Updates `serde_yaml` from 0.9.30 to 0.9.31
Release notes

Sourced from serde_yaml's releases.

0.9.31

  • Add swap_remove and shift_remove methods on Mapping (#408)
Commits

Updates `serde_derive` from 1.0.195 to 1.0.196
Release notes

Sourced from serde_derive's releases.

v1.0.196

  • Improve formatting of "invalid type" error messages involving floats (#2682)
Commits
  • ede9762 Release 1.0.196
  • d438c2d Merge pull request #2682 from dtolnay/decimalpoint
  • bef110b Format Unexpected::Float with decimal point
  • b971ef1 Merge pull request #2681 from dtolnay/workspacedeps
  • 29d9f69 Fix workspace.dependencies default-features future compat warning
  • aecb408 Sort workspace dependencies
  • 1c675ab Merge pull request #2678 from rodoufu/workspaceDependencies
  • dd61963 Adding workspace dependencies
  • 111803a Merge pull request #2673 from Sky9x/msrv-badge
  • 0024f74 Use shields.io's MSRV badges
  • See full diff in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 248 +++++++++--------- .../pallets/ethereum-client/Cargo.toml | 8 +- .../pallets/inbound-queue/Cargo.toml | 2 +- .../pallets/outbound-queue/Cargo.toml | 2 +- .../snowbridge/primitives/beacon/Cargo.toml | 2 +- bridges/snowbridge/primitives/core/Cargo.toml | 2 +- .../snowbridge/primitives/ethereum/Cargo.toml | 4 +- .../snowbridge/primitives/router/Cargo.toml | 2 +- .../snowbridge/runtime/test-common/Cargo.toml | 2 +- cumulus/client/cli/Cargo.toml | 2 +- .../relay-chain-rpc-interface/Cargo.toml | 4 +- .../parachain-system/proc-macro/Cargo.toml | 2 +- cumulus/parachain-template/node/Cargo.toml | 6 +- .../pallets/template/Cargo.toml | 2 +- .../bridge-hubs/bridge-hub-rococo/Cargo.toml | 2 +- .../bridge-hubs/bridge-hub-westend/Cargo.toml | 2 +- .../coretime/coretime-rococo/Cargo.toml | 2 +- .../coretime/coretime-westend/Cargo.toml | 2 +- .../runtimes/people/people-rococo/Cargo.toml | 2 +- .../runtimes/people/people-westend/Cargo.toml | 2 +- cumulus/polkadot-parachain/Cargo.toml | 6 +- cumulus/test/service/Cargo.toml | 6 +- polkadot/cli/Cargo.toml | 2 +- polkadot/node/gum/proc-macro/Cargo.toml | 2 +- polkadot/node/malus/Cargo.toml | 2 +- polkadot/node/primitives/Cargo.toml | 2 +- polkadot/node/service/Cargo.toml | 4 +- polkadot/node/subsystem-bench/Cargo.toml | 4 +- polkadot/node/test/service/Cargo.toml | 2 +- .../node/zombienet-backchannel/Cargo.toml | 2 +- polkadot/parachain/Cargo.toml | 2 +- .../test-parachains/adder/collator/Cargo.toml | 2 +- .../undying/collator/Cargo.toml | 2 +- polkadot/primitives/Cargo.toml | 2 +- polkadot/runtime/common/Cargo.toml | 4 +- polkadot/runtime/parachains/Cargo.toml | 4 +- polkadot/runtime/rococo/Cargo.toml | 4 +- polkadot/runtime/test-runtime/Cargo.toml | 4 +- polkadot/runtime/westend/Cargo.toml | 4 +- polkadot/utils/generate-bags/Cargo.toml | 2 +- .../remote-ext-tests/bags-list/Cargo.toml | 2 +- polkadot/xcm/Cargo.toml | 2 +- polkadot/xcm/pallet-xcm/Cargo.toml | 2 +- polkadot/xcm/procedural/Cargo.toml | 2 +- substrate/bin/minimal/node/Cargo.toml | 4 +- substrate/bin/node-template/node/Cargo.toml | 4 +- .../bin/node-template/runtime/Cargo.toml | 2 +- substrate/bin/node/bench/Cargo.toml | 6 +- substrate/bin/node/cli/Cargo.toml | 10 +- substrate/bin/node/inspect/Cargo.toml | 2 +- substrate/bin/node/runtime/Cargo.toml | 2 +- .../bin/utils/chain-spec-builder/Cargo.toml | 4 +- substrate/bin/utils/subkey/Cargo.toml | 2 +- substrate/client/chain-spec/Cargo.toml | 4 +- substrate/client/chain-spec/derive/Cargo.toml | 2 +- substrate/client/cli/Cargo.toml | 6 +- .../client/consensus/babe/rpc/Cargo.toml | 4 +- substrate/client/consensus/beefy/Cargo.toml | 2 +- .../client/consensus/beefy/rpc/Cargo.toml | 4 +- substrate/client/consensus/grandpa/Cargo.toml | 4 +- .../client/consensus/grandpa/rpc/Cargo.toml | 2 +- substrate/client/keystore/Cargo.toml | 2 +- .../merkle-mountain-range/rpc/Cargo.toml | 4 +- substrate/client/network/Cargo.toml | 4 +- substrate/client/rpc-api/Cargo.toml | 4 +- substrate/client/rpc-servers/Cargo.toml | 2 +- substrate/client/rpc-spec-v2/Cargo.toml | 2 +- substrate/client/rpc/Cargo.toml | 2 +- substrate/client/service/Cargo.toml | 4 +- substrate/client/storage-monitor/Cargo.toml | 2 +- substrate/client/sync-state-rpc/Cargo.toml | 4 +- substrate/client/sysinfo/Cargo.toml | 4 +- substrate/client/telemetry/Cargo.toml | 4 +- substrate/client/tracing/Cargo.toml | 2 +- .../client/tracing/proc-macro/Cargo.toml | 2 +- substrate/client/transaction-pool/Cargo.toml | 2 +- .../client/transaction-pool/api/Cargo.toml | 4 +- substrate/frame/beefy-mmr/Cargo.toml | 2 +- substrate/frame/beefy/Cargo.toml | 2 +- substrate/frame/benchmarking/Cargo.toml | 2 +- .../frame/contracts/proc-macro/Cargo.toml | 2 +- substrate/frame/conviction-voting/Cargo.toml | 2 +- substrate/frame/democracy/Cargo.toml | 2 +- .../solution-type/Cargo.toml | 2 +- .../solution-type/fuzzer/Cargo.toml | 2 +- substrate/frame/message-queue/Cargo.toml | 2 +- substrate/frame/mixnet/Cargo.toml | 2 +- substrate/frame/offences/Cargo.toml | 2 +- substrate/frame/parameters/Cargo.toml | 2 +- substrate/frame/referenda/Cargo.toml | 2 +- substrate/frame/remark/Cargo.toml | 2 +- substrate/frame/staking/Cargo.toml | 2 +- .../frame/staking/reward-curve/Cargo.toml | 2 +- .../frame/state-trie-migration/Cargo.toml | 2 +- substrate/frame/support/Cargo.toml | 4 +- substrate/frame/support/procedural/Cargo.toml | 2 +- .../frame/support/procedural/tools/Cargo.toml | 2 +- .../procedural/tools/derive/Cargo.toml | 2 +- substrate/frame/support/test/Cargo.toml | 2 +- .../frame/support/test/pallet/Cargo.toml | 2 +- substrate/frame/system/Cargo.toml | 2 +- substrate/frame/tips/Cargo.toml | 2 +- .../frame/transaction-payment/Cargo.toml | 4 +- .../asset-tx-payment/Cargo.toml | 4 +- .../frame/transaction-storage/Cargo.toml | 2 +- substrate/frame/treasury/Cargo.toml | 2 +- .../primitives/api/proc-macro/Cargo.toml | 2 +- .../primitives/application-crypto/Cargo.toml | 2 +- substrate/primitives/arithmetic/Cargo.toml | 2 +- .../primitives/consensus/babe/Cargo.toml | 2 +- .../primitives/consensus/beefy/Cargo.toml | 2 +- .../primitives/consensus/grandpa/Cargo.toml | 2 +- .../primitives/consensus/sassafras/Cargo.toml | 2 +- substrate/primitives/core/Cargo.toml | 4 +- .../crypto/hashing/proc-macro/Cargo.toml | 2 +- substrate/primitives/debug-derive/Cargo.toml | 2 +- .../primitives/genesis-builder/Cargo.toml | 2 +- .../merkle-mountain-range/Cargo.toml | 2 +- .../primitives/npos-elections/Cargo.toml | 2 +- .../npos-elections/fuzzer/Cargo.toml | 2 +- substrate/primitives/rpc/Cargo.toml | 4 +- .../runtime-interface/proc-macro/Cargo.toml | 2 +- substrate/primitives/runtime/Cargo.toml | 4 +- substrate/primitives/staking/Cargo.toml | 2 +- substrate/primitives/storage/Cargo.toml | 2 +- .../primitives/test-primitives/Cargo.toml | 2 +- substrate/primitives/version/Cargo.toml | 2 +- .../primitives/version/proc-macro/Cargo.toml | 2 +- substrate/primitives/weights/Cargo.toml | 2 +- .../ci/node-template-release/Cargo.toml | 2 +- substrate/test-utils/client/Cargo.toml | 4 +- substrate/test-utils/runtime/Cargo.toml | 4 +- .../utils/frame/benchmarking-cli/Cargo.toml | 6 +- .../frame/frame-utilities-cli/Cargo.toml | 2 +- .../generate-bags/node-runtime/Cargo.toml | 2 +- .../frame/remote-externalities/Cargo.toml | 2 +- .../rpc/state-trie-migration-rpc/Cargo.toml | 2 +- .../utils/frame/try-runtime/cli/Cargo.toml | 6 +- 138 files changed, 317 insertions(+), 311 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ac4725dd483..32b8217bba6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -191,7 +191,7 @@ checksum = "c0391754c09fab4eae3404d19d0d297aa1c670c1775ab51d8a5312afeca23157" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -206,7 +206,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", "syn-solidity", "tiny-keccak", ] @@ -333,7 +333,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -1218,7 +1218,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -1235,7 +1235,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -1417,7 +1417,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -2602,19 +2602,19 @@ dependencies = [ "clap_lex 0.2.4", "indexmap 1.9.3", "once_cell", - "strsim", + "strsim 0.10.0", "termcolor", "textwrap", ] [[package]] name = "clap" -version = "4.4.18" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" +checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f" dependencies = [ "clap_builder", - "clap_derive 4.4.7", + "clap_derive 4.5.0", ] [[package]] @@ -2628,14 +2628,14 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.18" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" +checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99" dependencies = [ "anstream", "anstyle", - "clap_lex 0.6.0", - "strsim", + "clap_lex 0.7.0", + "strsim 0.11.0", "terminal_size", ] @@ -2645,7 +2645,7 @@ version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "586a385f7ef2f8b4d86bddaa0c094794e7ccbfe5ffef1f434fe928143fc783a5" dependencies = [ - "clap 4.4.18", + "clap 4.5.0", ] [[package]] @@ -2663,14 +2663,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -2684,9 +2684,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "coarsetime" @@ -3382,7 +3382,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.4.18", + "clap 4.5.0", "criterion-plot", "futures", "is-terminal", @@ -3545,7 +3545,7 @@ dependencies = [ name = "cumulus-client-cli" version = "0.7.0" dependencies = [ - "clap 4.4.18", + "clap 4.5.0", "parity-scale-codec", "sc-chain-spec", "sc-cli", @@ -3903,7 +3903,7 @@ dependencies = [ "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -4286,7 +4286,7 @@ name = "cumulus-test-service" version = "0.1.0" dependencies = [ "async-trait", - "clap 4.4.18", + "clap 4.5.0", "criterion 0.5.1", "cumulus-client-cli", "cumulus-client-consensus-common", @@ -4409,7 +4409,7 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -4449,7 +4449,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -4466,7 +4466,7 @@ checksum = "50c49547d73ba8dcfd4ad7325d64c6d5391ff4224d498fc39a6f3f49825a530d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -4674,7 +4674,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -4735,7 +4735,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.48", + "syn 2.0.49", "termcolor", "toml 0.8.8", "walkdir", @@ -4960,7 +4960,7 @@ checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -4971,7 +4971,7 @@ checksum = "c2ad8cef1d801a4686bfd8919f0b30eac4c8e48968c437a6405ded4fb5272d2b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -5161,7 +5161,7 @@ dependencies = [ "fs-err", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -5488,7 +5488,7 @@ dependencies = [ "Inflector", "array-bytes 6.1.0", "chrono", - "clap 4.4.18", + "clap 4.5.0", "comfy-table", "frame-benchmarking", "frame-support", @@ -5554,7 +5554,7 @@ dependencies = [ "quote", "scale-info", "sp-arithmetic", - "syn 2.0.48", + "syn 2.0.49", "trybuild", ] @@ -5580,7 +5580,7 @@ dependencies = [ name = "frame-election-solution-type-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.4.18", + "clap 4.5.0", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-support", @@ -5709,7 +5709,7 @@ dependencies = [ "quote", "regex", "sp-crypto-hashing", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -5720,7 +5720,7 @@ dependencies = [ "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -5729,7 +5729,7 @@ version = "11.0.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -5962,7 +5962,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -6212,7 +6212,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.0.0", + "indexmap 2.2.3", "slab", "tokio", "tokio-util", @@ -6643,9 +6643,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.0" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -7876,7 +7876,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -7890,7 +7890,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -7901,7 +7901,7 @@ checksum = "9ea73aa640dc01d62a590d48c0c3521ed739d53b27f919b25c3551e233481654" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -7912,7 +7912,7 @@ checksum = "ef9d79ae96aaba821963320eb2b6e34d17df1e5a83d8a1985c29cc5be59577b3" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -8081,7 +8081,7 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" name = "minimal-node" version = "4.0.0-dev" dependencies = [ - "clap 4.4.18", + "clap 4.5.0", "frame", "futures", "futures-timer", @@ -8544,7 +8544,7 @@ name = "node-bench" version = "0.9.0-dev" dependencies = [ "array-bytes 6.1.0", - "clap 4.4.18", + "clap 4.5.0", "derive_more", "fs_extra", "futures", @@ -8621,7 +8621,7 @@ dependencies = [ name = "node-runtime-generate-bags" version = "3.0.0" dependencies = [ - "clap 4.4.18", + "clap 4.5.0", "generate-bags", "kitchensink-runtime", ] @@ -8630,7 +8630,7 @@ dependencies = [ name = "node-template" version = "4.0.0-dev" dependencies = [ - "clap 4.4.18", + "clap 4.5.0", "frame-benchmarking", "frame-benchmarking-cli", "frame-system", @@ -8674,7 +8674,7 @@ dependencies = [ name = "node-template-release" version = "3.0.0" dependencies = [ - "clap 4.4.18", + "clap 4.5.0", "flate2", "fs_extra", "glob", @@ -8992,7 +8992,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eedb646674596266dc9bb2b5c7eea7c36b32ecc7777eba0d510196972d72c4fd" dependencies = [ "expander 2.0.0", - "indexmap 2.0.0", + "indexmap 2.2.3", "itertools 0.11.0", "petgraph", "proc-macro-crate 1.3.1", @@ -9657,7 +9657,7 @@ version = "18.0.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -10832,7 +10832,7 @@ dependencies = [ "proc-macro2", "quote", "sp-runtime", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -11238,7 +11238,7 @@ dependencies = [ name = "parachain-template-node" version = "0.1.0" dependencies = [ - "clap 4.4.18", + "clap 4.5.0", "color-print", "cumulus-client-cli", "cumulus-client-collator", @@ -11912,7 +11912,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -11933,7 +11933,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 2.0.0", + "indexmap 2.2.3", ] [[package]] @@ -11953,7 +11953,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -12166,7 +12166,7 @@ name = "polkadot-cli" version = "7.0.0" dependencies = [ "cfg-if", - "clap 4.4.18", + "clap 4.5.0", "frame-benchmarking-cli", "futures", "log", @@ -12243,7 +12243,7 @@ dependencies = [ "fatality", "futures", "futures-timer", - "indexmap 2.0.0", + "indexmap 2.2.3", "lazy_static", "parity-scale-codec", "polkadot-erasure-coding", @@ -13010,7 +13010,7 @@ dependencies = [ "async-trait", "bridge-hub-rococo-runtime", "bridge-hub-westend-runtime", - "clap 4.4.18", + "clap 4.5.0", "collectives-westend-runtime", "color-print", "contracts-rococo-runtime", @@ -13486,7 +13486,7 @@ dependencies = [ "fatality", "futures", "futures-timer", - "indexmap 2.0.0", + "indexmap 2.2.3", "parity-scale-codec", "polkadot-node-network-protocol", "polkadot-node-primitives", @@ -13526,7 +13526,7 @@ dependencies = [ "async-trait", "bincode", "bitvec", - "clap 4.4.18", + "clap 4.5.0", "clap-num", "color-eyre", "colored", @@ -13620,7 +13620,7 @@ version = "1.0.0" dependencies = [ "assert_matches", "async-trait", - "clap 4.4.18", + "clap 4.5.0", "color-eyre", "futures", "futures-timer", @@ -13767,7 +13767,7 @@ dependencies = [ name = "polkadot-voter-bags" version = "7.0.0" dependencies = [ - "clap 4.4.18", + "clap 4.5.0", "generate-bags", "sp-io", "westend-runtime", @@ -13792,7 +13792,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6380dbe1fb03ecc74ad55d841cfc75480222d153ba69ddcb00977866cbdabdb8" dependencies = [ "polkavm-derive-impl 0.5.0", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -13813,7 +13813,7 @@ dependencies = [ "polkavm-common 0.5.0", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -13825,7 +13825,7 @@ dependencies = [ "polkavm-common 0.8.0", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -13835,7 +13835,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15e85319a0d5129dc9f021c62607e0804f5fb777a05cdda44d750ac0732def66" dependencies = [ "polkavm-derive-impl 0.8.0", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -14040,7 +14040,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62" dependencies = [ "proc-macro2", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -14131,7 +14131,7 @@ checksum = "9b698b0b09d40e9b7c1a47b132d66a8b54bcd20583d9b6d06e4535e383b4405c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -14203,7 +14203,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -14303,7 +14303,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -14621,7 +14621,7 @@ checksum = "7f7473c2cfcf90008193dd0e3e16599455cb601a9fce322b5bb55de799664925" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -14703,7 +14703,7 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" name = "remote-ext-tests-bags-list" version = "1.0.0" dependencies = [ - "clap 4.4.18", + "clap 4.5.0", "frame-system", "log", "pallet-bags-list-remote-tests", @@ -15531,7 +15531,7 @@ dependencies = [ "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -15541,7 +15541,7 @@ dependencies = [ "array-bytes 6.1.0", "bip39", "chrono", - "clap 4.4.18", + "clap 4.5.0", "fdlimit", "futures", "futures-timer", @@ -16698,7 +16698,7 @@ dependencies = [ name = "sc-storage-monitor" version = "0.16.0" dependencies = [ - "clap 4.4.18", + "clap 4.5.0", "fs4", "log", "sp-core", @@ -16800,7 +16800,7 @@ dependencies = [ "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -17184,9 +17184,9 @@ checksum = "f97841a747eef040fcd2e7b3b9a220a7205926e60488e673d9e4926d27772ce5" [[package]] name = "serde" -version = "1.0.195" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" dependencies = [ "serde_derive", ] @@ -17211,13 +17211,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.195" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -17242,9 +17242,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.111" +version = "1.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" +checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" dependencies = [ "itoa", "ryu", @@ -17274,11 +17274,11 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.30" +version = "0.9.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1bf28c79a99f70ee1f1d83d10c875d2e70618417fda01ad1785e027579d9d38" +checksum = "adf8a49373e98a4c5f0ceb5d05aa7c648d75f63774981ed95b7c7443bbd50c6e" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.2.3", "itoa", "ryu", "serde", @@ -17307,7 +17307,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -18146,7 +18146,7 @@ dependencies = [ "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -18539,7 +18539,7 @@ version = "0.0.0" dependencies = [ "quote", "sp-crypto-hashing", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -18557,7 +18557,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf5 dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -18566,7 +18566,7 @@ version = "14.0.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -18726,7 +18726,7 @@ dependencies = [ name = "sp-npos-elections-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.4.18", + "clap 4.5.0", "honggfuzz", "rand", "sp-npos-elections", @@ -18841,7 +18841,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -18853,7 +18853,7 @@ dependencies = [ "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -19125,7 +19125,7 @@ dependencies = [ "proc-macro2", "quote", "sp-version", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -19249,7 +19249,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" name = "staging-chain-spec-builder" version = "2.0.0" dependencies = [ - "clap 4.4.18", + "clap 4.5.0", "log", "sc-chain-spec", "serde_json", @@ -19262,7 +19262,7 @@ version = "3.0.0-dev" dependencies = [ "array-bytes 6.1.0", "assert_cmd", - "clap 4.4.18", + "clap 4.5.0", "clap_complete", "criterion 0.4.0", "frame-benchmarking", @@ -19372,7 +19372,7 @@ dependencies = [ name = "staging-node-inspect" version = "0.12.0" dependencies = [ - "clap 4.4.18", + "clap 4.5.0", "parity-scale-codec", "sc-cli", "sc-client-api", @@ -19526,6 +19526,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + [[package]] name = "strum" version = "0.24.1" @@ -19564,14 +19570,14 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] name = "subkey" version = "9.0.0" dependencies = [ - "clap 4.4.18", + "clap 4.5.0", "sc-cli", ] @@ -19613,7 +19619,7 @@ dependencies = [ name = "substrate-frame-cli" version = "32.0.0" dependencies = [ - "clap 4.4.18", + "clap 4.5.0", "frame-support", "frame-system", "sc-cli", @@ -19962,9 +19968,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496" dependencies = [ "proc-macro2", "quote", @@ -19980,7 +19986,7 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -20094,7 +20100,7 @@ dependencies = [ name = "test-parachain-adder-collator" version = "1.0.0" dependencies = [ - "clap 4.4.18", + "clap 4.5.0", "futures", "futures-timer", "log", @@ -20142,7 +20148,7 @@ dependencies = [ name = "test-parachain-undying-collator" version = "1.0.0" dependencies = [ - "clap 4.4.18", + "clap 4.5.0", "futures", "futures-timer", "log", @@ -20245,7 +20251,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -20406,7 +20412,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -20529,7 +20535,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.2.3", "toml_datetime", "winnow", ] @@ -20540,7 +20546,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.2.3", "serde", "serde_spanned", "toml_datetime", @@ -20613,7 +20619,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -20655,7 +20661,7 @@ dependencies = [ "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -20837,7 +20843,7 @@ version = "0.38.0" dependencies = [ "assert_cmd", "async-trait", - "clap 4.4.18", + "clap 4.5.0", "frame-remote-externalities", "frame-try-runtime", "hex", @@ -21220,7 +21226,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", "wasm-bindgen-shared", ] @@ -21254,7 +21260,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -22269,7 +22275,7 @@ dependencies = [ "proc-macro2", "quote", "staging-xcm", - "syn 2.0.48", + "syn 2.0.49", "trybuild", ] @@ -22391,7 +22397,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -22411,7 +22417,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] diff --git a/bridges/snowbridge/pallets/ethereum-client/Cargo.toml b/bridges/snowbridge/pallets/ethereum-client/Cargo.toml index dd7051fe3f0..541ded26737 100644 --- a/bridges/snowbridge/pallets/ethereum-client/Cargo.toml +++ b/bridges/snowbridge/pallets/ethereum-client/Cargo.toml @@ -15,8 +15,8 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.195", optional = true } -serde_json = { version = "1.0.111", optional = true } +serde = { version = "1.0.196", optional = true } +serde_json = { version = "1.0.113", optional = true } codec = { version = "3.6.1", package = "parity-scale-codec", default-features = false, features = ["derive"] } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } ssz_rs = { version = "0.9.0", default-features = false } @@ -45,12 +45,12 @@ pallet-timestamp = { path = "../../../../substrate/frame/timestamp", default-fea [dev-dependencies] rand = "0.8.5" sp-keyring = { path = "../../../../substrate/primitives/keyring" } -serde_json = "1.0.111" +serde_json = "1.0.113" hex-literal = "0.4.1" pallet-timestamp = { path = "../../../../substrate/frame/timestamp" } snowbridge-pallet-ethereum-client-fixtures = { path = "./fixtures" } sp-io = { path = "../../../../substrate/primitives/io" } -serde = "1.0.195" +serde = "1.0.196" [features] default = ["std"] diff --git a/bridges/snowbridge/pallets/inbound-queue/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue/Cargo.toml index 8756982ed7e..09653fa3b48 100644 --- a/bridges/snowbridge/pallets/inbound-queue/Cargo.toml +++ b/bridges/snowbridge/pallets/inbound-queue/Cargo.toml @@ -15,7 +15,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.195", optional = true } +serde = { version = "1.0.196", optional = true } codec = { version = "3.6.1", package = "parity-scale-codec", default-features = false, features = ["derive"] } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1", optional = true } diff --git a/bridges/snowbridge/pallets/outbound-queue/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue/Cargo.toml index ae52fb3e5c4..0956ac76678 100644 --- a/bridges/snowbridge/pallets/outbound-queue/Cargo.toml +++ b/bridges/snowbridge/pallets/outbound-queue/Cargo.toml @@ -15,7 +15,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.195", features = ["alloc", "derive"], default-features = false } +serde = { version = "1.0.196", features = ["alloc", "derive"], default-features = false } codec = { version = "3.6.1", package = "parity-scale-codec", default-features = false, features = ["derive"] } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1", optional = true } diff --git a/bridges/snowbridge/primitives/beacon/Cargo.toml b/bridges/snowbridge/primitives/beacon/Cargo.toml index b59e5191b41..4f5213b3422 100644 --- a/bridges/snowbridge/primitives/beacon/Cargo.toml +++ b/bridges/snowbridge/primitives/beacon/Cargo.toml @@ -12,7 +12,7 @@ categories = ["cryptography::cryptocurrencies"] workspace = true [dependencies] -serde = { version = "1.0.195", optional = true, features = ["derive"] } +serde = { version = "1.0.196", optional = true, features = ["derive"] } hex = { version = "0.4", default-features = false } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } diff --git a/bridges/snowbridge/primitives/core/Cargo.toml b/bridges/snowbridge/primitives/core/Cargo.toml index f735d01a848..460c6c3442e 100644 --- a/bridges/snowbridge/primitives/core/Cargo.toml +++ b/bridges/snowbridge/primitives/core/Cargo.toml @@ -12,7 +12,7 @@ categories = ["cryptography::cryptocurrencies"] workspace = true [dependencies] -serde = { version = "1.0.195", optional = true, features = ["alloc", "derive"], default-features = false } +serde = { version = "1.0.196", optional = true, features = ["alloc", "derive"], default-features = false } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1" } diff --git a/bridges/snowbridge/primitives/ethereum/Cargo.toml b/bridges/snowbridge/primitives/ethereum/Cargo.toml index 047ebdd5669..a9e2fe65421 100644 --- a/bridges/snowbridge/primitives/ethereum/Cargo.toml +++ b/bridges/snowbridge/primitives/ethereum/Cargo.toml @@ -12,7 +12,7 @@ categories = ["cryptography::cryptocurrencies"] workspace = true [dependencies] -serde = { version = "1.0.195", optional = true, features = ["derive"] } +serde = { version = "1.0.196", optional = true, features = ["derive"] } serde-big-array = { version = "0.3.2", optional = true, features = ["const-generics"] } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } @@ -33,7 +33,7 @@ ethabi = { package = "ethabi-decode", version = "1.0.0", default-features = fals [dev-dependencies] wasm-bindgen-test = "0.3.19" rand = "0.8.5" -serde_json = "1.0.111" +serde_json = "1.0.113" [features] default = ["std"] diff --git a/bridges/snowbridge/primitives/router/Cargo.toml b/bridges/snowbridge/primitives/router/Cargo.toml index 3dbf5483e1e..c4fc688a946 100644 --- a/bridges/snowbridge/primitives/router/Cargo.toml +++ b/bridges/snowbridge/primitives/router/Cargo.toml @@ -12,7 +12,7 @@ categories = ["cryptography::cryptocurrencies"] workspace = true [dependencies] -serde = { version = "1.0.195", optional = true, features = ["derive"] } +serde = { version = "1.0.196", optional = true, features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } log = { workspace = true } diff --git a/bridges/snowbridge/runtime/test-common/Cargo.toml b/bridges/snowbridge/runtime/test-common/Cargo.toml index ecf0a6fcc69..868f72b7b86 100644 --- a/bridges/snowbridge/runtime/test-common/Cargo.toml +++ b/bridges/snowbridge/runtime/test-common/Cargo.toml @@ -15,7 +15,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = hex-literal = { version = "0.4.1" } log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true, features = ["derive"] } +serde = { version = "1.0.196", optional = true, features = ["derive"] } smallvec = "1.11.0" # Substrate diff --git a/cumulus/client/cli/Cargo.toml b/cumulus/client/cli/Cargo.toml index 0495eab9bd5..b6d3e7a7442 100644 --- a/cumulus/client/cli/Cargo.toml +++ b/cumulus/client/cli/Cargo.toml @@ -10,7 +10,7 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0" workspace = true [dependencies] -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.0", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0" } url = "2.4.0" diff --git a/cumulus/client/relay-chain-rpc-interface/Cargo.toml b/cumulus/client/relay-chain-rpc-interface/Cargo.toml index fc89cdd5a9e..1534537e0d4 100644 --- a/cumulus/client/relay-chain-rpc-interface/Cargo.toml +++ b/cumulus/client/relay-chain-rpc-interface/Cargo.toml @@ -37,8 +37,8 @@ jsonrpsee = { version = "0.22", features = ["ws-client"] } tracing = "0.1.37" async-trait = "0.1.74" url = "2.4.0" -serde_json = "1.0.111" -serde = "1.0.195" +serde_json = "1.0.113" +serde = "1.0.196" schnellru = "0.2.1" smoldot = { version = "0.11.0", default_features = false, features = ["std"] } smoldot-light = { version = "0.9.0", default_features = false, features = ["std"] } diff --git a/cumulus/pallets/parachain-system/proc-macro/Cargo.toml b/cumulus/pallets/parachain-system/proc-macro/Cargo.toml index 5f41e6423ac..51737452739 100644 --- a/cumulus/pallets/parachain-system/proc-macro/Cargo.toml +++ b/cumulus/pallets/parachain-system/proc-macro/Cargo.toml @@ -13,7 +13,7 @@ workspace = true proc-macro = true [dependencies] -syn = "2.0.48" +syn = "2.0.49" proc-macro2 = "1.0.64" quote = "1.0.33" proc-macro-crate = "3.0.0" diff --git a/cumulus/parachain-template/node/Cargo.toml b/cumulus/parachain-template/node/Cargo.toml index fce5bd8f075..7ee0b4e8161 100644 --- a/cumulus/parachain-template/node/Cargo.toml +++ b/cumulus/parachain-template/node/Cargo.toml @@ -14,13 +14,13 @@ publish = false workspace = true [dependencies] -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.0", features = ["derive"] } log = { workspace = true, default-features = true } codec = { package = "parity-scale-codec", version = "3.0.0" } -serde = { version = "1.0.195", features = ["derive"] } +serde = { version = "1.0.196", features = ["derive"] } jsonrpsee = { version = "0.22", features = ["server"] } futures = "0.3.28" -serde_json = "1.0.111" +serde_json = "1.0.113" # Local parachain-template-runtime = { path = "../runtime" } diff --git a/cumulus/parachain-template/pallets/template/Cargo.toml b/cumulus/parachain-template/pallets/template/Cargo.toml index 2318375fec9..bd9d21d2dd2 100644 --- a/cumulus/parachain-template/pallets/template/Cargo.toml +++ b/cumulus/parachain-template/pallets/template/Cargo.toml @@ -24,7 +24,7 @@ frame-support = { path = "../../../../substrate/frame/support", default-features frame-system = { path = "../../../../substrate/frame/system", default-features = false } [dev-dependencies] -serde = { version = "1.0.195" } +serde = { version = "1.0.196" } # Substrate sp-core = { path = "../../../../substrate/primitives/core", default-features = false } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index 88fb56a2f0b..02e51782700 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -21,7 +21,7 @@ log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = [ "derive", ] } -serde = { version = "1.0.195", optional = true, features = ["derive"] } +serde = { version = "1.0.196", optional = true, features = ["derive"] } # Substrate frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml index 4ce77f8060c..6015251a238 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -17,7 +17,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = hex-literal = { version = "0.4.1" } log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true, features = ["derive"] } +serde = { version = "1.0.196", optional = true, features = ["derive"] } # Substrate frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml index ef9869d727a..60b8ef54f06 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml @@ -17,7 +17,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = hex-literal = "0.4.1" log = { workspace = true } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true, features = ["derive"] } +serde = { version = "1.0.196", optional = true, features = ["derive"] } # Substrate frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml index aae194f9ad8..a34521e9ee8 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml @@ -17,7 +17,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = hex-literal = "0.4.1" log = { workspace = true } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true, features = ["derive"] } +serde = { version = "1.0.196", optional = true, features = ["derive"] } # Substrate frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } diff --git a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml index b44d40c7013..8ff031b3e33 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml @@ -15,7 +15,7 @@ enumflags2 = { version = "0.7.7" } hex-literal = { version = "0.4.1" } log = { workspace = true } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true, features = ["derive"] } +serde = { version = "1.0.196", optional = true, features = ["derive"] } # Substrate frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } diff --git a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml index 7295b22f5a3..a08b9702160 100644 --- a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml @@ -15,7 +15,7 @@ enumflags2 = { version = "0.7.7" } hex-literal = { version = "0.4.1" } log = { workspace = true } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true, features = ["derive"] } +serde = { version = "1.0.196", optional = true, features = ["derive"] } # Substrate frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } diff --git a/cumulus/polkadot-parachain/Cargo.toml b/cumulus/polkadot-parachain/Cargo.toml index 9905ec86f47..bbd8f9d409e 100644 --- a/cumulus/polkadot-parachain/Cargo.toml +++ b/cumulus/polkadot-parachain/Cargo.toml @@ -16,13 +16,13 @@ path = "src/main.rs" [dependencies] async-trait = "0.1.74" -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.0", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.28" hex-literal = "0.4.1" log = { workspace = true, default-features = true } -serde = { version = "1.0.195", features = ["derive"] } -serde_json = "1.0.111" +serde = { version = "1.0.196", features = ["derive"] } +serde_json = "1.0.113" # Local rococo-parachain-runtime = { path = "../parachains/runtimes/testing/rococo-parachain" } diff --git a/cumulus/test/service/Cargo.toml b/cumulus/test/service/Cargo.toml index b710146ffe8..04119c4c9ee 100644 --- a/cumulus/test/service/Cargo.toml +++ b/cumulus/test/service/Cargo.toml @@ -14,13 +14,13 @@ path = "src/main.rs" [dependencies] async-trait = "0.1.74" -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.0", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0" } criterion = { version = "0.5.1", features = ["async_tokio"] } jsonrpsee = { version = "0.22", features = ["server"] } rand = "0.8.5" -serde = { version = "1.0.195", features = ["derive"] } -serde_json = "1.0.111" +serde = { version = "1.0.196", features = ["derive"] } +serde_json = "1.0.113" tokio = { version = "1.32.0", features = ["macros"] } tracing = "0.1.37" url = "2.4.0" diff --git a/polkadot/cli/Cargo.toml b/polkadot/cli/Cargo.toml index c5d9cb45baa..3f085e18765 100644 --- a/polkadot/cli/Cargo.toml +++ b/polkadot/cli/Cargo.toml @@ -19,7 +19,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] cfg-if = "1.0" -clap = { version = "4.4.18", features = ["derive"], optional = true } +clap = { version = "4.5.0", features = ["derive"], optional = true } log = { workspace = true, default-features = true } thiserror = "1.0.48" futures = "0.3.21" diff --git a/polkadot/node/gum/proc-macro/Cargo.toml b/polkadot/node/gum/proc-macro/Cargo.toml index 6fb38653e73..4c81c8314df 100644 --- a/polkadot/node/gum/proc-macro/Cargo.toml +++ b/polkadot/node/gum/proc-macro/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -syn = { version = "2.0.48", features = ["extra-traits", "full"] } +syn = { version = "2.0.49", features = ["extra-traits", "full"] } quote = "1.0.28" proc-macro2 = "1.0.56" proc-macro-crate = "3.0.0" diff --git a/polkadot/node/malus/Cargo.toml b/polkadot/node/malus/Cargo.toml index 6a3dff726ed..69a1ffac111 100644 --- a/polkadot/node/malus/Cargo.toml +++ b/polkadot/node/malus/Cargo.toml @@ -43,7 +43,7 @@ assert_matches = "1.5" async-trait = "0.1.74" sp-keystore = { path = "../../../substrate/primitives/keystore" } sp-core = { path = "../../../substrate/primitives/core" } -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.0", features = ["derive"] } futures = "0.3.21" futures-timer = "3.0.2" gum = { package = "tracing-gum", path = "../gum" } diff --git a/polkadot/node/primitives/Cargo.toml b/polkadot/node/primitives/Cargo.toml index 157594a5099..b5560929b2e 100644 --- a/polkadot/node/primitives/Cargo.toml +++ b/polkadot/node/primitives/Cargo.toml @@ -24,7 +24,7 @@ polkadot-parachain-primitives = { path = "../../parachain", default-features = f schnorrkel = "0.11.4" thiserror = "1.0.48" bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } -serde = { version = "1.0.195", features = ["derive"] } +serde = { version = "1.0.196", features = ["derive"] } [target.'cfg(not(target_os = "unknown"))'.dependencies] zstd = { version = "0.12.4", default-features = false } diff --git a/polkadot/node/service/Cargo.toml b/polkadot/node/service/Cargo.toml index 5b73d420e3b..dc6acd506c8 100644 --- a/polkadot/node/service/Cargo.toml +++ b/polkadot/node/service/Cargo.toml @@ -85,8 +85,8 @@ is_executable = "1.0.1" gum = { package = "tracing-gum", path = "../gum" } log = { workspace = true, default-features = true } schnellru = "0.2.1" -serde = { version = "1.0.195", features = ["derive"] } -serde_json = "1.0.111" +serde = { version = "1.0.196", features = ["derive"] } +serde_json = "1.0.113" thiserror = "1.0.48" kvdb = "0.13.0" kvdb-rocksdb = { version = "0.19.0", optional = true } diff --git a/polkadot/node/subsystem-bench/Cargo.toml b/polkadot/node/subsystem-bench/Cargo.toml index bc038abd648..fca88616ac4 100644 --- a/polkadot/node/subsystem-bench/Cargo.toml +++ b/polkadot/node/subsystem-bench/Cargo.toml @@ -35,7 +35,7 @@ async-trait = "0.1.57" sp-keystore = { path = "../../../substrate/primitives/keystore" } sc-keystore = { path = "../../../substrate/client/keystore" } sp-core = { path = "../../../substrate/primitives/core" } -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.0", features = ["derive"] } futures = "0.3.21" futures-timer = "3.0.2" bincode = "1.3.3" @@ -65,7 +65,7 @@ itertools = "0.11.0" polkadot-primitives-test-helpers = { path = "../../primitives/test-helpers" } prometheus_endpoint = { package = "substrate-prometheus-endpoint", path = "../../../substrate/utils/prometheus" } prometheus = { version = "0.13.0", default-features = false } -serde = "1.0.195" +serde = "1.0.196" serde_yaml = "0.9" polkadot-node-core-approval-voting = { path = "../core/approval-voting" } diff --git a/polkadot/node/test/service/Cargo.toml b/polkadot/node/test/service/Cargo.toml index f0410853799..47a754892ed 100644 --- a/polkadot/node/test/service/Cargo.toml +++ b/polkadot/node/test/service/Cargo.toml @@ -14,7 +14,7 @@ futures = "0.3.21" hex = "0.4.3" gum = { package = "tracing-gum", path = "../../gum" } rand = "0.8.5" -serde_json = "1.0.111" +serde_json = "1.0.113" tempfile = "3.2.0" tokio = "1.24.2" diff --git a/polkadot/node/zombienet-backchannel/Cargo.toml b/polkadot/node/zombienet-backchannel/Cargo.toml index 6af7a8d6e38..ec0521a592d 100644 --- a/polkadot/node/zombienet-backchannel/Cargo.toml +++ b/polkadot/node/zombienet-backchannel/Cargo.toml @@ -22,4 +22,4 @@ reqwest = { version = "0.11", features = ["rustls-tls"], default-features = fals thiserror = "1.0.48" gum = { package = "tracing-gum", path = "../gum" } serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0.111" +serde_json = "1.0.113" diff --git a/polkadot/parachain/Cargo.toml b/polkadot/parachain/Cargo.toml index e5ff1051d25..843c4fe3e95 100644 --- a/polkadot/parachain/Cargo.toml +++ b/polkadot/parachain/Cargo.toml @@ -24,7 +24,7 @@ derive_more = "0.99.11" bounded-collections = { version = "0.2.0", default-features = false, features = ["serde"] } # all optional crates. -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"] } +serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"] } [features] default = ["std"] diff --git a/polkadot/parachain/test-parachains/adder/collator/Cargo.toml b/polkadot/parachain/test-parachains/adder/collator/Cargo.toml index c2b11aadb50..268b96aa153 100644 --- a/polkadot/parachain/test-parachains/adder/collator/Cargo.toml +++ b/polkadot/parachain/test-parachains/adder/collator/Cargo.toml @@ -16,7 +16,7 @@ path = "src/main.rs" [dependencies] parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.0", features = ["derive"] } futures = "0.3.21" futures-timer = "3.0.2" log = { workspace = true, default-features = true } diff --git a/polkadot/parachain/test-parachains/undying/collator/Cargo.toml b/polkadot/parachain/test-parachains/undying/collator/Cargo.toml index c74b01be084..805262fe025 100644 --- a/polkadot/parachain/test-parachains/undying/collator/Cargo.toml +++ b/polkadot/parachain/test-parachains/undying/collator/Cargo.toml @@ -16,7 +16,7 @@ path = "src/main.rs" [dependencies] parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.0", features = ["derive"] } futures = "0.3.21" futures-timer = "3.0.2" log = { workspace = true, default-features = true } diff --git a/polkadot/primitives/Cargo.toml b/polkadot/primitives/Cargo.toml index c2fdf331568..fa3e949d691 100644 --- a/polkadot/primitives/Cargo.toml +++ b/polkadot/primitives/Cargo.toml @@ -14,7 +14,7 @@ bitvec = { version = "1.0.0", default-features = false, features = ["alloc", "se hex-literal = "0.4.1" parity-scale-codec = { version = "3.6.1", default-features = false, features = ["bit-vec", "derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["bit-vec", "derive", "serde"] } -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"] } +serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"] } application-crypto = { package = "sp-application-crypto", path = "../../substrate/primitives/application-crypto", default-features = false, features = ["serde"] } inherents = { package = "sp-inherents", path = "../../substrate/primitives/inherents", default-features = false } diff --git a/polkadot/runtime/common/Cargo.toml b/polkadot/runtime/common/Cargo.toml index 7257ca1d6d6..52fbfd9ac77 100644 --- a/polkadot/runtime/common/Cargo.toml +++ b/polkadot/runtime/common/Cargo.toml @@ -16,7 +16,7 @@ parity-scale-codec = { version = "3.6.1", default-features = false, features = [ log = { workspace = true } rustc-hex = { version = "2.1.0", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, features = ["alloc"] } +serde = { version = "1.0.196", default-features = false, features = ["alloc"] } serde_derive = { version = "1.0.117" } static_assertions = "1.1.0" @@ -69,7 +69,7 @@ pallet-babe = { path = "../../../substrate/frame/babe" } pallet-treasury = { path = "../../../substrate/frame/treasury" } sp-keystore = { path = "../../../substrate/primitives/keystore" } sp-keyring = { path = "../../../substrate/primitives/keyring" } -serde_json = "1.0.111" +serde_json = "1.0.113" libsecp256k1 = "0.7.0" test-helpers = { package = "polkadot-primitives-test-helpers", path = "../../primitives/test-helpers" } diff --git a/polkadot/runtime/parachains/Cargo.toml b/polkadot/runtime/parachains/Cargo.toml index 12cebe8e79d..1de34400758 100644 --- a/polkadot/runtime/parachains/Cargo.toml +++ b/polkadot/runtime/parachains/Cargo.toml @@ -16,7 +16,7 @@ parity-scale-codec = { version = "3.6.1", default-features = false, features = [ log = { workspace = true } rustc-hex = { version = "2.1.0", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"] } +serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"] } derive_more = "0.99.17" bitflags = "1.3.2" @@ -69,7 +69,7 @@ sp-tracing = { path = "../../../substrate/primitives/tracing" } sp-crypto-hashing = { path = "../../../substrate/primitives/crypto/hashing" } thousands = "0.2.0" assert_matches = "1" -serde_json = "1.0.111" +serde_json = "1.0.113" [features] default = ["std"] diff --git a/polkadot/runtime/rococo/Cargo.toml b/polkadot/runtime/rococo/Cargo.toml index 6b718e824be..68ded4aeaec 100644 --- a/polkadot/runtime/rococo/Cargo.toml +++ b/polkadot/runtime/rococo/Cargo.toml @@ -14,7 +14,7 @@ workspace = true parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } log = { workspace = true } -serde = { version = "1.0.195", default-features = false } +serde = { version = "1.0.196", default-features = false } serde_derive = { version = "1.0.117", optional = true } static_assertions = "1.1.0" smallvec = "1.8.0" @@ -111,7 +111,7 @@ keyring = { package = "sp-keyring", path = "../../../substrate/primitives/keyrin remote-externalities = { package = "frame-remote-externalities", path = "../../../substrate/utils/frame/remote-externalities" } sp-trie = { path = "../../../substrate/primitives/trie" } separator = "0.4.1" -serde_json = "1.0.111" +serde_json = "1.0.113" sp-tracing = { path = "../../../substrate/primitives/tracing", default-features = false } tokio = { version = "1.24.2", features = ["macros"] } diff --git a/polkadot/runtime/test-runtime/Cargo.toml b/polkadot/runtime/test-runtime/Cargo.toml index 3eca55a73d7..1f041fdfeaa 100644 --- a/polkadot/runtime/test-runtime/Cargo.toml +++ b/polkadot/runtime/test-runtime/Cargo.toml @@ -16,7 +16,7 @@ parity-scale-codec = { version = "3.6.1", default-features = false, features = [ log = { workspace = true } rustc-hex = { version = "2.1.0", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false } +serde = { version = "1.0.196", default-features = false } serde_derive = { version = "1.0.117", optional = true } smallvec = "1.8.0" @@ -74,7 +74,7 @@ hex-literal = "0.4.1" tiny-keccak = { version = "2.0.2", features = ["keccak"] } keyring = { package = "sp-keyring", path = "../../../substrate/primitives/keyring" } sp-trie = { path = "../../../substrate/primitives/trie" } -serde_json = "1.0.111" +serde_json = "1.0.113" [build-dependencies] substrate-wasm-builder = { path = "../../../substrate/utils/wasm-builder" } diff --git a/polkadot/runtime/westend/Cargo.toml b/polkadot/runtime/westend/Cargo.toml index 77acb1b8565..31b3f8351d8 100644 --- a/polkadot/runtime/westend/Cargo.toml +++ b/polkadot/runtime/westend/Cargo.toml @@ -16,7 +16,7 @@ parity-scale-codec = { version = "3.6.1", default-features = false, features = [ scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } log = { workspace = true } rustc-hex = { version = "2.1.0", default-features = false } -serde = { version = "1.0.195", default-features = false } +serde = { version = "1.0.196", default-features = false } serde_derive = { version = "1.0.117", optional = true } smallvec = "1.8.0" @@ -119,7 +119,7 @@ xcm-builder = { package = "staging-xcm-builder", path = "../../xcm/xcm-builder", hex-literal = "0.4.1" tiny-keccak = { version = "2.0.2", features = ["keccak"] } keyring = { package = "sp-keyring", path = "../../../substrate/primitives/keyring" } -serde_json = "1.0.111" +serde_json = "1.0.113" remote-externalities = { package = "frame-remote-externalities", path = "../../../substrate/utils/frame/remote-externalities" } tokio = { version = "1.24.2", features = ["macros"] } sp-tracing = { path = "../../../substrate/primitives/tracing", default-features = false } diff --git a/polkadot/utils/generate-bags/Cargo.toml b/polkadot/utils/generate-bags/Cargo.toml index b255b7ab64d..38f1ac561c5 100644 --- a/polkadot/utils/generate-bags/Cargo.toml +++ b/polkadot/utils/generate-bags/Cargo.toml @@ -10,7 +10,7 @@ description = "CLI to generate voter bags for Polkadot runtimes" workspace = true [dependencies] -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.0", features = ["derive"] } generate-bags = { path = "../../../substrate/utils/frame/generate-bags" } sp-io = { path = "../../../substrate/primitives/io" } diff --git a/polkadot/utils/remote-ext-tests/bags-list/Cargo.toml b/polkadot/utils/remote-ext-tests/bags-list/Cargo.toml index a300f0fca1c..79adfe36852 100644 --- a/polkadot/utils/remote-ext-tests/bags-list/Cargo.toml +++ b/polkadot/utils/remote-ext-tests/bags-list/Cargo.toml @@ -18,6 +18,6 @@ sp-tracing = { path = "../../../../substrate/primitives/tracing" } frame-system = { path = "../../../../substrate/frame/system" } sp-core = { path = "../../../../substrate/primitives/core" } -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.0", features = ["derive"] } log = { workspace = true, default-features = true } tokio = { version = "1.24.2", features = ["macros"] } diff --git a/polkadot/xcm/Cargo.toml b/polkadot/xcm/Cargo.toml index b89a5c5c870..888757150b4 100644 --- a/polkadot/xcm/Cargo.toml +++ b/polkadot/xcm/Cargo.toml @@ -18,7 +18,7 @@ log = { workspace = true } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } sp-weights = { path = "../../substrate/primitives/weights", default-features = false, features = ["serde"] } -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive", "rc"] } +serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive", "rc"] } schemars = { version = "0.8.13", default-features = true, optional = true } xcm-procedural = { path = "procedural" } environmental = { version = "1.1.4", default-features = false } diff --git a/polkadot/xcm/pallet-xcm/Cargo.toml b/polkadot/xcm/pallet-xcm/Cargo.toml index aa15d5edc82..ac39e5f7429 100644 --- a/polkadot/xcm/pallet-xcm/Cargo.toml +++ b/polkadot/xcm/pallet-xcm/Cargo.toml @@ -13,7 +13,7 @@ workspace = true bounded-collections = { version = "0.2.0", default-features = false } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true, features = ["derive"] } +serde = { version = "1.0.196", optional = true, features = ["derive"] } log = { workspace = true } frame-support = { path = "../../../substrate/frame/support", default-features = false } diff --git a/polkadot/xcm/procedural/Cargo.toml b/polkadot/xcm/procedural/Cargo.toml index f87347f12dc..1af4d2db4f1 100644 --- a/polkadot/xcm/procedural/Cargo.toml +++ b/polkadot/xcm/procedural/Cargo.toml @@ -16,7 +16,7 @@ proc-macro = true [dependencies] proc-macro2 = "1.0.56" quote = "1.0.28" -syn = "2.0.48" +syn = "2.0.49" Inflector = "0.11.4" [dev-dependencies] diff --git a/substrate/bin/minimal/node/Cargo.toml b/substrate/bin/minimal/node/Cargo.toml index 2cea39d0df8..fd10a3f37af 100644 --- a/substrate/bin/minimal/node/Cargo.toml +++ b/substrate/bin/minimal/node/Cargo.toml @@ -20,11 +20,11 @@ targets = ["x86_64-unknown-linux-gnu"] name = "minimal-node" [dependencies] -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.0", features = ["derive"] } futures = { version = "0.3.21", features = ["thread-pool"] } futures-timer = "3.0.1" jsonrpsee = { version = "0.22", features = ["server"] } -serde_json = "1.0.111" +serde_json = "1.0.113" sc-cli = { path = "../../../client/cli" } sc-executor = { path = "../../../client/executor" } diff --git a/substrate/bin/node-template/node/Cargo.toml b/substrate/bin/node-template/node/Cargo.toml index 0dcf2fefc43..dbdb270b09c 100644 --- a/substrate/bin/node-template/node/Cargo.toml +++ b/substrate/bin/node-template/node/Cargo.toml @@ -20,9 +20,9 @@ targets = ["x86_64-unknown-linux-gnu"] name = "node-template" [dependencies] -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.0", features = ["derive"] } futures = { version = "0.3.21", features = ["thread-pool"] } -serde_json = "1.0.111" +serde_json = "1.0.113" sc-cli = { path = "../../../client/cli" } sp-core = { path = "../../../primitives/core" } diff --git a/substrate/bin/node-template/runtime/Cargo.toml b/substrate/bin/node-template/runtime/Cargo.toml index a7b93a230ca..95d92e6eadf 100644 --- a/substrate/bin/node-template/runtime/Cargo.toml +++ b/substrate/bin/node-template/runtime/Cargo.toml @@ -42,7 +42,7 @@ sp-std = { path = "../../../primitives/std", default-features = false } sp-storage = { path = "../../../primitives/storage", default-features = false } sp-transaction-pool = { path = "../../../primitives/transaction-pool", default-features = false } sp-version = { path = "../../../primitives/version", default-features = false, features = ["serde"] } -serde_json = { version = "1.0.111", default-features = false, features = ["alloc"] } +serde_json = { version = "1.0.113", default-features = false, features = ["alloc"] } sp-genesis-builder = { default-features = false, path = "../../../primitives/genesis-builder" } # Used for the node template's RPCs diff --git a/substrate/bin/node/bench/Cargo.toml b/substrate/bin/node/bench/Cargo.toml index 78cdaeb0ceb..76b9c7a0ca6 100644 --- a/substrate/bin/node/bench/Cargo.toml +++ b/substrate/bin/node/bench/Cargo.toml @@ -16,7 +16,7 @@ workspace = true [dependencies] array-bytes = "6.1" -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.0", features = ["derive"] } log = { workspace = true, default-features = true } node-primitives = { path = "../primitives" } node-testing = { path = "../testing" } @@ -24,8 +24,8 @@ kitchensink-runtime = { path = "../runtime" } sc-client-api = { path = "../../../client/api" } sp-runtime = { path = "../../../primitives/runtime" } sp-state-machine = { path = "../../../primitives/state-machine" } -serde = "1.0.195" -serde_json = "1.0.111" +serde = "1.0.196" +serde_json = "1.0.113" derive_more = { version = "0.99.17", default-features = false, features = ["display"] } kvdb = "0.13.0" kvdb-rocksdb = "0.19.0" diff --git a/substrate/bin/node/cli/Cargo.toml b/substrate/bin/node/cli/Cargo.toml index 25ca10c1bb1..42e977e74ec 100644 --- a/substrate/bin/node/cli/Cargo.toml +++ b/substrate/bin/node/cli/Cargo.toml @@ -41,9 +41,9 @@ crate-type = ["cdylib", "rlib"] [dependencies] # third-party dependencies array-bytes = "6.1" -clap = { version = "4.4.18", features = ["derive"], optional = true } +clap = { version = "4.5.0", features = ["derive"], optional = true } codec = { package = "parity-scale-codec", version = "3.6.1" } -serde = { version = "1.0.195", features = ["derive"] } +serde = { version = "1.0.196", features = ["derive"] } jsonrpsee = { version = "0.22", features = ["server"] } futures = "0.3.21" log = { workspace = true, default-features = true } @@ -116,7 +116,7 @@ sc-cli = { path = "../../../client/cli", optional = true } frame-benchmarking-cli = { path = "../../../utils/frame/benchmarking-cli", optional = true } node-inspect = { package = "staging-node-inspect", path = "../inspect", optional = true } try-runtime-cli = { path = "../../../utils/frame/try-runtime/cli", optional = true } -serde_json = "1.0.111" +serde_json = "1.0.113" [dev-dependencies] sc-keystore = { path = "../../../client/keystore" } @@ -159,13 +159,13 @@ sp-consensus-babe = { path = "../../../primitives/consensus/babe" } sp-externalities = { path = "../../../primitives/externalities" } sp-keyring = { path = "../../../primitives/keyring" } sp-runtime = { path = "../../../primitives/runtime" } -serde_json = "1.0.111" +serde_json = "1.0.113" scale-info = { version = "2.10.0", features = ["derive", "serde"] } sp-trie = { path = "../../../primitives/trie" } sp-state-machine = { path = "../../../primitives/state-machine" } [build-dependencies] -clap = { version = "4.4.18", optional = true } +clap = { version = "4.5.0", optional = true } clap_complete = { version = "4.0.2", optional = true } node-inspect = { package = "staging-node-inspect", path = "../inspect", optional = true } frame-benchmarking-cli = { path = "../../../utils/frame/benchmarking-cli", optional = true } diff --git a/substrate/bin/node/inspect/Cargo.toml b/substrate/bin/node/inspect/Cargo.toml index e703e312b51..27bf190ce15 100644 --- a/substrate/bin/node/inspect/Cargo.toml +++ b/substrate/bin/node/inspect/Cargo.toml @@ -15,7 +15,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.0", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.6.1" } thiserror = "1.0" sc-cli = { path = "../../../client/cli" } diff --git a/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml index b82358b7c29..7bc608ba89e 100644 --- a/substrate/bin/node/runtime/Cargo.toml +++ b/substrate/bin/node/runtime/Cargo.toml @@ -26,7 +26,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } static_assertions = "1.1.0" log = { workspace = true } -serde_json = { version = "1.0.111", default-features = false, features = ["alloc", "arbitrary_precision"] } +serde_json = { version = "1.0.113", default-features = false, features = ["alloc", "arbitrary_precision"] } # pallet-asset-conversion: turn on "num-traits" feature primitive-types = { version = "0.12.0", default-features = false, features = ["codec", "num-traits", "scale-info"] } diff --git a/substrate/bin/utils/chain-spec-builder/Cargo.toml b/substrate/bin/utils/chain-spec-builder/Cargo.toml index df4ddf33311..2f4edeff49a 100644 --- a/substrate/bin/utils/chain-spec-builder/Cargo.toml +++ b/substrate/bin/utils/chain-spec-builder/Cargo.toml @@ -23,8 +23,8 @@ name = "chain-spec-builder" crate-type = ["rlib"] [dependencies] -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.0", features = ["derive"] } log = { workspace = true, default-features = true } sc-chain-spec = { path = "../../../client/chain-spec" } -serde_json = "1.0.111" +serde_json = "1.0.113" sp-tracing = { path = "../../../primitives/tracing" } diff --git a/substrate/bin/utils/subkey/Cargo.toml b/substrate/bin/utils/subkey/Cargo.toml index ac97428a7df..a79015238d6 100644 --- a/substrate/bin/utils/subkey/Cargo.toml +++ b/substrate/bin/utils/subkey/Cargo.toml @@ -20,5 +20,5 @@ path = "src/main.rs" name = "subkey" [dependencies] -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.0", features = ["derive"] } sc-cli = { path = "../../../client/cli" } diff --git a/substrate/client/chain-spec/Cargo.toml b/substrate/client/chain-spec/Cargo.toml index e8ad9e35092..82a5f7f55b0 100644 --- a/substrate/client/chain-spec/Cargo.toml +++ b/substrate/client/chain-spec/Cargo.toml @@ -18,8 +18,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } memmap2 = "0.9.3" -serde = { version = "1.0.195", features = ["derive"] } -serde_json = "1.0.111" +serde = { version = "1.0.196", features = ["derive"] } +serde_json = "1.0.113" sc-client-api = { path = "../api" } sc-chain-spec-derive = { path = "derive" } sc-executor = { path = "../executor" } diff --git a/substrate/client/chain-spec/derive/Cargo.toml b/substrate/client/chain-spec/derive/Cargo.toml index 5e8e64e5551..4e95206df6b 100644 --- a/substrate/client/chain-spec/derive/Cargo.toml +++ b/substrate/client/chain-spec/derive/Cargo.toml @@ -21,4 +21,4 @@ proc-macro = true proc-macro-crate = "3.0.0" proc-macro2 = "1.0.56" quote = "1.0.28" -syn = "2.0.48" +syn = "2.0.49" diff --git a/substrate/client/cli/Cargo.toml b/substrate/client/cli/Cargo.toml index 50fb57bf1ff..f5e2be23193 100644 --- a/substrate/client/cli/Cargo.toml +++ b/substrate/client/cli/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] array-bytes = "6.1" chrono = "0.4.31" -clap = { version = "4.4.18", features = ["derive", "string", "wrap_help"] } +clap = { version = "4.5.0", features = ["derive", "string", "wrap_help"] } fdlimit = "0.3.0" futures = "0.3.21" itertools = "0.10.3" @@ -29,8 +29,8 @@ parity-scale-codec = "3.6.1" rand = "0.8.5" regex = "1.6.0" rpassword = "7.0.0" -serde = "1.0.195" -serde_json = "1.0.111" +serde = "1.0.196" +serde_json = "1.0.113" thiserror = "1.0.48" bip39 = "2.0.0" tokio = { version = "1.22.0", features = ["parking_lot", "rt-multi-thread", "signal"] } diff --git a/substrate/client/consensus/babe/rpc/Cargo.toml b/substrate/client/consensus/babe/rpc/Cargo.toml index 49b4b1a4a6d..342c9259cc6 100644 --- a/substrate/client/consensus/babe/rpc/Cargo.toml +++ b/substrate/client/consensus/babe/rpc/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } futures = "0.3.21" -serde = { version = "1.0.195", features = ["derive"] } +serde = { version = "1.0.196", features = ["derive"] } thiserror = "1.0" sc-consensus-babe = { path = ".." } sc-consensus-epochs = { path = "../../epochs" } @@ -33,7 +33,7 @@ sp-keystore = { path = "../../../../primitives/keystore" } sp-runtime = { path = "../../../../primitives/runtime" } [dev-dependencies] -serde_json = "1.0.111" +serde_json = "1.0.113" tokio = "1.22.0" sc-consensus = { path = "../../common" } sc-keystore = { path = "../../../keystore" } diff --git a/substrate/client/consensus/beefy/Cargo.toml b/substrate/client/consensus/beefy/Cargo.toml index e76d9ec826e..d6b8a57b988 100644 --- a/substrate/client/consensus/beefy/Cargo.toml +++ b/substrate/client/consensus/beefy/Cargo.toml @@ -44,7 +44,7 @@ tokio = "1.22.0" [dev-dependencies] -serde = "1.0.195" +serde = "1.0.196" tempfile = "3.1.0" sc-block-builder = { path = "../../block-builder" } sc-network-test = { path = "../../network/test" } diff --git a/substrate/client/consensus/beefy/rpc/Cargo.toml b/substrate/client/consensus/beefy/rpc/Cargo.toml index cff3cc5b3bb..e83c95ab106 100644 --- a/substrate/client/consensus/beefy/rpc/Cargo.toml +++ b/substrate/client/consensus/beefy/rpc/Cargo.toml @@ -17,7 +17,7 @@ futures = "0.3.21" jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } log = { workspace = true, default-features = true } parking_lot = "0.12.1" -serde = { version = "1.0.195", features = ["derive"] } +serde = { version = "1.0.196", features = ["derive"] } thiserror = "1.0" sc-consensus-beefy = { path = ".." } sp-consensus-beefy = { path = "../../../../primitives/consensus/beefy" } @@ -26,7 +26,7 @@ sp-core = { path = "../../../../primitives/core" } sp-runtime = { path = "../../../../primitives/runtime" } [dev-dependencies] -serde_json = "1.0.111" +serde_json = "1.0.113" sc-rpc = { path = "../../../rpc", features = ["test-helpers"] } substrate-test-runtime-client = { path = "../../../../test-utils/runtime/client" } tokio = { version = "1.22.0", features = ["macros"] } diff --git a/substrate/client/consensus/grandpa/Cargo.toml b/substrate/client/consensus/grandpa/Cargo.toml index 1d02fbb2c88..e342377d820 100644 --- a/substrate/client/consensus/grandpa/Cargo.toml +++ b/substrate/client/consensus/grandpa/Cargo.toml @@ -28,7 +28,7 @@ log = { workspace = true, default-features = true } parity-scale-codec = { version = "3.6.1", features = ["derive"] } parking_lot = "0.12.1" rand = "0.8.5" -serde_json = "1.0.111" +serde_json = "1.0.113" thiserror = "1.0" fork-tree = { path = "../../../utils/fork-tree" } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } @@ -57,7 +57,7 @@ sp-runtime = { path = "../../../primitives/runtime" } [dev-dependencies] assert_matches = "1.3.0" finality-grandpa = { version = "0.16.2", features = ["derive-codec", "test-helpers"] } -serde = "1.0.195" +serde = "1.0.196" tokio = "1.22.0" sc-network = { path = "../../network" } sc-network-test = { path = "../../network/test" } diff --git a/substrate/client/consensus/grandpa/rpc/Cargo.toml b/substrate/client/consensus/grandpa/rpc/Cargo.toml index affb7b4a512..ca0c2ac371a 100644 --- a/substrate/client/consensus/grandpa/rpc/Cargo.toml +++ b/substrate/client/consensus/grandpa/rpc/Cargo.toml @@ -18,7 +18,7 @@ futures = "0.3.16" jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } log = { workspace = true, default-features = true } parity-scale-codec = { version = "3.6.1", features = ["derive"] } -serde = { version = "1.0.195", features = ["derive"] } +serde = { version = "1.0.196", features = ["derive"] } thiserror = "1.0" sc-client-api = { path = "../../../api" } sc-consensus-grandpa = { path = ".." } diff --git a/substrate/client/keystore/Cargo.toml b/substrate/client/keystore/Cargo.toml index dc6a0fe29a8..f7f3b138ec8 100644 --- a/substrate/client/keystore/Cargo.toml +++ b/substrate/client/keystore/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] array-bytes = "6.1" parking_lot = "0.12.1" -serde_json = "1.0.111" +serde_json = "1.0.113" thiserror = "1.0" sp-application-crypto = { path = "../../primitives/application-crypto" } sp-core = { path = "../../primitives/core" } diff --git a/substrate/client/merkle-mountain-range/rpc/Cargo.toml b/substrate/client/merkle-mountain-range/rpc/Cargo.toml index 79286cf961d..901c573b6bd 100644 --- a/substrate/client/merkle-mountain-range/rpc/Cargo.toml +++ b/substrate/client/merkle-mountain-range/rpc/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } -serde = { version = "1.0.195", features = ["derive"] } +serde = { version = "1.0.196", features = ["derive"] } sp-api = { path = "../../../primitives/api" } sp-blockchain = { path = "../../../primitives/blockchain" } sp-core = { path = "../../../primitives/core" } @@ -25,4 +25,4 @@ sp-mmr-primitives = { path = "../../../primitives/merkle-mountain-range" } sp-runtime = { path = "../../../primitives/runtime" } [dev-dependencies] -serde_json = "1.0.111" +serde_json = "1.0.113" diff --git a/substrate/client/network/Cargo.toml b/substrate/client/network/Cargo.toml index 79f5a6b4fad..d2ca8ba8c3f 100644 --- a/substrate/client/network/Cargo.toml +++ b/substrate/client/network/Cargo.toml @@ -36,8 +36,8 @@ parking_lot = "0.12.1" partial_sort = "0.2.0" pin-project = "1.0.12" rand = "0.8.5" -serde = { version = "1.0.195", features = ["derive"] } -serde_json = "1.0.111" +serde = { version = "1.0.196", features = ["derive"] } +serde_json = "1.0.113" smallvec = "1.11.0" thiserror = "1.0" tokio = { version = "1.22.0", features = ["macros", "sync"] } diff --git a/substrate/client/rpc-api/Cargo.toml b/substrate/client/rpc-api/Cargo.toml index 45f47920e07..27f8a1b9062 100644 --- a/substrate/client/rpc-api/Cargo.toml +++ b/substrate/client/rpc-api/Cargo.toml @@ -18,8 +18,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", features = ["derive"] } -serde_json = "1.0.111" +serde = { version = "1.0.196", features = ["derive"] } +serde_json = "1.0.113" thiserror = "1.0" sc-chain-spec = { path = "../chain-spec" } sc-mixnet = { path = "../mixnet" } diff --git a/substrate/client/rpc-servers/Cargo.toml b/substrate/client/rpc-servers/Cargo.toml index c8935c36afd..7dd525ada65 100644 --- a/substrate/client/rpc-servers/Cargo.toml +++ b/substrate/client/rpc-servers/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] jsonrpsee = { version = "0.22", features = ["server"] } log = { workspace = true, default-features = true } -serde_json = "1.0.111" +serde_json = "1.0.113" tokio = { version = "1.22.0", features = ["parking_lot"] } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } tower-http = { version = "0.4.0", features = ["cors"] } diff --git a/substrate/client/rpc-spec-v2/Cargo.toml b/substrate/client/rpc-spec-v2/Cargo.toml index 8269b631bdd..cb109becaf2 100644 --- a/substrate/client/rpc-spec-v2/Cargo.toml +++ b/substrate/client/rpc-spec-v2/Cargo.toml @@ -44,7 +44,7 @@ futures-util = { version = "0.3.30", default-features = false } rand = "0.8.5" [dev-dependencies] -serde_json = "1.0.111" +serde_json = "1.0.113" tokio = { version = "1.22.0", features = ["macros"] } substrate-test-runtime-client = { path = "../../test-utils/runtime/client" } substrate-test-runtime = { path = "../../test-utils/runtime" } diff --git a/substrate/client/rpc/Cargo.toml b/substrate/client/rpc/Cargo.toml index 5726c66e8b3..aa97eb87bb5 100644 --- a/substrate/client/rpc/Cargo.toml +++ b/substrate/client/rpc/Cargo.toml @@ -21,7 +21,7 @@ futures = "0.3.21" jsonrpsee = { version = "0.22", features = ["server"] } log = { workspace = true, default-features = true } parking_lot = "0.12.1" -serde_json = "1.0.111" +serde_json = "1.0.113" sc-block-builder = { path = "../block-builder" } sc-chain-spec = { path = "../chain-spec" } sc-client-api = { path = "../api" } diff --git a/substrate/client/service/Cargo.toml b/substrate/client/service/Cargo.toml index fd7a8cc1a11..a6fa360b74f 100644 --- a/substrate/client/service/Cargo.toml +++ b/substrate/client/service/Cargo.toml @@ -37,8 +37,8 @@ log = { workspace = true, default-features = true } futures-timer = "3.0.1" exit-future = "0.2.0" pin-project = "1.0.12" -serde = "1.0.195" -serde_json = "1.0.111" +serde = "1.0.196" +serde_json = "1.0.113" sc-keystore = { path = "../keystore" } sp-runtime = { path = "../../primitives/runtime" } sp-trie = { path = "../../primitives/trie" } diff --git a/substrate/client/storage-monitor/Cargo.toml b/substrate/client/storage-monitor/Cargo.toml index 6cea3f3894b..3916f560909 100644 --- a/substrate/client/storage-monitor/Cargo.toml +++ b/substrate/client/storage-monitor/Cargo.toml @@ -12,7 +12,7 @@ homepage = "https://substrate.io" workspace = true [dependencies] -clap = { version = "4.4.18", features = ["derive", "string"] } +clap = { version = "4.5.0", features = ["derive", "string"] } log = { workspace = true, default-features = true } fs4 = "0.7.0" sp-core = { path = "../../primitives/core" } diff --git a/substrate/client/sync-state-rpc/Cargo.toml b/substrate/client/sync-state-rpc/Cargo.toml index 0d22bb105a6..fc0efd6630c 100644 --- a/substrate/client/sync-state-rpc/Cargo.toml +++ b/substrate/client/sync-state-rpc/Cargo.toml @@ -17,8 +17,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } -serde = { version = "1.0.195", features = ["derive"] } -serde_json = "1.0.111" +serde = { version = "1.0.196", features = ["derive"] } +serde_json = "1.0.113" thiserror = "1.0.48" sc-chain-spec = { path = "../chain-spec" } sc-client-api = { path = "../api" } diff --git a/substrate/client/sysinfo/Cargo.toml b/substrate/client/sysinfo/Cargo.toml index 4c2b0eae161..ede0bccaedc 100644 --- a/substrate/client/sysinfo/Cargo.toml +++ b/substrate/client/sysinfo/Cargo.toml @@ -24,8 +24,8 @@ rand = "0.8.5" rand_pcg = "0.3.1" derive_more = "0.99" regex = "1" -serde = { version = "1.0.195", features = ["derive"] } -serde_json = "1.0.111" +serde = { version = "1.0.196", features = ["derive"] } +serde_json = "1.0.113" sc-telemetry = { path = "../telemetry" } sp-core = { path = "../../primitives/core" } sp-crypto-hashing = { path = "../../primitives/crypto/hashing" } diff --git a/substrate/client/telemetry/Cargo.toml b/substrate/client/telemetry/Cargo.toml index e0563c44bc4..e35fbc16142 100644 --- a/substrate/client/telemetry/Cargo.toml +++ b/substrate/client/telemetry/Cargo.toml @@ -25,7 +25,7 @@ parking_lot = "0.12.1" pin-project = "1.0.12" sc-utils = { path = "../utils" } rand = "0.8.5" -serde = { version = "1.0.195", features = ["derive"] } -serde_json = "1.0.111" +serde = { version = "1.0.196", features = ["derive"] } +serde_json = "1.0.113" thiserror = "1.0.48" wasm-timer = "0.2.5" diff --git a/substrate/client/tracing/Cargo.toml b/substrate/client/tracing/Cargo.toml index 1331071d3bd..bd5a661bb3a 100644 --- a/substrate/client/tracing/Cargo.toml +++ b/substrate/client/tracing/Cargo.toml @@ -26,7 +26,7 @@ log = { workspace = true, default-features = true } parking_lot = "0.12.1" regex = "1.6.0" rustc-hash = "1.1.0" -serde = "1.0.195" +serde = "1.0.196" thiserror = "1.0.48" tracing = "0.1.29" tracing-log = "0.1.3" diff --git a/substrate/client/tracing/proc-macro/Cargo.toml b/substrate/client/tracing/proc-macro/Cargo.toml index 4a826d9f4fc..aa6ae7d6fd4 100644 --- a/substrate/client/tracing/proc-macro/Cargo.toml +++ b/substrate/client/tracing/proc-macro/Cargo.toml @@ -21,4 +21,4 @@ proc-macro = true proc-macro-crate = "3.0.0" proc-macro2 = "1.0.56" quote = { version = "1.0.28", features = ["proc-macro"] } -syn = { version = "2.0.48", features = ["extra-traits", "full", "parsing", "proc-macro"] } +syn = { version = "2.0.49", features = ["extra-traits", "full", "parsing", "proc-macro"] } diff --git a/substrate/client/transaction-pool/Cargo.toml b/substrate/client/transaction-pool/Cargo.toml index 23ffebec67c..c7a2540f8ed 100644 --- a/substrate/client/transaction-pool/Cargo.toml +++ b/substrate/client/transaction-pool/Cargo.toml @@ -23,7 +23,7 @@ futures-timer = "3.0.2" linked-hash-map = "0.5.4" log = { workspace = true, default-features = true } parking_lot = "0.12.1" -serde = { version = "1.0.195", features = ["derive"] } +serde = { version = "1.0.196", features = ["derive"] } thiserror = "1.0.48" prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } sc-client-api = { path = "../api" } diff --git a/substrate/client/transaction-pool/api/Cargo.toml b/substrate/client/transaction-pool/api/Cargo.toml index 9cf732cb87a..79449b2ee87 100644 --- a/substrate/client/transaction-pool/api/Cargo.toml +++ b/substrate/client/transaction-pool/api/Cargo.toml @@ -16,11 +16,11 @@ async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" log = { workspace = true, default-features = true } -serde = { version = "1.0.195", features = ["derive"] } +serde = { version = "1.0.196", features = ["derive"] } thiserror = "1.0.48" sp-blockchain = { path = "../../../primitives/blockchain" } sp-core = { path = "../../../primitives/core", default-features = false } sp-runtime = { path = "../../../primitives/runtime", default-features = false } [dev-dependencies] -serde_json = "1.0.111" +serde_json = "1.0.113" diff --git a/substrate/frame/beefy-mmr/Cargo.toml b/substrate/frame/beefy-mmr/Cargo.toml index b2ede900f26..25290890fcd 100644 --- a/substrate/frame/beefy-mmr/Cargo.toml +++ b/substrate/frame/beefy-mmr/Cargo.toml @@ -16,7 +16,7 @@ array-bytes = { version = "6.1", optional = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true } +serde = { version = "1.0.196", optional = true } binary-merkle-tree = { path = "../../utils/binary-merkle-tree", default-features = false } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/beefy/Cargo.toml b/substrate/frame/beefy/Cargo.toml index 54b57b44742..866b032c331 100644 --- a/substrate/frame/beefy/Cargo.toml +++ b/substrate/frame/beefy/Cargo.toml @@ -15,7 +15,7 @@ workspace = true codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } -serde = { version = "1.0.195", optional = true } +serde = { version = "1.0.196", optional = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } pallet-authorship = { path = "../authorship", default-features = false } diff --git a/substrate/frame/benchmarking/Cargo.toml b/substrate/frame/benchmarking/Cargo.toml index cde1aea188b..8be7a500d3f 100644 --- a/substrate/frame/benchmarking/Cargo.toml +++ b/substrate/frame/benchmarking/Cargo.toml @@ -21,7 +21,7 @@ linregress = { version = "0.5.1", optional = true } log = { workspace = true } paste = "1.0" scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true } +serde = { version = "1.0.196", optional = true } frame-support = { path = "../support", default-features = false } frame-support-procedural = { path = "../support/procedural", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/contracts/proc-macro/Cargo.toml b/substrate/frame/contracts/proc-macro/Cargo.toml index ed6175ee8cd..7e4a6c58666 100644 --- a/substrate/frame/contracts/proc-macro/Cargo.toml +++ b/substrate/frame/contracts/proc-macro/Cargo.toml @@ -20,4 +20,4 @@ proc-macro = true [dependencies] proc-macro2 = "1.0.56" quote = "1.0.28" -syn = { version = "2.0.48", features = ["full"] } +syn = { version = "2.0.49", features = ["full"] } diff --git a/substrate/frame/conviction-voting/Cargo.toml b/substrate/frame/conviction-voting/Cargo.toml index 28ef8cd32fb..d4921bf7b4b 100644 --- a/substrate/frame/conviction-voting/Cargo.toml +++ b/substrate/frame/conviction-voting/Cargo.toml @@ -22,7 +22,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = "max-encoded-len", ] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", features = ["derive"], optional = true } +serde = { version = "1.0.196", features = ["derive"], optional = true } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/democracy/Cargo.toml b/substrate/frame/democracy/Cargo.toml index 1e824eac2d2..e1e8964dd9c 100644 --- a/substrate/frame/democracy/Cargo.toml +++ b/substrate/frame/democracy/Cargo.toml @@ -20,7 +20,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = "derive", ] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", features = ["derive"], optional = true } +serde = { version = "1.0.196", features = ["derive"], optional = true } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/election-provider-support/solution-type/Cargo.toml b/substrate/frame/election-provider-support/solution-type/Cargo.toml index 0b410364690..b5d6d1dca54 100644 --- a/substrate/frame/election-provider-support/solution-type/Cargo.toml +++ b/substrate/frame/election-provider-support/solution-type/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -syn = { version = "2.0.48", features = ["full", "visit"] } +syn = { version = "2.0.49", features = ["full", "visit"] } quote = "1.0.28" proc-macro2 = "1.0.56" proc-macro-crate = "3.0.0" diff --git a/substrate/frame/election-provider-support/solution-type/fuzzer/Cargo.toml b/substrate/frame/election-provider-support/solution-type/fuzzer/Cargo.toml index 7200d207aec..5ed37033e81 100644 --- a/substrate/frame/election-provider-support/solution-type/fuzzer/Cargo.toml +++ b/substrate/frame/election-provider-support/solution-type/fuzzer/Cargo.toml @@ -16,7 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.0", features = ["derive"] } honggfuzz = "0.5" rand = { version = "0.8", features = ["small_rng", "std"] } diff --git a/substrate/frame/message-queue/Cargo.toml b/substrate/frame/message-queue/Cargo.toml index 8f3e913ee36..104660a9886 100644 --- a/substrate/frame/message-queue/Cargo.toml +++ b/substrate/frame/message-queue/Cargo.toml @@ -14,7 +14,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true, features = ["derive"] } +serde = { version = "1.0.196", optional = true, features = ["derive"] } log = { workspace = true } environmental = { version = "1.1.4", default-features = false } diff --git a/substrate/frame/mixnet/Cargo.toml b/substrate/frame/mixnet/Cargo.toml index de4c2074e79..e0334b6692d 100644 --- a/substrate/frame/mixnet/Cargo.toml +++ b/substrate/frame/mixnet/Cargo.toml @@ -22,7 +22,7 @@ frame-support = { default-features = false, path = "../support" } frame-system = { default-features = false, path = "../system" } log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, features = ["derive"] } +serde = { version = "1.0.196", default-features = false, features = ["derive"] } sp-application-crypto = { default-features = false, path = "../../primitives/application-crypto" } sp-arithmetic = { default-features = false, path = "../../primitives/arithmetic" } sp-io = { default-features = false, path = "../../primitives/io" } diff --git a/substrate/frame/offences/Cargo.toml b/substrate/frame/offences/Cargo.toml index e7f559086a6..e1bf71f70df 100644 --- a/substrate/frame/offences/Cargo.toml +++ b/substrate/frame/offences/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true } +serde = { version = "1.0.196", optional = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } pallet-balances = { path = "../balances", default-features = false } diff --git a/substrate/frame/parameters/Cargo.toml b/substrate/frame/parameters/Cargo.toml index c95e23b1a02..82059e3a73d 100644 --- a/substrate/frame/parameters/Cargo.toml +++ b/substrate/frame/parameters/Cargo.toml @@ -11,7 +11,7 @@ edition.workspace = true codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["max-encoded-len"] } scale-info = { version = "2.1.2", default-features = false, features = ["derive"] } paste = { version = "1.0.14", default-features = false } -serde = { version = "1.0.188", features = ["derive"], optional = true } +serde = { version = "1.0.196", features = ["derive"], optional = true } docify = "0.2.5" frame-support = { path = "../support", default-features = false, features = ["experimental"] } diff --git a/substrate/frame/referenda/Cargo.toml b/substrate/frame/referenda/Cargo.toml index ad0e5993fc9..13c8cfc5961 100644 --- a/substrate/frame/referenda/Cargo.toml +++ b/substrate/frame/referenda/Cargo.toml @@ -21,7 +21,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = "derive", ] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", features = ["derive"], optional = true } +serde = { version = "1.0.196", features = ["derive"], optional = true } sp-arithmetic = { path = "../../primitives/arithmetic", default-features = false } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/remark/Cargo.toml b/substrate/frame/remark/Cargo.toml index 582f5958a69..c5f39a649ec 100644 --- a/substrate/frame/remark/Cargo.toml +++ b/substrate/frame/remark/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true } +serde = { version = "1.0.196", optional = true } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/staking/Cargo.toml b/substrate/frame/staking/Cargo.toml index 5f82a326145..51aa8c79fad 100644 --- a/substrate/frame/staking/Cargo.toml +++ b/substrate/frame/staking/Cargo.toml @@ -16,7 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"] } +serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"] } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } diff --git a/substrate/frame/staking/reward-curve/Cargo.toml b/substrate/frame/staking/reward-curve/Cargo.toml index 26ffbd7efbc..bae730f446f 100644 --- a/substrate/frame/staking/reward-curve/Cargo.toml +++ b/substrate/frame/staking/reward-curve/Cargo.toml @@ -21,7 +21,7 @@ proc-macro = true proc-macro-crate = "3.0.0" proc-macro2 = "1.0.56" quote = "1.0.28" -syn = { version = "2.0.48", features = ["full", "visit"] } +syn = { version = "2.0.49", features = ["full", "visit"] } [dev-dependencies] sp-runtime = { path = "../../../primitives/runtime" } diff --git a/substrate/frame/state-trie-migration/Cargo.toml b/substrate/frame/state-trie-migration/Cargo.toml index 83abf3615f0..5f86fb1595a 100644 --- a/substrate/frame/state-trie-migration/Cargo.toml +++ b/substrate/frame/state-trie-migration/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true } +serde = { version = "1.0.196", optional = true } thousands = { version = "0.2.0", optional = true } zstd = { version = "0.12.4", default-features = false, optional = true } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } diff --git a/substrate/frame/support/Cargo.toml b/substrate/frame/support/Cargo.toml index fd39e8397d0..73abc1a6aed 100644 --- a/substrate/frame/support/Cargo.toml +++ b/substrate/frame/support/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] array-bytes = { version = "6.1", default-features = false } -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"] } +serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"] } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-metadata = { version = "16.0.0", default-features = false, features = ["current"] } @@ -46,7 +46,7 @@ sp-crypto-hashing-proc-macro = { path = "../../primitives/crypto/hashing/proc-ma k256 = { version = "0.13.1", default-features = false, features = ["ecdsa"] } environmental = { version = "1.1.4", default-features = false } sp-genesis-builder = { path = "../../primitives/genesis-builder", default-features = false } -serde_json = { version = "1.0.111", default-features = false, features = ["alloc"] } +serde_json = { version = "1.0.113", default-features = false, features = ["alloc"] } docify = "0.2.7" static_assertions = "1.1.0" diff --git a/substrate/frame/support/procedural/Cargo.toml b/substrate/frame/support/procedural/Cargo.toml index 5b7fbb3fcaa..f3434bd2491 100644 --- a/substrate/frame/support/procedural/Cargo.toml +++ b/substrate/frame/support/procedural/Cargo.toml @@ -24,7 +24,7 @@ cfg-expr = "0.15.5" itertools = "0.10.3" proc-macro2 = "1.0.56" quote = "1.0.28" -syn = { version = "2.0.48", features = ["full", "visit-mut"] } +syn = { version = "2.0.49", features = ["full", "visit-mut"] } frame-support-procedural-tools = { path = "tools" } macro_magic = { version = "0.5.0", features = ["proc_support"] } proc-macro-warning = { version = "1.0.0", default-features = false } diff --git a/substrate/frame/support/procedural/tools/Cargo.toml b/substrate/frame/support/procedural/tools/Cargo.toml index 0a046a164b6..9be5ca0bfd4 100644 --- a/substrate/frame/support/procedural/tools/Cargo.toml +++ b/substrate/frame/support/procedural/tools/Cargo.toml @@ -18,5 +18,5 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro-crate = "3.0.0" proc-macro2 = "1.0.56" quote = "1.0.28" -syn = { version = "2.0.48", features = ["extra-traits", "full", "visit"] } +syn = { version = "2.0.49", features = ["extra-traits", "full", "visit"] } frame-support-procedural-tools-derive = { path = "derive" } diff --git a/substrate/frame/support/procedural/tools/derive/Cargo.toml b/substrate/frame/support/procedural/tools/derive/Cargo.toml index 08c6a95a34f..bb9935ba58c 100644 --- a/substrate/frame/support/procedural/tools/derive/Cargo.toml +++ b/substrate/frame/support/procedural/tools/derive/Cargo.toml @@ -20,4 +20,4 @@ proc-macro = true [dependencies] proc-macro2 = "1.0.56" quote = { version = "1.0.28", features = ["proc-macro"] } -syn = { version = "2.0.48", features = ["extra-traits", "full", "parsing", "proc-macro"] } +syn = { version = "2.0.49", features = ["extra-traits", "full", "parsing", "proc-macro"] } diff --git a/substrate/frame/support/test/Cargo.toml b/substrate/frame/support/test/Cargo.toml index 8b61f25f569..1dbf46109c8 100644 --- a/substrate/frame/support/test/Cargo.toml +++ b/substrate/frame/support/test/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] static_assertions = "1.1.0" -serde = { version = "1.0.195", default-features = false, features = ["derive"] } +serde = { version = "1.0.196", default-features = false, features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-metadata = { version = "16.0.0", default-features = false, features = ["current"] } diff --git a/substrate/frame/support/test/pallet/Cargo.toml b/substrate/frame/support/test/pallet/Cargo.toml index 48adbcab582..94afab76d89 100644 --- a/substrate/frame/support/test/pallet/Cargo.toml +++ b/substrate/frame/support/test/pallet/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, features = ["derive"] } +serde = { version = "1.0.196", default-features = false, features = ["derive"] } frame-support = { path = "../..", default-features = false } frame-system = { path = "../../../system", default-features = false } sp-runtime = { path = "../../../../primitives/runtime", default-features = false } diff --git a/substrate/frame/system/Cargo.toml b/substrate/frame/system/Cargo.toml index d25746dcde8..b852aa99b97 100644 --- a/substrate/frame/system/Cargo.toml +++ b/substrate/frame/system/Cargo.toml @@ -20,7 +20,7 @@ cfg-if = "1.0" codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"] } +serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"] } frame-support = { path = "../support", default-features = false } sp-core = { path = "../../primitives/core", default-features = false, features = ["serde"] } sp-io = { path = "../../primitives/io", default-features = false } diff --git a/substrate/frame/tips/Cargo.toml b/substrate/frame/tips/Cargo.toml index 65931bf2d59..ae308cf67f0 100644 --- a/substrate/frame/tips/Cargo.toml +++ b/substrate/frame/tips/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", features = ["derive"], optional = true } +serde = { version = "1.0.196", features = ["derive"], optional = true } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/transaction-payment/Cargo.toml b/substrate/frame/transaction-payment/Cargo.toml index 4b30f8e755f..56c1cc4caa7 100644 --- a/substrate/frame/transaction-payment/Cargo.toml +++ b/substrate/frame/transaction-payment/Cargo.toml @@ -20,7 +20,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = "derive", ] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true } +serde = { version = "1.0.196", optional = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } sp-core = { path = "../../primitives/core", default-features = false } @@ -29,7 +29,7 @@ sp-runtime = { path = "../../primitives/runtime", default-features = false } sp-std = { path = "../../primitives/std", default-features = false } [dev-dependencies] -serde_json = "1.0.111" +serde_json = "1.0.113" pallet-balances = { path = "../balances" } [features] diff --git a/substrate/frame/transaction-payment/asset-tx-payment/Cargo.toml b/substrate/frame/transaction-payment/asset-tx-payment/Cargo.toml index f7114e8f24b..9a80081e04f 100644 --- a/substrate/frame/transaction-payment/asset-tx-payment/Cargo.toml +++ b/substrate/frame/transaction-payment/asset-tx-payment/Cargo.toml @@ -30,10 +30,10 @@ frame-benchmarking = { path = "../../benchmarking", default-features = false, op # Other dependencies codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true } +serde = { version = "1.0.196", optional = true } [dev-dependencies] -serde_json = "1.0.111" +serde_json = "1.0.113" sp-storage = { path = "../../../primitives/storage", default-features = false } diff --git a/substrate/frame/transaction-storage/Cargo.toml b/substrate/frame/transaction-storage/Cargo.toml index 8ae77cae79f..59b6e578571 100644 --- a/substrate/frame/transaction-storage/Cargo.toml +++ b/substrate/frame/transaction-storage/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] array-bytes = { version = "6.1", optional = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true } +serde = { version = "1.0.196", optional = true } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/treasury/Cargo.toml b/substrate/frame/treasury/Cargo.toml index 2dc603e9f9b..edc68b386bc 100644 --- a/substrate/frame/treasury/Cargo.toml +++ b/substrate/frame/treasury/Cargo.toml @@ -23,7 +23,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = docify = "0.2.7" impl-trait-for-tuples = "0.2.2" scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", features = ["derive"], optional = true } +serde = { version = "1.0.196", features = ["derive"], optional = true } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/primitives/api/proc-macro/Cargo.toml b/substrate/primitives/api/proc-macro/Cargo.toml index 166ce55e515..f0644eea8f4 100644 --- a/substrate/primitives/api/proc-macro/Cargo.toml +++ b/substrate/primitives/api/proc-macro/Cargo.toml @@ -20,7 +20,7 @@ proc-macro = true [dependencies] quote = "1.0.28" -syn = { version = "2.0.48", features = ["extra-traits", "fold", "full", "visit"] } +syn = { version = "2.0.49", features = ["extra-traits", "fold", "full", "visit"] } proc-macro2 = "1.0.56" blake2 = { version = "0.10.4", default-features = false } proc-macro-crate = "3.0.0" diff --git a/substrate/primitives/application-crypto/Cargo.toml b/substrate/primitives/application-crypto/Cargo.toml index 2b38b59b4da..cf57aa4b76d 100644 --- a/substrate/primitives/application-crypto/Cargo.toml +++ b/substrate/primitives/application-crypto/Cargo.toml @@ -21,7 +21,7 @@ targets = ["x86_64-unknown-linux-gnu"] sp-core = { path = "../core", default-features = false } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, optional = true, features = ["alloc", "derive"] } +serde = { version = "1.0.196", default-features = false, optional = true, features = ["alloc", "derive"] } sp-std = { path = "../std", default-features = false } sp-io = { path = "../io", default-features = false } diff --git a/substrate/primitives/arithmetic/Cargo.toml b/substrate/primitives/arithmetic/Cargo.toml index 332646710ea..92f9d23620e 100644 --- a/substrate/primitives/arithmetic/Cargo.toml +++ b/substrate/primitives/arithmetic/Cargo.toml @@ -24,7 +24,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = integer-sqrt = "0.1.2" num-traits = { version = "0.2.17", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"], optional = true } static_assertions = "1.1.0" sp-std = { path = "../std", default-features = false } diff --git a/substrate/primitives/consensus/babe/Cargo.toml b/substrate/primitives/consensus/babe/Cargo.toml index 9409b44ef62..f6bc94e3adf 100644 --- a/substrate/primitives/consensus/babe/Cargo.toml +++ b/substrate/primitives/consensus/babe/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] async-trait = { version = "0.1.74", optional = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"], optional = true } sp-api = { path = "../../api", default-features = false } sp-application-crypto = { path = "../../application-crypto", default-features = false } sp-consensus-slots = { path = "../slots", default-features = false } diff --git a/substrate/primitives/consensus/beefy/Cargo.toml b/substrate/primitives/consensus/beefy/Cargo.toml index 9fd9ce5d123..38fc67b7f52 100644 --- a/substrate/primitives/consensus/beefy/Cargo.toml +++ b/substrate/primitives/consensus/beefy/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, optional = true, features = ["alloc", "derive"] } +serde = { version = "1.0.196", default-features = false, optional = true, features = ["alloc", "derive"] } sp-api = { path = "../../api", default-features = false } sp-application-crypto = { path = "../../application-crypto", default-features = false } sp-core = { path = "../../core", default-features = false } diff --git a/substrate/primitives/consensus/grandpa/Cargo.toml b/substrate/primitives/consensus/grandpa/Cargo.toml index 1398cb93564..b556b43bc95 100644 --- a/substrate/primitives/consensus/grandpa/Cargo.toml +++ b/substrate/primitives/consensus/grandpa/Cargo.toml @@ -21,7 +21,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = grandpa = { package = "finality-grandpa", version = "0.16.2", default-features = false, features = ["derive-codec"] } log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", features = ["alloc", "derive"], default-features = false, optional = true } +serde = { version = "1.0.196", features = ["alloc", "derive"], default-features = false, optional = true } sp-api = { path = "../../api", default-features = false } sp-application-crypto = { path = "../../application-crypto", default-features = false } sp-core = { path = "../../core", default-features = false } diff --git a/substrate/primitives/consensus/sassafras/Cargo.toml b/substrate/primitives/consensus/sassafras/Cargo.toml index 6d44bc6c5a8..4c908201127 100644 --- a/substrate/primitives/consensus/sassafras/Cargo.toml +++ b/substrate/primitives/consensus/sassafras/Cargo.toml @@ -20,7 +20,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] scale-codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, features = ["derive"], optional = true } +serde = { version = "1.0.196", default-features = false, features = ["derive"], optional = true } sp-api = { path = "../../api", default-features = false } sp-application-crypto = { path = "../../application-crypto", default-features = false, features = ["bandersnatch-experimental"] } sp-consensus-slots = { path = "../slots", default-features = false } diff --git a/substrate/primitives/core/Cargo.toml b/substrate/primitives/core/Cargo.toml index 25c69253354..c06dd9197ed 100644 --- a/substrate/primitives/core/Cargo.toml +++ b/substrate/primitives/core/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } log = { workspace = true } -serde = { version = "1.0.195", optional = true, default-features = false, features = ["alloc", "derive"] } +serde = { version = "1.0.196", optional = true, default-features = false, features = ["alloc", "derive"] } bounded-collections = { version = "0.2.0", default-features = false } primitive-types = { version = "0.12.0", default-features = false, features = ["codec", "scale-info"] } impl-serde = { version = "0.4.0", default-features = false, optional = true } @@ -63,7 +63,7 @@ bandersnatch_vrfs = { git = "https://github.com/w3f/ring-vrf", rev = "e9782f9", [dev-dependencies] criterion = "0.4.0" -serde_json = "1.0.111" +serde_json = "1.0.113" lazy_static = "1.4.0" regex = "1.6.0" diff --git a/substrate/primitives/crypto/hashing/proc-macro/Cargo.toml b/substrate/primitives/crypto/hashing/proc-macro/Cargo.toml index 179b70048a7..2856c93c7ee 100644 --- a/substrate/primitives/crypto/hashing/proc-macro/Cargo.toml +++ b/substrate/primitives/crypto/hashing/proc-macro/Cargo.toml @@ -20,5 +20,5 @@ proc-macro = true [dependencies] quote = "1.0.28" -syn = { version = "2.0.48", features = ["full", "parsing"] } +syn = { version = "2.0.49", features = ["full", "parsing"] } sp-crypto-hashing = { path = "..", default-features = false } diff --git a/substrate/primitives/debug-derive/Cargo.toml b/substrate/primitives/debug-derive/Cargo.toml index 2a18f505fcb..aaf5e618189 100644 --- a/substrate/primitives/debug-derive/Cargo.toml +++ b/substrate/primitives/debug-derive/Cargo.toml @@ -20,7 +20,7 @@ proc-macro = true [dependencies] quote = "1.0.28" -syn = "2.0.48" +syn = "2.0.49" proc-macro2 = "1.0.56" [features] diff --git a/substrate/primitives/genesis-builder/Cargo.toml b/substrate/primitives/genesis-builder/Cargo.toml index 1967e9ff885..6497465a881 100644 --- a/substrate/primitives/genesis-builder/Cargo.toml +++ b/substrate/primitives/genesis-builder/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] sp-api = { path = "../api", default-features = false } sp-runtime = { path = "../runtime", default-features = false } sp-std = { path = "../std", default-features = false } -serde_json = { version = "1.0.111", default-features = false, features = ["alloc", "arbitrary_precision"] } +serde_json = { version = "1.0.113", default-features = false, features = ["alloc", "arbitrary_precision"] } [features] default = ["std"] diff --git a/substrate/primitives/merkle-mountain-range/Cargo.toml b/substrate/primitives/merkle-mountain-range/Cargo.toml index 66dc762ec28..f859bb5514e 100644 --- a/substrate/primitives/merkle-mountain-range/Cargo.toml +++ b/substrate/primitives/merkle-mountain-range/Cargo.toml @@ -19,7 +19,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } log = { workspace = true } mmr-lib = { package = "ckb-merkle-mountain-range", version = "0.5.2", default-features = false } -serde = { version = "1.0.195", features = ["alloc", "derive"], default-features = false, optional = true } +serde = { version = "1.0.196", features = ["alloc", "derive"], default-features = false, optional = true } sp-api = { path = "../api", default-features = false } sp-core = { path = "../core", default-features = false } sp-debug-derive = { path = "../debug-derive", default-features = false } diff --git a/substrate/primitives/npos-elections/Cargo.toml b/substrate/primitives/npos-elections/Cargo.toml index 74423ea556c..8c3aee1d086 100644 --- a/substrate/primitives/npos-elections/Cargo.toml +++ b/substrate/primitives/npos-elections/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"], optional = true } sp-arithmetic = { path = "../arithmetic", default-features = false } sp-core = { path = "../core", default-features = false } sp-runtime = { path = "../runtime", default-features = false } diff --git a/substrate/primitives/npos-elections/fuzzer/Cargo.toml b/substrate/primitives/npos-elections/fuzzer/Cargo.toml index 93177325436..fc231a535f2 100644 --- a/substrate/primitives/npos-elections/fuzzer/Cargo.toml +++ b/substrate/primitives/npos-elections/fuzzer/Cargo.toml @@ -17,7 +17,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.0", features = ["derive"] } honggfuzz = "0.5" rand = { version = "0.8", features = ["small_rng", "std"] } sp-npos-elections = { path = ".." } diff --git a/substrate/primitives/rpc/Cargo.toml b/substrate/primitives/rpc/Cargo.toml index 735f8ed32da..04bbe3eea53 100644 --- a/substrate/primitives/rpc/Cargo.toml +++ b/substrate/primitives/rpc/Cargo.toml @@ -17,8 +17,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] rustc-hash = "1.1.0" -serde = { version = "1.0.195", features = ["derive"] } +serde = { version = "1.0.196", features = ["derive"] } sp-core = { path = "../core" } [dev-dependencies] -serde_json = "1.0.111" +serde_json = "1.0.113" diff --git a/substrate/primitives/runtime-interface/proc-macro/Cargo.toml b/substrate/primitives/runtime-interface/proc-macro/Cargo.toml index c453f9079bb..8031ba75b9b 100644 --- a/substrate/primitives/runtime-interface/proc-macro/Cargo.toml +++ b/substrate/primitives/runtime-interface/proc-macro/Cargo.toml @@ -24,4 +24,4 @@ proc-macro-crate = "3.0.0" proc-macro2 = "1.0.56" quote = "1.0.28" expander = "2.0.0" -syn = { version = "2.0.48", features = ["extra-traits", "fold", "full", "visit"] } +syn = { version = "2.0.49", features = ["extra-traits", "fold", "full", "visit"] } diff --git a/substrate/primitives/runtime/Cargo.toml b/substrate/primitives/runtime/Cargo.toml index f42d8d77d50..8e3b63a55de 100644 --- a/substrate/primitives/runtime/Cargo.toml +++ b/substrate/primitives/runtime/Cargo.toml @@ -25,7 +25,7 @@ log = { workspace = true } paste = "1.0" rand = { version = "0.8.5", optional = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"], optional = true } sp-application-crypto = { path = "../application-crypto", default-features = false } sp-arithmetic = { path = "../arithmetic", default-features = false } sp-core = { path = "../core", default-features = false } @@ -38,7 +38,7 @@ simple-mermaid = { version = "0.1.1", optional = true } [dev-dependencies] rand = "0.8.5" -serde_json = "1.0.111" +serde_json = "1.0.113" zstd = { version = "0.12.4", default-features = false } sp-api = { path = "../api" } sp-state-machine = { path = "../state-machine" } diff --git a/substrate/primitives/staking/Cargo.toml b/substrate/primitives/staking/Cargo.toml index e1ab87bef09..9429432babb 100644 --- a/substrate/primitives/staking/Cargo.toml +++ b/substrate/primitives/staking/Cargo.toml @@ -16,7 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"], optional = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } impl-trait-for-tuples = "0.2.2" diff --git a/substrate/primitives/storage/Cargo.toml b/substrate/primitives/storage/Cargo.toml index 4c2f7cadefe..e15a3296d52 100644 --- a/substrate/primitives/storage/Cargo.toml +++ b/substrate/primitives/storage/Cargo.toml @@ -20,7 +20,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } impl-serde = { version = "0.4.0", optional = true, default-features = false } ref-cast = "1.0.0" -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"], optional = true } sp-debug-derive = { path = "../debug-derive", default-features = false } sp-std = { path = "../std", default-features = false } diff --git a/substrate/primitives/test-primitives/Cargo.toml b/substrate/primitives/test-primitives/Cargo.toml index 3649217cf74..0411326c28b 100644 --- a/substrate/primitives/test-primitives/Cargo.toml +++ b/substrate/primitives/test-primitives/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, features = ["derive"], optional = true } +serde = { version = "1.0.196", default-features = false, features = ["derive"], optional = true } sp-application-crypto = { path = "../application-crypto", default-features = false } sp-core = { path = "../core", default-features = false } sp-runtime = { path = "../runtime", default-features = false } diff --git a/substrate/primitives/version/Cargo.toml b/substrate/primitives/version/Cargo.toml index 7de762d71e9..21f95776375 100644 --- a/substrate/primitives/version/Cargo.toml +++ b/substrate/primitives/version/Cargo.toml @@ -21,7 +21,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = impl-serde = { version = "0.4.0", default-features = false, optional = true } parity-wasm = { version = "0.45", optional = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"], optional = true } thiserror = { version = "1.0.48", optional = true } sp-crypto-hashing-proc-macro = { path = "../crypto/hashing/proc-macro" } sp-runtime = { path = "../runtime", default-features = false } diff --git a/substrate/primitives/version/proc-macro/Cargo.toml b/substrate/primitives/version/proc-macro/Cargo.toml index 9e90940825d..b99894f8ab5 100644 --- a/substrate/primitives/version/proc-macro/Cargo.toml +++ b/substrate/primitives/version/proc-macro/Cargo.toml @@ -22,7 +22,7 @@ proc-macro = true codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } proc-macro2 = "1.0.56" quote = "1.0.28" -syn = { version = "2.0.48", features = ["extra-traits", "fold", "full", "visit"] } +syn = { version = "2.0.49", features = ["extra-traits", "fold", "full", "visit"] } [dev-dependencies] sp-version = { path = ".." } diff --git a/substrate/primitives/weights/Cargo.toml b/substrate/primitives/weights/Cargo.toml index 3a57720b964..87e0c6a7299 100644 --- a/substrate/primitives/weights/Cargo.toml +++ b/substrate/primitives/weights/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] bounded-collections = { version = "0.2.0", default-features = false } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, optional = true, features = ["alloc", "derive"] } +serde = { version = "1.0.196", default-features = false, optional = true, features = ["alloc", "derive"] } smallvec = "1.11.0" sp-arithmetic = { path = "../arithmetic", default-features = false } sp-debug-derive = { path = "../debug-derive", default-features = false } diff --git a/substrate/scripts/ci/node-template-release/Cargo.toml b/substrate/scripts/ci/node-template-release/Cargo.toml index a0b93184546..c96da2ed426 100644 --- a/substrate/scripts/ci/node-template-release/Cargo.toml +++ b/substrate/scripts/ci/node-template-release/Cargo.toml @@ -14,7 +14,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.0", features = ["derive"] } flate2 = "1.0" fs_extra = "1.3" glob = "0.3" diff --git a/substrate/test-utils/client/Cargo.toml b/substrate/test-utils/client/Cargo.toml index 14f55f64546..4ec2857b109 100644 --- a/substrate/test-utils/client/Cargo.toml +++ b/substrate/test-utils/client/Cargo.toml @@ -20,8 +20,8 @@ array-bytes = "6.1" async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" -serde = "1.0.195" -serde_json = "1.0.111" +serde = "1.0.196" +serde_json = "1.0.113" sc-client-api = { path = "../../client/api" } sc-client-db = { path = "../../client/db", default-features = false, features = [ "test-helpers", diff --git a/substrate/test-utils/runtime/Cargo.toml b/substrate/test-utils/runtime/Cargo.toml index cdc94782ec5..49b84e04dab 100644 --- a/substrate/test-utils/runtime/Cargo.toml +++ b/substrate/test-utils/runtime/Cargo.toml @@ -62,8 +62,8 @@ sc-executor-common = { path = "../../client/executor/common" } sp-consensus = { path = "../../primitives/consensus/common" } substrate-test-runtime-client = { path = "client" } sp-tracing = { path = "../../primitives/tracing" } -serde = { version = "1.0.195", features = ["alloc", "derive"], default-features = false } -serde_json = { version = "1.0.111", default-features = false, features = ["alloc"] } +serde = { version = "1.0.196", features = ["alloc", "derive"], default-features = false } +serde_json = { version = "1.0.113", default-features = false, features = ["alloc"] } [build-dependencies] substrate-wasm-builder = { path = "../../utils/wasm-builder", optional = true } diff --git a/substrate/utils/frame/benchmarking-cli/Cargo.toml b/substrate/utils/frame/benchmarking-cli/Cargo.toml index 2af8e055cf2..68ecb0612ce 100644 --- a/substrate/utils/frame/benchmarking-cli/Cargo.toml +++ b/substrate/utils/frame/benchmarking-cli/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] array-bytes = "6.1" chrono = "0.4" -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.0", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.6.1" } comfy-table = { version = "7.1.0", default-features = false } handlebars = "4.2.2" @@ -29,8 +29,8 @@ linked-hash-map = "0.5.4" log = { workspace = true, default-features = true } rand = { version = "0.8.5", features = ["small_rng"] } rand_pcg = "0.3.1" -serde = "1.0.195" -serde_json = "1.0.111" +serde = "1.0.196" +serde_json = "1.0.113" thiserror = "1.0.48" thousands = "0.2.0" frame-benchmarking = { path = "../../../frame/benchmarking" } diff --git a/substrate/utils/frame/frame-utilities-cli/Cargo.toml b/substrate/utils/frame/frame-utilities-cli/Cargo.toml index 87e3a50753f..3f5e8e48e0f 100644 --- a/substrate/utils/frame/frame-utilities-cli/Cargo.toml +++ b/substrate/utils/frame/frame-utilities-cli/Cargo.toml @@ -14,7 +14,7 @@ readme = "README.md" workspace = true [dependencies] -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.0", features = ["derive"] } frame-support = { path = "../../../frame/support" } frame-system = { path = "../../../frame/system" } sc-cli = { path = "../../../client/cli" } diff --git a/substrate/utils/frame/generate-bags/node-runtime/Cargo.toml b/substrate/utils/frame/generate-bags/node-runtime/Cargo.toml index b35e06879df..8604bab1ce5 100644 --- a/substrate/utils/frame/generate-bags/node-runtime/Cargo.toml +++ b/substrate/utils/frame/generate-bags/node-runtime/Cargo.toml @@ -17,4 +17,4 @@ kitchensink-runtime = { path = "../../../../bin/node/runtime" } generate-bags = { path = ".." } # third-party -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.0", features = ["derive"] } diff --git a/substrate/utils/frame/remote-externalities/Cargo.toml b/substrate/utils/frame/remote-externalities/Cargo.toml index a3376c7af40..c96d37d4ae6 100644 --- a/substrate/utils/frame/remote-externalities/Cargo.toml +++ b/substrate/utils/frame/remote-externalities/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] jsonrpsee = { version = "0.22", features = ["http-client"] } codec = { package = "parity-scale-codec", version = "3.6.1" } log = { workspace = true, default-features = true } -serde = "1.0.195" +serde = "1.0.196" sp-core = { path = "../../../primitives/core" } sp-crypto-hashing = { path = "../../../primitives/crypto/hashing" } sp-state-machine = { path = "../../../primitives/state-machine" } diff --git a/substrate/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml b/substrate/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml index b2004bfcacd..58c2f3d99ac 100644 --- a/substrate/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml +++ b/substrate/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml @@ -32,4 +32,4 @@ sc-rpc-api = { path = "../../../../client/rpc-api" } sp-runtime = { path = "../../../../primitives/runtime" } [dev-dependencies] -serde_json = "1.0.111" +serde_json = "1.0.113" diff --git a/substrate/utils/frame/try-runtime/cli/Cargo.toml b/substrate/utils/frame/try-runtime/cli/Cargo.toml index 1c8484f9a80..998d0790490 100644 --- a/substrate/utils/frame/try-runtime/cli/Cargo.toml +++ b/substrate/utils/frame/try-runtime/cli/Cargo.toml @@ -38,12 +38,12 @@ frame-try-runtime = { path = "../../../../frame/try-runtime", optional = true } substrate-rpc-client = { path = "../../rpc/client" } async-trait = "0.1.74" -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.0", features = ["derive"] } hex = { version = "0.4.3", default-features = false } log = { workspace = true, default-features = true } parity-scale-codec = "3.6.1" -serde = "1.0.195" -serde_json = "1.0.111" +serde = "1.0.196" +serde_json = "1.0.113" zstd = { version = "0.12.4", default-features = false } [dev-dependencies] -- GitLab From b8ad0d1f565659f004165c5244acba78828d0bf7 Mon Sep 17 00:00:00 2001 From: Hugo Trentesaux Date: Fri, 16 Feb 2024 13:00:03 +0100 Subject: [PATCH 072/133] Update subkey README.md (#3355) fix typo --- substrate/bin/utils/subkey/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/bin/utils/subkey/README.md b/substrate/bin/utils/subkey/README.md index 3232c829587..a5f27cfd370 100644 --- a/substrate/bin/utils/subkey/README.md +++ b/substrate/bin/utils/subkey/README.md @@ -91,7 +91,7 @@ SS58 addresses are: ### Json output -`subkey` can calso generate the output as *json*. This is useful for automation. +`subkey` can also generate the output as *json*. This is useful for automation. command: -- GitLab From 34352e82cf557f20375c1757a2d934e3a9d2a6b0 Mon Sep 17 00:00:00 2001 From: Xiliang Chen Date: Sat, 17 Feb 2024 01:39:42 +1300 Subject: [PATCH 073/133] remove recursion limit (#3348) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit According to the [doc](https://doc.rust-lang.org/reference/attributes/limits.html), the default is 128, so no point to specify limit with 128 Co-authored-by: Bastian Köcher --- substrate/client/executor/src/lib.rs | 1 - substrate/frame/collective/src/lib.rs | 1 - substrate/frame/core-fellowship/src/lib.rs | 1 - substrate/frame/ranked-collective/src/lib.rs | 1 - substrate/frame/salary/src/lib.rs | 1 - substrate/frame/support/procedural/tools/derive/src/lib.rs | 2 -- 6 files changed, 7 deletions(-) diff --git a/substrate/client/executor/src/lib.rs b/substrate/client/executor/src/lib.rs index 25bad81938f..6b99f0a6ee0 100644 --- a/substrate/client/executor/src/lib.rs +++ b/substrate/client/executor/src/lib.rs @@ -29,7 +29,6 @@ //! wasm engine used, instance cache. #![warn(missing_docs)] -#![recursion_limit = "128"] #[macro_use] mod executor; diff --git a/substrate/frame/collective/src/lib.rs b/substrate/frame/collective/src/lib.rs index 10f989e5c4c..c084784e0a9 100644 --- a/substrate/frame/collective/src/lib.rs +++ b/substrate/frame/collective/src/lib.rs @@ -40,7 +40,6 @@ //! If there are not, or if no prime is set, then the motion is dropped without being executed. #![cfg_attr(not(feature = "std"), no_std)] -#![recursion_limit = "128"] use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; diff --git a/substrate/frame/core-fellowship/src/lib.rs b/substrate/frame/core-fellowship/src/lib.rs index e3924594321..d1b81c3ca13 100644 --- a/substrate/frame/core-fellowship/src/lib.rs +++ b/substrate/frame/core-fellowship/src/lib.rs @@ -56,7 +56,6 @@ //! cannot be approved - they must proceed only to promotion prior to the offboard timeout elapsing. #![cfg_attr(not(feature = "std"), no_std)] -#![recursion_limit = "128"] use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; diff --git a/substrate/frame/ranked-collective/src/lib.rs b/substrate/frame/ranked-collective/src/lib.rs index 2b4fb2dbff4..ceaf03de211 100644 --- a/substrate/frame/ranked-collective/src/lib.rs +++ b/substrate/frame/ranked-collective/src/lib.rs @@ -39,7 +39,6 @@ //! least a particular rank. #![cfg_attr(not(feature = "std"), no_std)] -#![recursion_limit = "128"] use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; diff --git a/substrate/frame/salary/src/lib.rs b/substrate/frame/salary/src/lib.rs index f83a75dd97d..efb4f5d3c54 100644 --- a/substrate/frame/salary/src/lib.rs +++ b/substrate/frame/salary/src/lib.rs @@ -18,7 +18,6 @@ //! Make periodic payment to members of a ranked collective according to rank. #![cfg_attr(not(feature = "std"), no_std)] -#![recursion_limit = "128"] use codec::{Decode, Encode, MaxEncodedLen}; use core::marker::PhantomData; diff --git a/substrate/frame/support/procedural/tools/derive/src/lib.rs b/substrate/frame/support/procedural/tools/derive/src/lib.rs index f7c57c08674..a3590263558 100644 --- a/substrate/frame/support/procedural/tools/derive/src/lib.rs +++ b/substrate/frame/support/procedural/tools/derive/src/lib.rs @@ -19,8 +19,6 @@ //! Use to derive parsing for parsing struct. // end::description[] -#![recursion_limit = "128"] - use proc_macro::TokenStream; use proc_macro2::Span; use quote::quote; -- GitLab From 99234440f0f8b24f7e4d1d3a0102a9b19a408dd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=B3nal=20Murray?= Date: Fri, 16 Feb 2024 13:05:15 +0000 Subject: [PATCH 074/133] Add broker pallet to `coretime-westend` (#3272) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This brings functionality to Westend's Coretime Chain runtime, where previously it was not much more than a shell. It is assumed that the Coretime pallet will have the same index in the Westend runtime as it does in Rococo for the runtime calls. TODO: - [x] Generate chainspec - [x] Regenerate weights - [x] Check hardcoded RuntimeCall weights against relay weights for transacts Aura key generation: https://github.com/paritytech/devops/issues/2725 --------- Co-authored-by: command-bot <> Co-authored-by: Bastian Köcher Co-authored-by: Anton Vilhelm Ásgeirsson --- Cargo.lock | 4 +- .../chain-specs/coretime-westend.json | 71 +++ .../coretime/coretime-rococo/Cargo.toml | 2 - .../coretime/coretime-rococo/src/coretime.rs | 5 +- .../coretime-rococo/src/weights/xcm/mod.rs | 2 +- .../xcm/pallet_xcm_benchmarks_generic.rs | 10 - .../coretime/coretime-westend/Cargo.toml | 12 +- .../coretime/coretime-westend/build.rs | 10 +- .../coretime/coretime-westend/src/coretime.rs | 249 +++++++++ .../coretime/coretime-westend/src/lib.rs | 55 +- .../cumulus_pallet_parachain_system.rs | 70 ++- .../src/weights/cumulus_pallet_xcmp_queue.rs | 60 +- .../src/weights/frame_system.rs | 143 +++-- .../coretime-westend/src/weights/mod.rs | 3 +- .../src/weights/pallet_balances.rs | 60 +- .../src/weights/pallet_broker.rs | 516 ++++++++++++++++++ .../src/weights/pallet_collator_selection.rs | 356 ++++++------ .../src/weights/pallet_message_queue.rs | 165 +++--- .../src/weights/pallet_multisig.rs | 120 ++-- .../src/weights/pallet_session.rs | 56 +- .../src/weights/pallet_timestamp.rs | 38 +- .../src/weights/pallet_utility.rs | 52 +- .../src/weights/pallet_xcm.rs | 369 +++++++------ .../coretime-westend/src/weights/xcm/mod.rs | 2 +- .../xcm/pallet_xcm_benchmarks_fungible.rs | 150 +++-- .../xcm/pallet_xcm_benchmarks_generic.rs | 239 ++++---- .../coretime-westend/src/xcm_config.rs | 69 ++- .../chain-specs/coretime-rococo.json | 1 + .../chain-specs/coretime-westend.json | 1 + .../src/chain_spec/coretime.rs | 12 +- cumulus/polkadot-parachain/src/command.rs | 1 + .../scripts/create_coretime_westend_spec.sh | 30 +- prdoc/pr_3272.prdoc | 11 + substrate/bin/node/runtime/src/lib.rs | 2 +- .../frame/broker/src/coretime_interface.rs | 6 +- substrate/frame/broker/src/mock.rs | 2 +- 36 files changed, 1967 insertions(+), 987 deletions(-) create mode 100644 cumulus/parachains/chain-specs/coretime-westend.json create mode 100644 cumulus/parachains/runtimes/coretime/coretime-westend/src/coretime.rs create mode 100644 cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_broker.rs create mode 120000 cumulus/polkadot-parachain/chain-specs/coretime-rococo.json create mode 120000 cumulus/polkadot-parachain/chain-specs/coretime-westend.json create mode 100644 prdoc/pr_3272.prdoc diff --git a/Cargo.lock b/Cargo.lock index 32b8217bba6..0d55487f8f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3110,7 +3110,6 @@ dependencies = [ "pallet-xcm-benchmarks", "parachains-common", "parity-scale-codec", - "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-runtime-common", "rococo-runtime-constants", @@ -3161,11 +3160,11 @@ dependencies = [ "pallet-aura", "pallet-authorship", "pallet-balances", + "pallet-broker", "pallet-collator-selection", "pallet-message-queue", "pallet-multisig", "pallet-session", - "pallet-sudo", "pallet-timestamp", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", @@ -3174,7 +3173,6 @@ dependencies = [ "pallet-xcm-benchmarks", "parachains-common", "parity-scale-codec", - "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-runtime-common", "scale-info", diff --git a/cumulus/parachains/chain-specs/coretime-westend.json b/cumulus/parachains/chain-specs/coretime-westend.json new file mode 100644 index 00000000000..92e119b935f --- /dev/null +++ b/cumulus/parachains/chain-specs/coretime-westend.json @@ -0,0 +1,71 @@ +{ + "name": "Westend Coretime", + "id": "coretime-westend", + "chainType": "Live", + "bootNodes": [ + "/dns/westend-coretime-collator-0.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWP93Dzk8T7GWxyWw9jhLcz8Pksokk3R9vL2eEH337bNkT", + "/dns/westend-coretime-collator-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWMh2imeAzsZKGQgm2cv6Uoep3GBYtwGfujt1bs5YfVzkH" + ], + "telemetryEndpoints": null, + "protocolId": null, + "properties": { + "ss58Format": 42, + "tokenDecimals": 12, + "tokenSymbol": "WND" + }, + "relay_chain": "westend", + "para_id": 1005, + "codeSubstitutes": {}, + "genesis": { + "raw": { + "top": { + "0x0d715f2646c8f85767b5d2764bb2782604a74d81251e398fd8a0a4d55023bb3f": "0xed030000", + "0x0d715f2646c8f85767b5d2764bb278264e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x15464cac3378d46f113cd5b7a4d71c84476f594316a7dfe49c1f352d95abdaf1": "0x00000000", + "0x15464cac3378d46f113cd5b7a4d71c844e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x15464cac3378d46f113cd5b7a4d71c845579297f4dfb9609e7e4c2ebab9ce40a": "0x084acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28bc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c", + "0x15464cac3378d46f113cd5b7a4d71c84579f5a43435b04a98d64da0cefe18505": "0x00a0acb9030000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef734abf5cb34d6244378cddbf18e849d96": "0x000000008277f47279c4", + "0x26aa394eea5630e07c48ae0c9558cef74e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x26aa394eea5630e07c48ae0c9558cef75684a022a34dd8bfa2baaf44f172b710": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef78a42f33323cb5ced3b44dd825fda9fcc": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a44704b568d21667356a5a050c118746b4def25cfda6ef3a00000000": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a7fd6c28836b9a28522dc924110cf439": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da998af9d0b9c163cc7caff71526e1e20bf4acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9d834c58cefa69bb125bbc8bef14e90eabc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8": "0x66763d0040636f726574696d652d77657374656e64", + "0x3a63": "0x", + "0x3a636f6465": "0x52bc537646db8e0528b52ffd0058544104aeba85d4125110284d291ddb4a3c50cb1bd0b174da063483a55a47260ab964ae54d7660813796247c0bec7d0f1b338b9c2280c6160568412ae63c3a7815a544782b1d1ea96f47ff926f8d0da2664efbdf7de5b4a99640afb1340115a114f7dbbfcdd2d91be3a3843eef26f979f4522f5a9bd54f4a9d7cb0d9bcee28fe9e3738e1937a9166f6707e9b5f46edf77febca5077e3ac8b3bb558280dee92c0ab13febf7e59ff5341265ce93916b5180787e58439561514395b06f6fd71165cdb70f35547f3b778f192b9116a00609dec11fd56143b1b7f3986840761ad9a7b3d7ed94523adba39a76fbec6eeaddce22ed7e7af7937c0d995e7d7a25abe653b2368cc9727e93533ee5800cc2c835da9e1f1baa6b28aed1f03c7bd850b307c7caa167672626a7fc39c76c4e0d40be609ccece1ec80d35793e3be8e286e267e7866a2fbb67af0d359d1d9c470b50238777f083339c773e8bed6087d48f26a03256a73e9d8a3f26c933680169fc348cbf9d5e6e18759086f3ce6fb19dc54a72c3caeea947358b3f6ac3a84775544775544f9101b36f67f1c7f4a88eead99c330c90994a7fea20076004d982911b568e4f7d3a8b3fa64f110809341a2dca4f073be4869ace5e1baa9d9dc5ae61d4a37adeb061d4798ce5d0536faf5e764f1d1c6a286e183bf5a2869a4f1da9a1a653076758d450fcd4599c1ed5515def93a57732fb265f43ba6fefbec9d790f6eaed95ac9a3fc9da307616854c6fb26c9f1ed551cd4c949cf2e71cb39e330910640e5a401a3e0de3e786b1b3f8a3368c3daa19090499831690864fc3f8b961ec2cfea80d638f6a4602b9416eb01ca7738d86f5ec2c823cd38c5c3be3e727eda9833cd38c5c3be3e727eda9832e76662a27eddb41be53cb38c5ce5f4eda97ecedfd5c3b13e8e74f6f71c8a43d25cb497b76904522e357a7f749fd4a52cdaca7ceb79c3d3bb38fb2fa4bc8f795a4723df51ecfe34bcefa92bd3d8af2e5a79779073b3843be53cb38c5ce5f4eda97ecedfd5c3b13e8e74f6f71c8a43d25cb497b76904522e357a7f749fd4a52cdaca7ceb79c3d3bb38fb2fa4bc8f795a4723d75bef33cbee4ac2fd9dba3285f7e7a9977b0831df29d6fc6e9f3992d609f22953f7516a9c6a72452c3a647755447755447355f6e183b3335393569cf396674f61d70ce19a773c3d839041983713a3b7b4f564c2cd4c8f33d9df1f4e7c9cf139ca73bb0219ea27872f3d4c4539d273a4f443ce1f0b4c3530e4f433c31f1f4e66989271d9ea078eaf234e609cb0909272d6055386501b3024685531930269cc880d581110183038be33405ac0898113029606d6039c06e80e1009b02e6c6490998164e79602cc0528089719a03530186020c4d530db025605f602bc0c2c080683ac1a9052726602748e00706c6c90c9816a7349c02358dd1a48593144e6a38ade1c486531b4e4fc0cc38dd716ac26909a73a4e4738e971f2e324c7c909273a4e28387d71c2d3c4a7890b98104e6838d9718ac2e90427289ca23851717ac1698c130e4e679cc838617162c12907273330139cca38c9e034c5e906a738b019607a9ac86832a3098ca62c9ad468c2d31482a63e4d5d3479d1f4a7e98ba6329ab068b2a1e94b93094d5d9cb434a1d02443530a4d58b01a9cae9ac0346535a191a00b09c468b22241194d5b48f0c7298b0461345d493006cc8b045f34016a4aa3098d26319afc4840869394a62a4d3134e5699aa2e98e930d4d503449d1a4a7694b1315303c4e4338c17112a2c90aa73730289aae34618145e1c4a7898a939ba6354d55c0bc70a23915e164048c0b5809b02d4d7b605d38d560779c8870fac1a98fd316b03c303f4d6a9ace38e9c00488290d263098ca601a83890ca62f98b660f282a90b262e98ee30e5619282c908a6384c3a30e5c0e4860907261b98da303dc174039317262c26354c6498ba3095616a81a904261898d630b9c0f4021316262e4c26305d3189b95f7c0f530a4c3130c970694c3530cdc0748509858bc245e2cab94c5c3a77893be7d6ee1057881bc405e2fe707db83d5c1eee0e57879bc33de21a718bb871ae9d0bc5dd73adb84ddc3a97880be7beb94adc2a2e1557cf9de24a71f35c3cf7ce8de2267167b8692e0bf7858be6ba70cfdc3297cc6de18eb95a2e1707744fb85b6e949be576b9265c2cf7ca03f9184e866739155e854fe17a5c0aaf791e27c2e3f81b5fc2e1b811fe83fbe03d4c1d669c69c66370357ec665f02b7ee5519cb614c6929ea526969c587a620988a51f96725862b394c29218df614985a51296b82c6d59d2a294a5b486d2194a642899a154c65214253f4a5f2881a1d485d21e253d4a6094c228b1a02446898c920b4a65945a505a41898bd2094a584a5e944c58f242c98a5215a5ab243594aebcac78a9f14ae3c5c64b8cd711af35906078055a1ae365c66b8cd717af3290c8bceebca27849f182e285e745c64bce0b89579c5713af33c0aca23cad34dc9ea324849e38a223d4c411124b593c09f184c7688a9215444d10915164c5119a2335de134a583c3d71b405c626e47274550446384611202317261146568caa14f131ca62a4c5088bd116a32ba3128cb81c19717404d195a23ddc15474514e1196243488c100c457d8ab2f02a447f96b03a1a96bc049109a2419c23d221aa82e807222188862082822808223b444010d588b8200a811702a52e2d322d168c6430c2c1ebc304c6d3e2ca706330aac1dab168381a426588b08868445e8e6a445b88ae86b284c21071212ac18333d4c6119c232288a61061116241e805a230b388a01c826a1042230485d00f426b5a68847c10c20aba21888720218286084a130443109a201b82ce04ed10e4264886221d8a70081293b446919b2124b818382a5c6f966018925314c6e6d9a6e8d67c736e6cb991c5c5e8d204e9b004a66313c4c2b7449118b72248ca4867cc1a9918db18e78c4a8c498c7ec6268eb25eb451cf9865d47264e568ca1195a32a631ea32b4654462ce3d558c518c518659432d2c629aeacb1ca48659ce29a19a93052234425c887a21b9ebc28c2228c4224056c83cb82284b51152d354266845c408078159211aa110212af846d885dc412442e2216518bb845cc12061269e119e294300d314a58862845b412b2215209d708a1089f08ed844e84688459a1192296582544214c21fce242c29584501dd71ca41590c020a9809402120a485f904e403201c90b12165217a41290b8206d41d2829405090bd215d215242b485590a8204d11b212d406d1164c663a1b9a0271575a7f5a675a6b5a34b46a50a2c20886a440621df72332114407c795d315477598d2f819e2124251969208ca0aa708d9790aa26b012683530f620c4e622c4dc1c404531da624c23c4c48846088b48454c22a445688aa10f92024035197d04a2885a88790361448088c500b476f846268cd20b442911e2132477184c6bc9e08af6041845796c068d9b0e4e70617ee4c510f37b418d960a31877187910aac2c9f049b1f40248c35219904d50202129a30edd0ca21ba1294d6138ed6064c6bf30ca6189cca7c607e83b23a807a23e45360495099a41c8093050eb05212e2e2236295a654638de0c482d787196f87844e0c0b224c6cb09a336dc9dcd088fcc9398a22994c6006b00db80362cb910c4068705d319a53698c460f2b334e5d3a3848612a0a0183822884c98703a3845528851885088764427c427b819bc2fba1b96c61051e1b4604283e373a914515174055194a52f96bae0d0883d0889c16486d0198e0db7a669ceb5b259e132420805a21d8878f0deb46410fa227442d4b6c5b667fbb389b1f1692db15de1416c58089920a482cbd9b4104a61cb626271218cd62cb9712c82d6b4ae08d1827e0852639406a94c100e4c5a2899095ac18f083242298ca0165c6d2c140e84eb082616967600cf00e5b4c00455b164825318dd1c5896560b5bcd2886a6283a3a5e98ed8d111aa333e0114b347862c612bc3162134a3034d9615a2108081007223a4c6896cec0b06c15d78b1804131bbf41fc42498cae8d77c5e362890c3b05ac07ae886d08d00ca72eac1437be386161a443b744501bd70f412ec0e6785bb87c087aa1b5c56be382e35d8d3d34b5e134466b05cf8a088493174d337834387d0173628985221496ca80fd3004674909d18f52154d59b63b4e3e38f1e03934d1b958ad314c2a0c1161e4c66d68fa21c88c111babc76806a52bdc4aa7c67130ba012ca3c8078fcf520bcec3a7048c872636416cbe2444219ab8040561f120a971fa033be304042c86262d5e1738b27435048df1fc783534a5697202b6a6e88b110d4c67c0d4785e582aec9d5bc57570374a250451318f984a4c246612730b14d9942381a9094c1b924c34217cc003554a38108139c3dce1010ee02001441ab0010316a08020407e680000d8a4338dd031810f02ec78a260039d18e07c9e4170bca6524e9068c0880848a4423062049cf4832552468c7c3b76bcc6bc5e621884241b58c244aa0a8954130f9c247122b5442a08d32b8c0746986c608910254a9820a0ca0329254a98208082d70a3c5e60942841e28112254810a0420855202c915292024b496dc008932a235521809044aa034f2f14d889922448aaa43e302225b501a7d71766b28125498c78a089d408541e40c281bf4e60290f963059620489912422bc94074b9834a06ac9084c3cf0e0935e267cc0c403a90f965e5ed88907a4a68c3871e201a9214b982471e2440a02f785c51d4822c237919a6ac2640244eeab0b1b911a416a040714f9400a04109014a1e055023b99f2402a040fb8b093aa25524da4425892c45f5b9624d9c0eba5853d48e26449922655244819f1801211444062a489d4074a928c109bf280899218d22b0b3b397a61612446905e57cc012348aa907c80a44a6a83a0d7950f245182244955922549885e5646f0408a0429a157156ec20401e18b0a2f6122c208463e509264040d185982c403234944f8aa25521c00410a021f487d3082120f9624591201244d4678a7d714762295a42a49132621548d80e4c64b0a2f6192a40a4993113e3052c544891323494478244d4678a2560f3ce50113251a30225585e483244a903c69f19452a20124554b46d880912a0fa49a24f960491223468c6e9787db93841a3bcf33906b34dacf1935daba31ba31a60893ab2f8ee338ce5faf66aeb6d9b6d74d6cd9b6e759ef76b3b5b5d26a69b7672ba594d6ba44b9569e9e67adc7dc2d669e3cd9329dddade6f6badbf6b4cc936d579eec596bbb5a4aab6d6bd9f6646bd9b265ca5c2957a9b55dab999976dcb6d932b36506bbeea363d7792dcab69d687bb57ad54eae75a3bd7db5daadebdaccd672edd8b2b595bbbba910b6ddad00b66c39cbb6369d6d2dc8cd69ad05396b9939e656c7d6522b766deb51d7b5abd8cc4df986ad5cd74aada5dc9452fa715ba6b4d66eca93a765b66c6dad96d9d619a42e55ae4d992d65e6ae636bed9cb36933536edacd6c9932dba6b59b76b4e575f41bbb6ddb6aedba6da51fed5a2bb3e568a5cc5c7976f735c9dd3d5ae6aee35a29e5aab5b67274d25927b5b5da5a69edbeb0295769d7516e6bbbaf56dbd5d64a9bda6eb6d572b4a394d63a9b562bd6a6b569d30a93a0bdee6e8ea3d4923870e0c0318e5ddbd66a2dedacadd6327365ae73f6e4b6d572d759bb76edb6d6325b6666ae67dbb6b6d666daddd556e6e6e65abbcad56abbc56e55cbcd952bb7654be9f731775dd775b5a3b4d5a2ad16a5fd7ddd5dadb5d2ae56da75dd59f2e356ab7e638bdd5cedf6686d6eea6adaf1b4d64eaf6366facd8e765cd71cc7d1da51ae9572edbaeb2aad1d534adb7237c4d3560f88e7791c672db7c8956b57b641ccd66b668ea3b59963a6cc1cd390b6ba9b2b8b2df2edda97d9dace72edee4a9927a5fcb99832739dddd336cf8e67d755dab5ba47db2d57fd51bd24e6cacdec2580f6a4cc744eee9abbcea3ad27da35ad5cd7ee2a1ddb7250100e4aabb547a2c8f3c68d1b372c775dc7dce2d608a81d830d35edae5dbbae2cd4ccfcddedd47577d35a9b6a9db36bcfd99f2b8afbbaaeeb68eddad559c5da1f336dd767958e9879daca2c8aac8104b567655aab57bd56aab5bdbb52ca89dd4dc55a99593c129794447a80393434bb6eb679ab957bd6aeb3fb6b6b2973b7ede6b176f7e4dab5b97eb3526b5d963f97ebfbe6ecb6dd5fb7adb5dad9b9babfb69c672767bb9bc5b6cc3c6480dadd3dbbbb999916a0766f6cbbd2e68d85b8999bc10c6a57aefd357794bbb9bb6b37a78f6a6464645439cbd9e69edd5d6ba55dbb9b52da5df775b4eba825802deaba5a6bb596eb38dbae0130f52872d12228a6223ae7b41cc7b96cb5185c600590c3e6985c9ddc7459aeb3b655abb575ceca6c6d73d6b6a5ad00749576d552ae56cb6c375b29fd68a594ed275adbb5abe57a6b5a9999adb5fd64c5d315f406b7079a30e180140736902d907a22a5c408094838b034c41680c0448911109624010104264a8c04a0444992291e1638f18055008b4047044e009860078fa00f942409c10323554a4640b201a90f9e9c04a90f962431b2240992103cd8c006989001c8a1a30a4913a9255220349120e40048397122d544040e30f1c04813a9274c3c906a620489930f3c903262c11414060110c10329273d923690a489d4132304b000030c02f081941126552278a089073928f840aa89d4074d981881c2200052499a544d3039c06c8154930d1869c2a40a89074c9428418274015412094c3a50a5048541009c280016180980d41412251a3002c2922421384112824b712049d592119a904203d840d508494224488560a46a4992262218f1c049120e344152e5c429c889d10d0e0013264d8ea63c40a201254a983499600224524d8e3c203565a48a04a9254a48555220181139a97292840429a48d2d58c224499507489c2c41d2a4ca4855120e7080092c020e0009493620d5440452a8091229251ebc9e2c91a2416c8191254c92386922f5c4c8074ba44410018911291090346152e5c448150952484f96485158a8453367653d6a359b714766f3b3b29acdba236c6565357956a97864365d4766d3caaa522bab7a6436adacda8a1e994dab59f711ab39b3b2a247d8b39af511b69acda86c568f3038b3d5caca6a668ff06c6665359b6d4778666535adacfa08cf66df91d9b49af48895d53cc256563deb8ecce68c3bc25656b3593d6235f908cfb623b339b34766938fcce66cd6f408cf66b3593dc2b3591f99cdd93cc2b3796401b5cf64e6c901274838e5ec67e79b48e4f372fed4f4affbda67f9797b15876ce20faa2912a13ec94dfc41f5633ae820174e11c8dc1c9ca27b2e7ad7b1d3b36dadb54e3daf80b885e09d0fb218e4732317bdb879ea9fbf442f669e7a17861e76de1110516c857326b95c2f251732be9492c4d946149344f126b5ac98e45e52cb75e3f54a6a3baf97507d390e7fb9c670cea1250a68e9f5ba62107d891e818b0e8af7b5a6a15e6e1d1c6f4c1df9e73cfe8608e4c83fb7e210ea9f834261779884248a4d84844a1e8e2ec4e59d874aa187235952b9c49b445259fdcbe72dc3f0ce477abd3caa93e6b7f8e3e55424d2bf39d2e50fdf822fb72fd18b5c74b0fe9c2290e99d47d16974cbf947a21f5d23a3cb5f14c1b5601a661dc7a56534ccba77c57b4bd907dd52f642b794b56e29a38028a0b76ec13454d7693b6f9d96d1509e5b07b7f0bbfca0f8912f21d4a7bc3ae72c2a605bd350a25b074551f416ddefd5a5c0ebb93604192f71bcd8c1e28365072bce78e5310fdd3f27651ebe15458f6a500cc521f33bcf5bee79cb37201aaae5d6c1d6b7becfe563183611f27da077fe7d9f77640986d755da31dfe51f5badeff2bb5a2d4f1c02137dba48ce350db3ee7e3f12a058b9934e9234f77befe5d8ecbeeb97cde6ddadea9f2e12b1e20fae69d9f255fddfd34f7203a261d65bdcc0fb8f2c3bd00798932c65778a3f42b722e1a7fb9d6ec56332772b190c5ffcd062d769548f175f683127a958f1987b479615f48d6abe775eda3193ceebf977a983b724e13d677103d87b42bea71ed5dfa5a51df3d63720ba4ec3acdb327c3bdbbcc580f5ae33dbd8e9e6edd05b1fddf9adcf356e3dec03b8db2a16b9ee4ec522ee32d29dbc6ec9a9adf69cc38ace5eedd4455954849465cd53af6190d97355d52959560929654d9e74be5555dfbc4522b02fadde9624502d7de72c12b15d8121a43cad800d9dde7a5457f107259f742477ad53d0c5b67a54db5bb657f5bf86cc6f17e2f5522dfd74d0fa764bd96fd6b7926e96fd5271088cfd89fde92c12f9683fb949fb64e0e8b926449f07990266be325ffdc8d9bb9901f7e5a91fddb2ffe5cc3e78bccb8f9eb2c697f3f872fe78795433fbb0e0fb296bdc9cc797fd9d4f71c8cbcb7e717e3bc82210023cf38e6ed6bedd12c8ece776673fefcb5db73cf2e60a206de02e47424242a2484a480ed21009c9a37a1c9594941c9ce1789594bcc51f4a2e57abd5727086aedbea4285022d079902498e2324ea3cc84676e4ece7ed6e397dbae82eea87dbec96f9b59ba0f8a3e5aecb3f5e7e3082708ad38bc02936c0fae62c4e5201d6c12e904df2a4a4a4249bb4e44b499e94b4949484e42d476ab5c069e6e896f35f4e95bce54a258f47f2f2e5ed9b0884fa6b3e92b78c8e40b045fdb45a60ab7593ba45be8644e0491e8127959d74f4ba01b6c0248bd54a4a0293922e18267912f91a52e4a01739e820d26d521fc95f48439b1b97b3b8c1fced6391c89391bbdcc85d0eba2ee848b7fb5926246a799293d9b7383cf3935a975ffc010e41bafcd3bdd73df2287a74f98d2e67a761d58beed6a661d523b876cb2d65ffdd52f6e5e646c638dce538c80e50c3aabbc859a66195e86260a45f7278383c5fbd0334743130f6979b9bcdcd57e7ec3494dd72e362609c5f5aacafbeb56928975707699f860a0afa3e178738fdd7a7a13eaf6077cbf9bcc51f9f779c869a5e1d1cc7717416a37af4280dcb69e647a7e28fd1e7e50fbafc23d9711a561df4bce979f5ae83b3dfe68d6a70c24a28e3773cd74a88d3c5cff778ae757183e5e6a19e6b5d0295e0a60430255c7dd2738d4b9fe782e7919e6b5c7e785028e41c64100c59ecc852f69dcf5b8e3ffdde2b8fdd705e5276ddea8b7163d0750e82e1bd1c9bb7fad64dcfc130f43c055870bbafe9fcb37f0de97cf3201d59523f5f7d96932c653f7df653fc313d8adeaafee99c48e409749783ee22ed9686815e1de4c4214e0f8a0e76384521d33b9fa290cda7e87323b96193acea0fbdec02893f5a0eb2e8224bab77f976cbee379fb7ec7eba789b0811bd7321422e926513219d07b990d13b17c9727321921b26bac80d15449654f345efc8921bb6f9e8ba214965b55df1f2b72ed57c1074f1f28720e8e210d883e46bc8e79e7f24edd3b0ea5ed905faeadf9886f2c859e6a88371f30e3ae8609c3fc5397f75ee0803e354e7fc25f5d3728e30304eaf1d39b93bc2f855e36fe4992b8fc9da4d99255ca0c5ac6a9f3761e0c4a07840368b31f9151e312b3c626ca6db2d0d45fd34d4fc31cf5fd23fdf72be7acfc941fbe468397d801ce534f3d5271ab2e390d34c1f80f4ea474f59e304f3fc65f7d56b43bd363f73e5b1ebfcdac822e3f5fef9d5b90f70bdfabc45b382e9f61eb18ee956dc402c42fa752a16f1eb2d3a806339c8913e4d9fa2033846921d7d9acea203387649a64fd3bf3a4bea0aaecc320de5392527a032ce325f2b4029603f07c1197ed786adcfb6875f6f9b779d7f5f4704f65f77a77f37f4ed72dbe6dfb5e4f639187a674520a27f4e6fe7e1053d8a76203b78f945ebecad5bceaafeae4037ddf3497acee28fcfe7ed3e2e8a527e90410ee715ddbbaf21d43da79e83f472b33a1589c09e92a575f0cce8514daf7590c5902c651f7ee3bdf2d8e8dc0718499995cbb931f85cc68d01f5d1a9579108eca7fbbca087b77dbc1c735d36a183f38a64597f7a78bbef7a2290cda7d3fb39783b8fa25fe7d5a3a8c7dfb7aa7fbedd6e59d55324f2f4d62d39cb3c658db3ccf3539f659e7358f52ceced0ad650317413aa86cfd37e4a5decafe7da9632ada5cf338d7bae6999f3ccb50a841f5960ece79a1637cf3e82cd79dfb272f7ca635cc871a48c73ab0aeec0a1c5388e9faaab00ac4c59d54f87383df5339d47755f2b049e873d957df5beddbdf2581776571edb7c23cb7a808e94756ec53937065d037d75b0c3beecdde5188b44e8b757928a666565653d77b93128b94666ce57f5976928a6449e9e92b30c8b3f9aa4e2ac7a667b0303e406b94190c3193d02390323d7b28c79fb5cc302c47310489d994aaef3ed20bd577c2a59cebe3abd200de99719368f71000d558ecf8e34b1b8a1e28f79c2384b1e3c06d03d9c46a3d162ed51ddc363ed0a68a8eedb19c03ea80b09cb7c1d0df5622bca23cf2210d6f3ec200d6b991a5a2c5736dcc41f81b8702b40f7e0d89fcd5aee1c2d7617fb60927907f5e63a4f184b1e513d003bdfce03cfb75fd050737e01be9deb583e3ccc4a764c32ed0038646c980b9c4546161730fb5ac93913d003530418a16898db3bf4161930fb2926a9ac9ebd6c9f4c0a71c5c373ed0a4cbde2e76905c1c9220366cf0d2bebcf7e8254fc613bdcacdf1a64760df502327ff38dec7ebabdf399a99253639b0187cca9792798322703610bc6f0eb83aef626a957bbb537d40b483b75bab9f529ba79d750fd5bf8511dd5b35964045d579501b349760dab5e1d7409a1b2facaf32ba901909b524abdbd564a2b7529901b8a6b57e43c75f6f082dce034616c3dd7ace8f9e9dc02e2ab6f3d380e8d1663a73de8eca08b7dcab383cc3e5a0e82b3d05b9779474896e00faafe202059f6833e3dbca5d587f35ba513ab07bdfae7cc3e3c6ff18e1659824e5d74ef32ef10c93274ca3f2f15979595d5c6570f6f390332bf7a548397ffbb53957c327f7321fd9b537148f526cbd9b7f3833e6f397f3a15816cde72f0be84fc148384cebc637a148deaf052cdacefbc755f6210ce3db29ceff977cbf99f83db2de76f2e7a1589540f3daa41a6c0c8352b73befae620831db2184474902ceb83b49f74dff216394521f5370741165b6439fb9687f749f7a183b79c0f3ab38f902c4357e2e39977800e7a14052f757086e0a5ce628ba4a23fc51f9b8bb7dc8654dfe2d0a43cf839fb1483ccac0f49e61d254852cdac07dd3a8b0ae00749661d9ed3eff27b773a8d7758e76ed93ea37ae6cd4d0ae8b4471597bb33e75570f8e63bdaab1879cb0140bd0a96e9455e8133ed51c5889409393df22a4b0e3a7b477b54e15125023772f622daa30a0e5286c35d2e370a72d7751c2ec341ca8298dcc865383c749757a1a4aceb7c2465a373d4659494752496e9b2ce953cc845671f698f2a1f29fbbcdb412352463a97c39dbc39377259bb1129b3d64752f6f9e8322307bd4af523523634e442a48cba47ca3cbfa0cbae83a48c529781fef21be3e82129b3d63f52464959927f3e521f5d16fa48caec0d97597f72095c36ba04a46c68c889481991739c2b91b2212527e2885c0297712e012953f2a892cb3c26120bec0629bbe14444feba4144447a8eef73a5a4ef033fef1219e108a223d7de8783762091755d1b766714e4eaee72231d3f8e08b4210e231cdd7505d9cff32838724441e188c30b8d82b8eefa735d4b442d9111ed9c2618c1765dd0290892e07372ba610452efe9c965ed4f4f1e0e1ca4c3c6ce61e10413b8ecf30926b81ce772d29fc2cf9f2c0ce6b2eb302758e7221a9df4098c827c02a7c065389c82a7279781fe24c19353e032cf3a3981d74987592287b59393d3042ee37c0252e6e414449d020924487259e8499e93fea4e449397050e04f2e33f22718acc925206530cf21813fb9ecf31c2e1b4926bf71a38d9c74a7a424cfa1a4e42f4fcae1492e0bf2a4ce9d5c87cb5cae63f240720a5ce6720a2693e7a0c075b80c87e77059e74f302424d2919e1cc965d791a6cb404772095e2fcfa144ca92485992e7f09792bf5c16fa6bbaccba040e73d9e8b0d9e4e432ce9d4859939393a4acc995941ce632eaa4cb3cd741ca2698c09948d98b94bdfcc60d4722654747fe2265487ee386bf5c46e42f5276f4f223978564143f4d67720a7284429e44ca8e48d9912779183ae9b2cf49522694c3499781a408fc345dc82970d94792c04fd3292065139058e613e81290b2eff3215236e4a0040ebacc233b0049d9073a1229fb3cf43c74d92539e0a7e9482e232215c04fd361a4cc89c89b481949ca4897e03a1329cbe14e9232ee824a1a73b25820c384abd86de2ca1726d408234c95266255ae4719911d3f4dbfa4cc49c741323f4d5f22993eb12b25193912297b15f911299bfd80831775f0c89086ce0f31a399182952788193270d3c6462462e0b228bf869ba11292b22993e554096b06797b9c8919fa6474076f4891d87cb2819023f4dc741ca88482c335625c8874899cb6f905878c4aa50172265412d7791b2590c67d8381303a032be102346798a3f38c0e0450c203869c4aa5077b96c2407c04fd35da4ac45327d0200590ee0d9651cc9839fa68b64117d62973579849fa687a40c24b1c0625536ff4859017600028c1aca78e28f9a984762f15895a53767e430841456d4f02256a59d735947c2f8693a47ca3692e9d30eb2e4f1ec324b3a3f4dafe4489fd8d967d4eaa977648153871f6ad82c6ad8f47eae9dd9e187a8ea23f18e4972c3a847358bf3ce20fc5433ebd959147947df9077b4d3dbe6cbfa3dc35b8d5cab52e7d9ed73ad8a992f3beaed22159f6b556a6080d52b9fa9f3430d63b236ac9d45aa99f5f676bca37df6d4f996f3d9a794b17e7b1845a3ba631fd6db3df6c1df6e2f734161c3b86165d14f9fcf353354fc50c3a6d73b9f2f378cbdce59abf22510086353a7d429a5739e524a29136d42058d37547b1d1baa74262a58df1e3614fdf632fcd23b5686cf234c29f35c9352c697aea74ec1734d8a9c075dd46b43cdbe6c9f3e874c4df235a4fef4fad349a0d168b4ea4d96edd559145292f0d52b4935359dbfe47e3a9d54407e660267197ecf09cedb35d147399fba00c62094ecc00ee7ad1fb28f26f3cf5371ff44c805d2705efae2932ddcf34fc216fb6832694fa5c14f6f679148a5d1a62864a4fdf450c8a4fdf49e8d07e4ef1aaa3c9a80cad83ebda4ce0dab606ca7cece2291f6294a760d638fead99c0998aeaeef0454c6e9ed94cee9e00ce924bb9ef5a40299a91cb96165d74f6cc1b8f97c900330820c82916b527878ae49c1fad2f5ed0278ae45d1f32dfe28c7e95ca3614175de5e1b6af3f60e6cf147b74d9101b3df1c9ce17c708a9b83dbad9a543f6ac3aa0b197ff3292a807f73167f70c3caf0a725cbd9dbaef328daddb27e59bd8a3ee72cfeb01ed520c822254bd953ef6ee9dff9764b6e1809df79c90d2b45f8cecbcdb9616508df7914a59723a96401734eef6a7b37e7f4aea968374be0eaeafbf2beafeaf52a208af3097638390e08d5cc6a52158a20e4c8771d731c1aad73b03af5c82654fc8746fb2aa1ef9cd2aaf03b22227cb7099506dfbca37ad5f69f974d3eafda861879cf97a834f8c93b6af548661dd54520d637b2de8ebd49253bae0c6a1db477c68981d44170f6dd2cdb3b7a69734e9ba492b1d3ae54f4e7e49c0a8973b06b58e739774bae738fe3486e58479655fd54d3737a4bea4fc657526951beec9c8a0b1425eb9f74df4d556f079d23bb1b725e761dc979d9395547454a946ff264fbe94fb877327bcea3287737ef2e3fdf928afbf3d6378feaaeaabfbaf712326bdfbeddc9f9e67d9f74e7fdd63b671108554927779d639f776a92dca5dd91e57476ce85d02f9dcc1e087f25a72839c5deecd3e7f605c8dd903fbb3314588ed3856cbef9e6b1b3e7ecf9e7ccdf1dbd27f3b76d92ce3b189c21177ef3c1cf79bce7736e0e7e1e72dc90cd41d2e5f9146797ce99f4c8a186f1e69e7f6439fb6fbae7517482e0e775dd4ff72eb7b18e765b594703a93f492ad953f72e6dd6d15e2e6036c44fd6d1be80d910cfaca3bd8fbd3b4b18a90b19c3a6aaa5f5e9dd7d4d926a3e4752f0e00cbbbbe3d9b7e99b10585ac629eb4e5f5a7e7e29f6d184c79f619ff1163e9d99dc482a5676c436bfba11dbd250435f5dc95be7f1e5d053d97c5e8de5dca2c55797b18ff4c98c313b90d9a1cd1e1a2db6f904375a5553020c525e082246c5ca8ed8f4ab1b31762a563ac6f9d50162d3b77b7580988c4d91e9320ee853f55947c6a608e71b298ba24fd53952c6a6c8e693946d240fa64fb5daf492ebf019579038f3851469ae7022c6319ea1302788165a7883e70632621c632f7914711a2d36b73494d357aceeea0238b3d0271c33f099c188f7510af9aad76af5d4279fafa42db7ea1cb96423cbcd95501bfebc25977849c952b639bd74b07eb35e5a7d399d5a57321bf3dc12a683f5d6cb0ffcc6914b98cfdb6d8901a8d4de9275d00851c11d386449c2539f5e2b7046c66ee5cacaca1a13f3ced092b8c4f0a54c1309cad42087562502f3f933b40aeec0a13d3100951a0934a68315632f3ff0560af38979248df9c4d84bd95bb29c4eb982926b67daf8aaf9676838aab0610a17345a8c4919bbd592165cc81325108d16eb485a597d96223c2d1274451532b44185468b6d70e39b8649e33a1c1469a8768ef57c757032c174f0f8ea30ee96ee4cdce5ea1dcc0aaa176958750e38a03d9ceb5c80ebf89131a8f4a74d5be5ace33e702404114898344b0668edd8509db33bfbe08454d03a8b41ac57b7b37f4cf77cf3167f0c5d5bef93f9e57c32bf56079b27776dd9cfa7a653a75ef515dcee4b08f379d0e937f8025914c2f57990e45941d96fb7d94c5b3c2bb04eeb55127ebdaf21746a73eafcfc6343f17876eff278ff36b7777a19a717358c7d68f3aff32952758fc43bd8bb3b368c9dbbce3b9864b256b20c6b20d03a3395dd1f2b63d9d56a0dc579f52e4e43b557efe43454e7d5cb6ece57075f30f3123cd75c08f4205dc3dafbc158a977dec5a935ac7ad931c07a4776722600ba1aedd161f553f5dad58c8cd6bb5a87d5b0ea1a182df912d234da53b7a2d17e8a2c89fe49ad834584d9a8bd2595587f2980df766ebfeac5183333e0af9df8837a3f48434035e46ed9617dedb06606fdd5bb2f667cf50e4c43d1af0e4eda93b6fabedacf5c6910f37cbebdf3b9fb6a7f59e75a96352f4b4280639e73cd85329e6b2e90f142e85ba76f49ab2ed887f5ea5d1dda03f4ea1d1eda63c6aa777b688f9657effacc0c3aaf0e6efeb2de8139df0125be755fd667ac63e07d59d2019d5b9931d07dc6ece520f4ee961d58e237af0fd29e4c6bab9cfd8b83cf5f9cd7a7ff7280e82f8ee44024a7701620cfebf7cf8f6afae19d326320f9da482a57d69fb9ba20e6f91457561b312b33e63995ee23a770595959fd2c22993e8398dd9fe71c56dbcc366705381d9cf367736200b24f29b4d66b7d4ecedacda973d65a70bb483388da3558af3fa866d657df48a45aeffcdafd0438bd631f938441cd0f358c7ac73ea83310b3f28e26b9611d9dd306ca0e5a3a69e77473ba39f501741b75ba39a5d42d52c3b6f6a1dacb76f3b9f9abfafcfe57ed6173cb91480ddbc8daf10e9e5f6f3b01b657f6d1aee9957d4c926a66b5e51d5c79c79c02b2f5ca3e3667b7ec635ed030ebe67934cc3a8bb40c656188d43d3eb75e00f6e1bca373eb3a1a6a6c58e7d607f0d43953f7e8faa92b5f61f718eaa7cead33d69717bcf517fb986f41ef2235ac736a43547be7d46508f4dd143b7f712f8e73ae9d59f35c3b53c30f35acf3edcef748a48675dcb5bc8377f07c9e33db940dc836683a90efa73f8b3f9a32ed9e44c0cdc16d8a3f3eee8a6e1d042f90d997a17f9c33e7ece12db9e7da19345f798708e4b68b9773efce07c1ce411af2ed5ce49f1d5976dedd66b274b0c3f07eef1f4841d1992c3b2a12a8488942fb266148523999bde76507d66023eb9d701d345fb2bc478664c9208336ec6e835ec12dec6e3b8b4186886449c283ce21c96e45205fe81fffd4e7cc2495ec4bfa9fb328a47b909ceabccb6e487be779148dea1938eb0f208330562f8bde7a4359bfa0a1fcad179da1bd75247b79bc2d7aebe390b5ce352a603932df00d90eb90238d053493bd5ad73d69fb30ec2b8f90038a8b21b8f3414e70568287fce3b6828aec980e639bf4043d1e718cf737700cf3937d4fd92e770c3ac473512d897fb33c178aebfc84f4fa6adb1a1a8733ed450d539ce3967268e9ce23f144d9a38df43d8a194723b7524f6c14f3d897d00a9d5867140a50a942aaad4c1aa7244157c624c9faa7c13379c30a74f1a68bc89317d6ae70e0a1b5649186c7af74c7b703f95e37b3fd762a0f2b52b47ebb59dc71ccdecf9f621580168ac0cff86cc056d64d8304b86cf353380be92430d9b5c7bce31b3b30181136be4860595e3d3ae84e918ed3cf521ea5d680170b619bb86e7da0f5ebec93cd77818e361cf351e5e105235e5cf973320b62f47f062c4b3bb669b91c279aeb9d9f333ce730d8731afe3b976439c2906f996d8f354479e3dc8f7a785a7b26a7d598545154564beac424306c4e2cb2a2d58d82f9de4c981021fea7ce984873acfded16895467bce7a76f973a300e8ed1daaf9d5c17a6d25cb39c521d49b2cdf8cf95a7dbebdf3be86d83b5fdddef9d2c9ecabdb4bc5595959a01df341f4f9fbf7c10eedb577beefd24f31887f7bbd4f882659d5feed43c0bee50c32b3a8a5e44bc874ea41a653b20afc1a98afeab764d9fe643ef5d9d3fbf4deef2af0d96776361aa08f497bb0c38f7df8f89e9d39039e155477b10f66a3c7369fa657b2ecbedca28430c33baa8bf5398328215ee00af82b5936595afd937e109acc9fee0d7d2318865c83c1cf53b9fe49b7cd0cf869d95f6e4f7d8a4dc6e9e014a9c6094e3a1ce70755a5d1784fe5c1ead9f8f0cc60f6d1f3ec3d7b708cfd9b1954da3310d6c3755dd76ddd7574765d6761ade99b9da539208cec4c0d555630d45025d34f57fae9a119343f7d6ca8f99399e606ade79a992be0730d86403f840432ed71a4478a33ce1083258d9a184761478d12f62891060630b1e9946bc366756f58ddc8d27ff38e6d506d185956d19feef95043811cc87d1f5972dce7d441eece07bfcb0d9b6290a4af0df39caba2ff39389b276df183b086d25122fd9c64b9b975ea209d73bbfcdeed34a8b24fc26fceb7ec9e3bb64155646a1875cebd611cd95e6f399f6a6eb78a4ef10793961cc3a186e2a7ee8fd450fed475b00ffad499fe9bd60c730f4a9956e7fba43e7bbd42c667a7949def3c8f2f39ebcb49a3e349c6e9652dc7b18381cfb7874349a29a3613902f0e6318c3d5b70f1521cd0ce67c334e9fcf8d013bb740662a79cf5767f6c13e6677b48747adb51664b0651fa5937df4b3835de710989d7b0e5a07ed15f27177bc1d7daa6eefd7fd68ef728cbb42c0a73e7e5367431dfcd16439ab218bd6f6d6dd6d6de85d286cac5b75ee6d937dd0670741ea9e832f21e58ccc5bdf66784b36f1df7cf61be95da74fd5bf5b449feae604029f7b2414cf3f8f00c73c928df52a3ee08a06b18f84d1a7ea3ce853f54e2442dd92250713d0468e757e458398e79e97b01f1deb5bce860809fa910d9ddc93b6ca087cc3f0e54bae7df50e1aaa2c80021aaa643c17682828aebdf0e7ab2780e77c651ffcec53ccf1a555011a8a7a75de53bb6bb3e7c9e79a0b40bc0b6b5cb8fa3280acdffadc50d54bd85b6742f27e16eb543bf572f472f3f6269b92536315e32ddfa9f6ea54dca0e9bbf0e7cbf1ed5089f4969ca2de5efa5beb61d19cd46706fd74031ecfbc63523620adce6ebe3768a765cc7c3b2d13e7dba9ab86f496ece373cfabc800ebd5ab48a49d7abd95c77fd4bfe9168036b42211fa390d3da4fe7d4ec13bfd2ba94fdf4409740ea523a174be39e7e0165271483b75b00ba72881ae025666ac732856666cdb1ccae69c4775079d43e93ae89cab93ae007e79eab24d2c43e5a953eb9f6f413ec920dfee94ce37b7d7ea8b5967f6c1f9465a7db1cd4b91888b39b8f2dcda3b63de6543a581cb83f1732b9cb3019df6c5ac5777393f9932ee92ef410faf90afe55504325d28f4f24e29013d74d1697897802e7a149d975fe896b30f9aee72f97843679101937491238c5f561a6d7acbc1fb02453240fc7daeb5b086cc9a677aae91b96a01900dc1fb6a89a2287221785f60ebbe4051f4a8b622270e119d8c9cafea21df0338b691533a52368b7da415ce67cc9eb9e24212f4a8b7a46cb3ce791b6965ad6f2eb3ea7c23af3cb6f995c73cd2aa23af3c66492b571ac4aacc62d6ad731b05c9a908ccacbfc06a3576b291044bbe40b7d6e7245fa0256765bec51fd4a7b524d3a7ce6aebce0a904918b99625d0772d063dcfdf851980cc54f268a8da50ed72ab0e62a30444b71aea22d18b8a6eb8b8398b1d885e7443073bacd373f60e64506264918e8d35e486e22f02e5c8438732fa91b358e4c84352d6f2d13a8b1d8c2c047aea2cf8b162911b1ee4e115bd75a1dcf0965bb102462efa768bdcf02052d6722267f6e1a00d7dc9652d231749d90d0f5b976342174ae846be74a118f90d27ba5064a1df70ee96b3e7e18d9e6b2cd47990c51f9cdbbbe47bebdc15f2bd752207c12956a0e544eeba505aeef2a02b6b79908f57d6f2d159acc00d37f2a826ba32972f7978670ffae720175a162b200b72d1652e1772168bdc70235236bab874a1c864a28ffef28e095d283259c831d795c9dc953ce842b90ec51d8a92cbaca020b99013b9acc865385c36e4a3158bb43cc8add8c1e83748d92ce6f2a32b9b3117696f9196f52012cae8410e7221152510ba110945f49673e24616b9e150427779d19589de22ada044e04b1d84e414230fdd455a7db12017afd5171b997d58b2e4f167ae5a1e92565f2cf4cd83aecbc72bce5890e99db328819194852e92565ace6623a79458e77cf3208f6a902c793ce71fd92290e9d4b9bbc4fae651744e31f2205236632409e545ca8e5c16e44beeddcec33bc5c8973c822b2bf2081cdcb89479a683b57129f39bdbaf3f06cd73133f918c6551cde227cb1a16d08c0134a64f0b5b5a183306cf1839637808437b5f1d39c4fbcde77fcff579e6c3f5f9cde9f7b32884f9fc66002ab58a051bda6fcefdf6602bb4f765bdbb2f4b0af9cdbdef9e452115dc81430b452106a052fb8d0e56cb9748d98cc92eb934bacc979446d20ac9454f22ad90d8703e3a9147f5bc63fcf4a07be5b1207fdd2b8f85eeba562e27f223d22a74a220d2cae52e17dde8ca66b11ba49516c9864a839f2a79565072cdcb9f9f6e9d7fc68aee95c7649dcc65571eb30a5df41b1ec195e120adc2d0895c6625e444e495c75a3e445e79ec066925445e792c24ad04915334880579e82239c595959595152bb9e6a5cf4fb24a24c24dfc243930d2f9d29e69f34ab8cefbaccc7b61e82dafceb48757ef4409b4dce523ed31c5e52df22a8864b3f92c569d232d39c5911198c5c68eb5bc5a8d9d6cd26823095c235fd65b6ec5632ddfbc14e137f265c922e3f4f2c84f9fceb4878b84227ae8d5bb99c1247d132bd0128b84de22652d0f5b62c8e339875504b3aec71ebc7c79eadf567bfb5179a6c0a4e1e6a1539108f50fab8ca73e1db4e1141b30c2f873deaaf13907bf16814c1e6fc1b3f8f91643afc3951065ba3f74cab498585aba13c8082a2bf0a006ab01539899d3268a33a09001ce1a7fdad89400238276682c50384fd82e896d03c36b17d3ca5519ef4650123d5c49d3a78a324acc62a002062d59e09961882c98c00a9918c3c60b0a2b9248a1052d65e4b0700496181ea880c184156a9851bf4079a603faf0d0054b0e3ac490c59675c21156c8c040e58b10b10d882e7e2b630d5f120616795280620d27aecc7a90f2849a2fc4f041e20a0d2d35ba2ee401ab33418b28070c47ad1726466fa8088e1e387636f1c7365a137af8b8ab5d1266ba24462f7d42ef82226ab50a3c21065680a1f44d126b420c92b0dd1a31e88225e49810e59bd3e7823bd616b50281522e6c81e50a22d270a266f4c21938a410c68f963d21204a63c80a3956d4792ac17e7f62376011b1c5eca1cf0f5788e0fc308688e3380ecc05377431451b50b06046192b00ea689f0a30e8f0051e38410c01688fbde39605037001a286374e4ca14495a42f64b4f451a28e0e637868586b6d2ccb9fb0b3d67aa8a586b7564800dbb66ddb9f0068dbb2e0388ee338306a360a0c3923c7e9e1c0e82e09b734c41989729033bbda308ee3ba3b35ac5d68e70f65d31b18672678c41cf9600124860f7368501344cf1a785c90c306960f6748e0b5b9e10934c260830f123eb4e499028a3f61d4c892a13363dbb66dc3439fb8a378a0881c9006167a5e2053c2137c92b0b2020e538a4083821577bc32267863bfc811a4c4d2eccb9d3743f8b132069e35e42c11484c135eeab061c61a3266d8b66dbb02a89ff8bd2b5a376e9b1795853214e6058e0a6db031e20c375dc4caa22b8e38c14f175df42c5184123cc6d869f8bba0e130ac3831049eab2e725ee8e285082804a1062a6a609c19e3470531b62f018b0a15eba1b5d65a2e4e6d7462806033840b28a8f083151ca49006971fb6b879a18dcdfafe30953e436357c60327b0060c52c874e902893eb1b2080b0c5957a408d1064f6c83b3049509c2289282a986258ac8418940808888e2824415668099d3264dcc6e6aecd9c0a8c551c2b3839487ead9b6087edbb66dfb03a8ab5c948f0fcc5afb0015b6c840840f6bf4194301265889e1cd1653c6c8e3820564bff8c268c18f116c60e9b2e5ea846ddb74e80963420b6ab2e02286d8e6a7e560f5114e5c90854d1a36c8d0020b59a021254f9b2ec290f2a6081e62b6931326c80b1b4bd3c6f3c20faa68b2a1d5ca3f048dd21faec6a271a8284f9c3c73f200513462d90931782bea787a7a90636d51f7c34473840a615e80020713cc50b2620b186fb2f85153a68b2e46c35514319b1a4360a094c20e2778c1c41a55b0a8d1c3842f6eb408e28a3163e07001830b220c7c79214d1a5c64e9c10427a81cf1021524beb8e973860e73cee820461c3b1d7803c55518294af4e027049c9c91092fd80a1e2b7ba8952cec983f514bd466342966aa6041c5851b84e821884e1242b33045d056b861072ceaf4d922041d861c8ee3b82fdd6aac50c5586b6dd8d903704438c503da50e6891a117f8e2801b6edfb6ddbb6edca5557b72965fc36858d2a26111454b9a0a58d142e74a1020687406ae810854d1c1fda90e2849c20aa1637d02b3552a0d65a8b4219cf13734268adb5dd1d1004601031054d1648bcf9130131d8102184195a00c1c6c847866edb3c3836749bfd9e06301d00f260a140a50440324ca982068a1e4a1883c60c2ec2b0e9c2e4d065c1449b34ec0579007180c0d8580f57b3ab26aa54d1a5872a687001ce181e42383e0072f50a60a86922501436c260c1dd098918b7f96b805913468e0c6c8c61e7852e88b0e28b2c68fa88b1478b2e2a9f2296aea042b864e1e2093a67b00122043e50c9428f1d30ba24f1c795c5b487e39c03d9f09c57ce09381e1f38540f17c5588318175a46f040c59fad0a3096e6c9f19ad8289c2c33140e177dc29f0ae78b1c110d77010a344ab53cf1d56cd7c5e9c46c2bc809a2badd98469ff8c7d6cd24b238a2091fbab85025c8183f9499a10b397142d08585b302ed86155ca3d16854c6a271a8e8aac31ac114fd1686d63eeae0ae7658a3edc1075a973c6ab858230b156848d3c74f192cbcc4de688144e3a95590450dabb12f65ac2ddad8d8b66ddbe205236a3384e1c4963739ccdcb04697345d8081868a16845228f3c38a094de0f963070519c2c0f0bdf1628527064dce1531d858d3c31d3d4a415ea800c6891cccb439e1823953e0f1628b174250268a15a229b62d841378c8e20207159e80b3e445072e5edaa0b973441c29c49082c6aa8041e359c1eae250305d08c57a643801d00a73e09c2943c90b6118c1a70e9f10600552c27b13c80ed2e7698eb332c3735c0e6d050c115c14127236af5b0f2d84cec0dab66debea96c1986eb3b54d1f6b6763b88013a60c2a9a089347853469fc00e884187688e244adf5c6093a43789eb8b21d07049ae7c4e7eed4a6b8fe0b9a98192d5af890040c6780618217464c37f3810c0d4ba4b96244183d77bacd06b529632dc78518bc1ddbd1f190b8caa26a6a4576131f07dab6891963df6079e15db0f1a0d16853ac77f68ed6cb2eb4e468a65b33f6f828c6f413ff56f461ae6a982ddb057f041e243b667cc33181021f2c4e18e326893159572c21658925c228a289285e18a57867faf413f7889df9d34ffc6537f2b09161adb563b409bbcdbef9a10eed82072a091af6a440a676831e3a333437c470041559646419014506ba2853840b7cdcacb9f345873066108186952b63443b1c57bf9855f45183884f8d25383a7ab834e4805d121b0fb40c976d23c2f5d65a6bc3ce663dc1d5ac6460f305203d377861c64c0c0eb52c940033840c7b6841d4f1893c2d1ddbb66ddbb6c5d0ece9ea0d2a26c4200399335854819306ce6f1b1728fc4684f5849b22de0871f5841338d88926dee0c0a7ca992a72c8c3c294ac1e86f0e28a36028d4d2c71006b8a1d2d7cb4c0e28a930a56a451a38a0f3aa04007853a550d96d0123443d001038829e860d92a52ac49830e17658cd1c69db74c042961abfc8980892a69be706941cf10575e7edbde5ca1f01b4d0514b8a401031617c888d1d962dbb66ddb7e05345d1553c36fdb057cea126a709689335d8d891ac425b41c599bc5e6ed8db75b06d65a02b8912b269c91a58420a840e3bba863e68b3f56e44401478e10bf1dfd36c7dafbd65a6bc34e0c2d4260c4943c6c5c1db16789366b30c1c213213013a58db75e8394b09623428a6db3bf6ddbb6bd0a603c8fb9f8cdebb66dad4510d7861dba0616ce004db4a0c5092df4f1220f57faf1c282942a45b051458cab4f7449fca007071fb8f8c0420e6fb0e0236689326ac033854c8ce3b83753b83cc72131563fa0b5d65a6badbd801846a000841028a4a181c403c61425f4c1010d326216dc22be1c232c2d75a8c883460e6b2435a186cb19698461a68798058f18a27738047bca1eb288d3460a25d0b04620cb45940f96a508148e58c10e1a2362d688b7d6766317b212333891860a38cc59e30a6b54c2132918a1821337ec10b3d6eb9b3e7ddeda22b6502db2c0c842678d305dee0cc101ce165a80e903464c203dbf6d71c8a1b16152c6b8c62840806a40244c78c106cc161dbcf0504495315f90e1a54c9b1048d1a1b2118523a245e0428d24a40c91458c1bac4cc0e28d1a448831538585c3b9306f1823ce0972be3c91658bd915304ce893250b062b6cc041224e056f66f0df7ef3a2eec131a93c5966a8c1fa220b3d48d42143041453d8d046132b3cf85b4dcddc346021079ff1650b3654c819838b2b0ac872c20830a4642903cc1b295a820e502f50210826989022059c2b2b6248e30a303a4cb1e70436d61832c019a32dabf61b01388ee3b8ca0dab1b9cc0c2115b8cb0d2c61004d8e3041e16b2ec6051e54d1434383570180b40ff4a063354ece0c294134050060c35bc69618d26b8e8f2adb06540419d31b2dce142cc1458cca8b0010405202e69b858418e095d1ab5ce470f80fcd04962cc165dd4c04cf819034c9f2b96a87395c6145d1a597004f08dc6f5731cc7715149b4b1d6bbb5bbbc849c5614f4025a79ce1a2eb26eb8420525b050c08b1a5e80a344145f44a14366a313c3d000c6a2f187985a90bbcab4c7f882162b27b071c613719a48c0092b40c1031d3464044147cbd7e7bce3f31c8ee77a3cc771e3386e6eacb5d65a4f7c14e4785ed8352c015e7ef8c1cd1a686c5144ac2cfa25f28062683718808605336154f8c2892f31a88de435d6a803031442e0b9c28505c1183a26a4a1d3650b2c6216092a6fed9b30657cf81683b7d67990433cf4d5b3ccdaa38b591fad75b7d6da1678a04f3c81600bc6eeeb3bc7715ce7cef9c8f95011c7711c7742adbd1b40d09045adce15953e53a8c0c49b2e4a2071e70d2d0d9be58c77413776e108a6cd1cda9ecb1413ba206382164c10c11d00862e42bc30850a3429c4b8da11543cf79c96209ef3b0a1849e736f28013ce7b086b2ceb98eee31d268b428316ef3a186216d5e0e7d51c3aa6f2452c36a8f58181ee813bfb5dd0f213153dd66cd02a178f3caf9e6614375becda14672f68183e93923c6cd677799f698e1a0c61914ac4cb9d26242ac2cfacd47daa3481b25fef010c5175da410db9ce1bc71e1373fbfb93754007e7318ed51639bf3204b2503784b860dabcec519f9cbdaa38bd5b1d11cf1e5082a4694f5d98834b6e79a1164d4aed81822ceb383cfb521c6f09e6b437051c1684a29a574de491bb976a626c40f716d081fd80c33a594569252a674cacc8ab1614a2ff38e24234869656e582bd1ade3db8bf8ea68a810b8e9db79cdf74b50c4038521f8b422308bb543a15e8592573488b53769d5d3c12900766eb105b53a7d39bd4b1e33db593d67713bb7570215dc79931563afd7521bfec43a92c674b0aa97de532776162be0f4f3891e3d4a7055aff5be36672a575dacbbea629cf32dad8a342dd6b977ad0c20c6bd84cc326fed4672c0397f9131d66d1b49e57a4b9ee9664f3d8a7ec75da64f65f7b61bab470bf55ed558757a8b8cb1eacc949254ae3f5399bb271931946e308605be332b68610c487b4a9693365900e79591ab8cb361932905ec7da9e6cbe66cb6063c98f6e0bc1c378ee953e57c73eafdaa80e79b17e99cebdc92453cdf48191beb1ee94d8a6cde91b2913ed1ea0fe058117da24eede6a53fe538b65d8ed55bc2fef2a04d5f00ad8c4d75ab2f66c15a81cd2d2963539d72aca34f318e59a75648d0371bb0964abea75e9d9265754b9ed982f09fb9f2181bea541ce27df5bee76ce6a6dd5f40a9f1730a402a27f33d726abae73798f9b29b4cde61bdaa7b2ace0a3fd0faec7273ebdebde106371f825edf72d62c76e90da91efa148784ee89413807bd1b7a1587743e7d92cc3b66d9a4e7e02ddb7bd23d156d835656cdd936bb8b3a3330d61696781bfcfce6b40b3b3802a6be9dce410736f0405d0f85f178eb1c58b7b02321345459c47218d006d0dbb74c2c27c797ac878f7fc97e7ad85869e79c739b3c37e7e955dbf37ca686b24dd8abb62146be7d6be7d924b38ef9638a3a039ca9cf0982df5c06058a5567e5b1477664499dbddc806c4e876c24146fa8ee8d1c32bddd3ad23854abe1cf6fd7fed850bcf964a38d369ec52cd6c1bedd99f52cd258077b546fb769cfa2908ef2d39bfef1d2802e179d7da8a8a140676a28ced9610db5f9ecbf2b3a776720f839f5f0be38f75cdcf3d5bf3a5fdd73902930d58c5caba1cd3305ca7c75b10607bf70bbd5417036a97bdeb5e00c2d157d17e853af219d5bef3a8f1437f83aafd5710e7a9ff37b1f098473cfbdd0b93b7f9b403eebdef711a9fe9153d6cbea4e66bf39271299dabc7a47969b87a4e7dbfccd75695b61b660c6a8b8dc0ab3059bcf58140def4752c9a8bb2ec74030745d8e7d97bf9c3d778778e494e8f5c7045246d1a92804f48d2c4507c917e89d5b6f518848be407208e7dbe71dd74d711b50aef3c812bc65e82fcff9e59140ac830e921f82ee6d539f836439bfdc888024f30e926a66794ec16dfee7ddf9179405788eb177e1cca0a33c7b8bc7001a8a5acdd9d7211610a44128d944660f2a9f53614341d0bb5ceb063ba4a177798fc8802a12a11c6beea70b9875f98e080ffed380a8ef9c8a43dad52aadb01410f59db71864c9535909097fe9b916173023f31d905999a73af29d83ad566955e637661d65e77301b321369fce75e1d7915ef85d8e8942da37f22bad7ebc4e494a96542452a9e84f518e1ca1e8ab8a7ea33ee9d4f4e9f5be86941e107ff4d616d09a3e6bc0c4b141ec5823fb74906f7df189904bfc907d3073ab452275b2f38f261d1aa7afcdbc63e37c63ffd807d71de7d33972faf7d33ab7cf5b1bd65b3ffb4704243fded11c7727eddb9977744779b7e4e8d46fc089354e07419e358c754cf3e7a73313582ff97682b3450684de777a78a7c420edd3a7f39461e49a1ada7f42a0f9ef4307411a4e3148e79e4f35d8f725b2e87dfb452eaccc3b5c6ebd6fcb5db76c2f81b4b7c872b608a47df4283aded790f696b7b7c8fb1af27dfbf74d1ef10eca3688f3a63d3846bdafcb5be548e4fbd08fd807752b1279f10eea2179d4f2a89eb46731c8a49d09f2a81e698f20b2f61375ea22cbee379f6affbc6c6706cc86f8bea8066bb728a4fd739e6af10eea2ede41695f79071561acb4282f3ae88122e972e61d9ee8f25658d207bd057ad5f62df13669b9428f6a57e82d3174ef96f3411d20e9f9e77df9996d10478e0ddb48f22d39e4ade6c480132b0d178f35dae75a1a3ecc3b24e8a694f29c96c99284e7be9346bdac7c27d94514453174d0455114456726b1a980200882200882ce4c201b9ee7799d73ee799ee739337967701cc7711cc78d338cdbede014fbce6f679177d8ae61d6a775be9336d6b7d645f6f1a3d9471f8f7dfca0ece302903b30729c832eea94524a29e52d08cfade36b35722d8d9db70eabdb76836c5e3daa39ef185c2e57cb450f1d7497cbe572e90141f073cf3be71c0441107402646666e6c9cd85bc83f379adc6cd6dd93dc779c83eda3917d9073fe77d9938ca3cc027f6a9bc3ed5f8a49faa3ed20d3602ad831b5986bf3948b93f2b2065f7d7822ec2f720b9e42badbe7a97ed4ae6196fc9255f7b25cbcfbbefd3b879fb9f8335eced3f921b56d6df2cfdeeb496b4ed79df9e156c0ed6591bb6753bc8b56fb28cfa76d00bedb5ceb5e7da0c653cbff9266bc3b6cdebb8a7cedd5249f8d637f247f5a6cd020ae19cabce79ee798b3fb6eec97cae5ecec16ee71d3cd56dd0f9e61e59bb5a2fd8cd39ab77339c0ea8c4e955ea4381182471d0213353339a049000c31540303024140c87e3812ceb3a14800d8ab24c4e3e1507234994a32086611886611086010000000c200431c52cd20240f98d80fa372520bea03b32db02dfc3c5c0646f1b737b5671ab412a4bd3d0b46d8338fa132f40f8f97f0e6842a284a96cfdd1076c01395441522d3f82a6e5d29d49a69e6cfab01634d157934db4c0396e954670f0205056eb49d63bfc6974c9b3a92ab1d84b2bb7ec8f3336c46497d4e722abcfb4874887f6508536fc821b4fa23df3138578013124db10b427a358fcf9a2cac1172f72e458467633bd79682c41ed91b536a90b4b31d5ddb59f787cabb1f864e8fa938014d16c4299fcfb881cd59999483f8900b9ca283b8bbbb9b90451068fa0516172a669c0b9ff57636a0fe534bed582442000c630a942173385851b3a1f23115bd1187bd05031642f476c7ba424795252bab50139b366b1a405306c6f4ccc9719f1b67f155d434e3b09921dcfa2e435087f9f8b7118c2b44f239196a50a51e8c719c31a10b57a80a4a83dc42045824254da68bf187a0a7ddc0006a67c81f8d7b79a5171320507f60b9825d30ea3ad38a751c169c4e5be36f60cb1d66c30c0eb092870fa0951e872814f3abc3225b81f51ea6c868c0cfe5c27dc12181a59f5f1b78dca9fc2bf341c594c3c0e3b01aba9e9ab10319139370dc491baedb28c27ca48d09791302eed9c8014a33b9d86b6ee4ac165a5058ae2fded93bec92352d0696b16a8a5bf502c842c3d6f7241e152d5142029d8dc388879a6c5dca08eab615a483e87147525290c16f38434eba1662fd488b6243da065b71fa4f00c76499a503193fc16a7d8005de30c26316d0a833392e38913ebfd7084331252c47ce24864338b0960094bd64caa963c24b9c1beeabd95ecbb6121697eaf734b3391c6ac9af1f4d1731ec476f3131b16f25c8198a9af8489b787cc6cfe572ab340d2382d2e508241d27518ed748f06eef1af2d909abafae0c5b92601d650e0ea8204838611a26f1c6169273853980c39177cc37c8bc09e1dcc129303f7c9817ba7fb910736e3ac80937278ff91180543eab0068180e8dd7a3d5ca04fced799bc12b078702614291a96f1da2323c3660de1f3bbd6263617a1980d2fa29d96a4250f52632363131e6a0ea090a415967ad27738a334ccde5a94338abfbd2382a7c33d9c3d6c10f0c38b43037fbd3c18d1374ffa8645cef65febb6c2fb11069ee80c7b970a7e8ff569295292749093c1263a56f1b16e0a72eaf490e80584701f5eca3ac1ac0b356c3ae44249a42314bb8c0d91cb4cb20c86bd8e0eebd90b1da7ddf774a632f289451149c6b2dae220b2c8a4ece63fca9bfc56ae532a9ab4f1e2d5eed41abbbb3a8ddf0c0f33830ac31982157e4fb8d9d7361b2584b6c1e8a8b1c1d677cd7869aca31f2e2a72f8c39ffac0d638db85105cf4e368f14b511a7508900b6ce666c46f84846a5229e83b3205c87b759de873182ba204ebebef8e0b4a298737ba3d1af644a9b36cfd1edef8500891e781fdc9bafca08592d73de6d2e2c5573fe5764b929d817b5394b756da5276aae8a0f62e647d259123f98c6418c900d0175afccd67c6d64bd222a487440fc5a6c6ed63f3fffa346859bdbb17079ca724ee535f0a1d1af0200df269c898621df1815527a4274ec8dc3230e863158f6b9eb96525348b2e3444a74644eaa9cabcad96b0e12b7b138115bdc82ece7d4c62224a569448681384e8738495c60946fe9caed286a639a21aaed28e5ab463c9453f47b1b304e359abb66e324edfaba71ec50900ff2d4f9df249147b65bc8b12942720b846abbcfdede10186b14c2f46951a6fd37ee65d0bbd0820ddba418813d07d62cf156bf30610e0a7d6d842f3f76a35064c44b32a06227df918c00c7d08d7097baeeab8c624d80670ae83d966f7bb11a88491b95f3e87e3ef13a51e8fc352da9ef9ed75ba4446cffff645d5bbee462381938c6c8ce9dc25257b7c33860a3f79a160867317139858ea3472b5a8960fa635d10e6c0284907c2204a9d3b974e105a12dbc6d741ed924e107eb2908a1655f1e998451eb12a8b0eaf12716d96ccfb70fcdfab0f3253cf5f9e5ba446facc66777b63514acb870425899806ea596101d5340c813baf06eb94a7f42985cf290a2036c12bb4b744292fcc8d896c93420a8dbb990b04101904d8c52f909439a3f6b1bf1a397c75ce4ff4a10bd321e32f50c896e4cb5f9fe148fcd9700c7e3f38a0871935d2d3c9e048ecfffafaf2587ae45bff8a2d289dd8d47f7b06fdd5268d4a3ff289ffd04fecd8f6e8c5c9faf11d6f6fd4330fc59920fdefc30d27b85f5d73a876193a657247b1116a4d88dd0f0bcd59392629480356ab2cf17a63a8ca37ed22a32751000335a3d2dcf69cea7e0384e53e0e8c6b4c79366e36504d2ff29d7a91744bbb37aa5c9d12f9c640496a7b6cb1cfd0451cda9917d0e0b97fabcb8683f607c9d1b604eb4fecb4aa48d52b0360a22c97aada4c7096c19099171f4c07a186d381f51b4cbcba01bb996b9554e059b9e0478645793aa30a3336925a3eb1c7705e637f72ed2c613f5f8072b87e48eea9cb45e2453a168bbf559b39059b0b18098494b0515cb35c830a7cccf3c6c0e832c6b61788e0722392361a643dcaf9bc185e774cd28dacd887daa4c327592c72822724bf16ca34680c22a181f9b470231055ba0661036fea389b93a9664a76953000e59ac28b1333357f4735b530d2d7412f224b67834c5832090394841e306f96038493e147d6a24c6611e268bb8b838088c51c0c7c42f282de337ffcbca009c4be2a3af655b4db8d0898c0396a8846aa9aea682b107034fd50b32ba44fb083a12bf0cf76bae99f4d95c07ef9a60a147d43421ee19081971ae9fcbafa604b49bbe7e14e997149cb875bd5f637052c804cd1b43a4e83efa7a40ed5eb2d26436fb374a685b5c2a6e87b44a7678d66e410ca8224db1df70ce8890106a802807a6c01a86744db1ce270f35865543cc13e0f6a2ec7f2b829377b660ad860382f4711f0042802541809ef5ef222bc0ce444b424b658279930de78ca84e7b9106fdf4e6210f6f294485d570c7a1d70ad9ef721eab3410e4c801ebdb406ff20e262a4647624f95c4c651e5dc463c434d93181cbd8f0d212ff7a179344b0e62015964adb30a67cfe6cd92186507ba3057a7a71c4bb711b15c3ba44426462bb43d3b9e893af6b2a8bcfc4489d0b0054aeddb146486575b6c1b4b2a970fe4363934c0f15ecb6d9fd80211f87c575fa023b685c0a32e2cae30f679ab7ac5a2a4d938b117282a896feac5c7898bf11c3e9c2b0f6dd224d4c9df4b84395c4f16a6ab5d8900f834499202c9f8c7ef4665882f801be5c7c24f138f0233a75f8702b105a15d9a4e94bdf56973c654393939f0bcc99a588841a9f98175bf69992932d57828c18daf7ecab2ce2131f858a69fa85e9a08b7f29b2376d6b159df51798bf7c5f139214d59a03f304cc360dfd6305b810b12a5e98da0432cf78fb7170a3765400dbff9c7400560453eff4c900e827baeb3f5af08ac51024bae4a812fa35dc8e4a79541a323dff7c4be8030a69a668bafa66e961fa1f15e6c825cb13105224e745582f280bc154a7be6d0b98205848678e9a11906fe4ae9e5086021a75314836b1930ff10eec9d89aa9be8cc143d083b2292279758786ac9dfd5d3988283d82aae45fb6dd02c613ea0697c35ec5f5846db8eecb84600a20a86d947e3b57d992f00d6de082449e12fca1e6848d8c74668c266bd9ffd673baab27c7749ddd23034904642bef4590edca2d370920a194335b7767a6522b0b16f27aa1a5d8889d0a18cdfc359fd39d7153bce269b9b37c90af5f08525114706fae6e692740a344090492472cec7eae86768cd8e2406efcecdf4280e3a03349056c2ca247cb74fd07730f5f02988f4bf4a9a3562a409b7e15690c46a07e717bbc90c7631f9f0a7da046d7c4442d2e9ffcf4c6becb6977f81700bc277717ad9944768494f2e30183f4cbfad1d317f9088051facbcc162fb2bbf9b6f90f6e7b055b7801018070376b1fd09701098d03052f37dbb647badafa142e5eab0e19ea516e033274c089e05b7b1503a10e53275bd4727b8f947d7b9f6c85a18babf01375f81bf474afe58fe2fd7e15ac01e641b32b85b246bf593dbfc18436f6dc97c11ea030ed77f13d17536840a9d077a29216079042caef39c25dc35dbc5f28daaa8d31586111d9c2d642463aa2f17eb562d44600eb18632e76e35e55d40900c18bad152879a2222d28141289879a94d216e60330d39d51c6fa955699e949335d4249566ab436265e40b7863b439e1a13f6d99e4e0ad8dc77d2f29d0ad3c884ca8332ccc2873ad907064054b704227c8f424b8d237c6e049861ec6fd514d973b879a83ac4e21475d3bccb1cc567e32a0c92a50b14012a487a4504f79d1995b9a1e90ba534b34fe4586711a81fef40a4277d201ac6655b20f9c4c588bc89133f11dc2f0aa0ce71e2739b302f2ad54778134453ad8e001841306f36455dfe1a0d165fcb08502e6719f0352a9e6f9c34f10a2a84404b7b56704507cce052864c9b7dab7023c4f3d25ea03c4dc434563653857e147de5417dda63ebfc3f349eebc2e76410285b475c4e389ce740a4c69748e66a3b098158e91734ee7b2b355ad363857250ea1ce18733feeb2f35cc4a7eb091d9163d22e84b622df62bc56ae3b228d1de11c20871a16d4cd7b92d4f749cf46b9bd1ee0e15c50e370a75d40e86b86c7dce10bcb5ca261a917e68c4361b9be4c7c0b57bf9a8b88d7c9b15816ad1e99339c6ffce45489152020deda8343eac8da6d311679d90630a0e19e5a552e708050afb20cb55c18fb3c3c52fc1e5cf776dec2aa5d3677c828b07213c9aabc43774aed044abc02c3e00dd97d01177484bbc0a1c97b80b02d6cb5aaffa69a15cd5128c3f6bc958b20e2626b42a3004790085beff8a2da2439082460f41982f6dc9b3303e3e017c8e00bcaa3002d80d6f41c5b99ac32d3013e25295ac25d02e3b37b40cc95f3b9c28717020d1b8635dfe40823927e64455aff9d51e501cb995ee1132dbfd7da1a96dc17d4e19cc66a57e42ccd4de2ae266a10b9d9987b5dc281128c1e053ed6623314c81aeaf6ae72059007e68229102fbe986526d62a058faef918a58a98b37ea616dd2869151fcac13f9dce68974d25a9d1086298397b839c5e08b30ceb36a4866a278ff817b60644c4a11c77f92572e3886e553a387447fa019c066c061494f33a104882db20c5f2b141c1a73229675b2dfcfec9adc9b98e5dfb4884b72f5b0a4c1059c47e6951518b9b85c51b06c62f30899c9cf1d8f80e7009188651d6440866bbc4c1bc7ee8c2143200eb712e8344b06740459b9fa26a8a033dbcc618a1d906a6a31749efc012ebcf63ab44e2e6789b25ce39a98701c1f3b935127ffac6f3b284a92f3c045e86191415ba0eeec784f5d4791cdf00592b775c387b0bf40136d88bd503c525e524f204a62f80d947ec2e1680363952d676c4916064cfcc2511b83270242c465447176770007b5cd7aa11a5c3e60a86e9eb1b0bd852832e84d45b5dacdb4db25b26f24b0ff059f486947c3173495aac70212d857fe5b6185d432be8902b51ad684749a43a51eee3ef8b51e94c27b8270928e3e0b19441e07644e0745139450f5337c8456b285d6493a0c12449554b83bc2407741f3e4719ad1b90104fbc59a24ed7259602e2cb1ca2848007ff295f48b803cbc68cfa36f86993c0a612360f2d7400c9ba93a62c8510e7bd0d28e8271c28a397c8f9f19206430e01b4ae4d088809a4991f560517e29c1a6c511f9e6e66f5e0a2bd566bfccb218f3a10a6d506afa99e5b1d077f427f8e6eb6c13e1af4e69836b66c4db9707227386f3a20076562385eda75969a717893b62b83a17f4067bdd7b9b14cca728f0d4683a3ce85cc03c48613c97e2ecb7486f35d179567fc65778b7030b4083d906c3aad875f7e91b5ecb475373e4362a5399248dcfd4002e41ccdc881210b7adc8e149475f0266ba799902298f32739301b76368ad94398783a0d444019799e7d214300e0833b0bdacc0b4acae510f36ee89c06a5c000b3a1b495daa3ecfd8ad1bc3b4dfe8355f54b942b5b935076ca49e405511142812e54f0162522ff2c8e86a8ad6a28326442fe2651a457075d77bfae2c1d9eb382cab346499bc929bb27da6767d19a4f93ec279b339298f87cbc31a4ee256d9f99a97a35ad9be6e2da34e5ae26493f68a12cd96351c995cb20ab06ea6e8acf14f76234f86bcc5eb5d244775f31686533986d7a34280735c72646cf2139831505c392a1155d7ad63861226978241714cf875d98c1b71af188cc5a5679f164522d2d4b0dfdc7cbb573709b0deccd0cea01ea4e6d63a2e0cbb5143037f089b28306334ca18e9995e9ee94574a477ff245a918f6c1846a676dfb80f3b800f334b9edbe5cd63be1482c15894bd109c7195602b7ccd45de9c58efc97ff7be17726f049afec455b888ee2189fd641a801a90925f14484ccc4d28202515de24edf177c6c1de88b0e8aea25c749a95700ae098c0313427214a0816d2b661a68248555a41dbe69602b5a9dd43135bd018f74ff162590bf075c5ece05841fa7a5a84d709663833a7152403c88e9723e70692fe9a1f0175ab1f232148bbc88e766b82e22c6747e793ddd19d5f72dd1414480d3a7202f1106c7358fcc17efec17380c62964847c951987d64703e14f9f48271ed47f1457a30f0d53ed01be5ca322b33253672ac47dad415d028748cc3013e2699c928f4f664e17ddd40c2293929ffa786268b81e223cb07921c7ae9549bb1e53e8aedcab387b32c9a7f11889d4383ff4d9ce40989d960a1d20e749f41e178b133e483bd80ed4478aa0be033d55d61826a09bbeb1f10e045e8380c4c6400c04bcd870b8a8f39145376721c824b333dc3527e3610b7a8c481344b21d6b8f0ac8b735b69df22e3a7e9455689c304221570c6d133705f9368e7d01353033464420ba862323521942cc23f1a68cd322c6c9f544ea4cd189af0dc719285367e7ab8359d55959336b914df7ca26c7e676cef524586b5a01ae9baf34589e7c2b581ad42cc622588e996d0e809b299171c9452c91a69fc65d611c56baecfad5b239e18b5935b7fd9f575a2371c72bf1916533f01203148d22d8a08a5fe1a7fc186143e4a5424799858353231a9826a395079b28e75e7ccab9e9b8bdaf4263e7b2dfc89cdd827c7cf3efec50f86b5eb76094ff4268d9faf49e1db5075b10727148f024ac9af5326525b2d8f26de68c22c448ccee7db2faafba9653ecf7c65862650df08a6ed1ad2e231caec1562006463b72a60543b10942e6a8176f24ba6815748ef0812c8482b50a54410011c88e52f9c60eb05a1ea9444ee5747038fb13f2eff53d27ebbbe8077b1b18736b00d43e90c26efccc0e7e538350ca3851f210f3cb8086cc68cd9038f9695e1e4b3f1c4d3d1e884e47d41b77034125352f118470e1e7d53a9748a934d03e8b4477198195c53328ac33f0c210621991222115b9696a2c81b10f55f313856a0386b86a003acdd17b41ec8523fa3614536124b5acf819bd75eb5213704dda829bc85d74a65b172bca009da1b5ec54f514a9d30d0c0ece781a483fe752f8fd2ade5a42c84e4c0a1300e69441a3c0850b5223dad0211e14132f9d2da5e16b46e55c8ecf46054060fd40725d16b145d37c0f2cd5a1765217c5ff1023241cb30ca03302c9ca45082f098815dc044bf1f516cecd0a07379554562ad950cf26f799840377d603e6f3482eb83d13d21061a4bb419a0b22a2ad52124aeb7b1f7e42ab326163eb697a93509a820e83eb144c9be8c735138020195047101221bc7bac483b5c99a6f196f34aeb91c809d0f1360cac89f850fd73bcb23d3e9c2634bb4b8f7aa836b29215f08f601f4a834822726a47498e6459a431b2b31ba8e204da5137be156c666c94fab8720329256292c7d93ef4365a7f258aeecd2116ad226b962a9e8edccbeafebfd07f9782e80e85d0141c5e8b2d3fbd20d2516519fd53126ef042003ecbf7b8394bc9f43117c911154abd5faacd33b3ed31d47c5d8bb8dfd3d323e8b8be55901c9dabcb29a2d2b3e7787136ca22b8c3253b9d885543adacc0a9181c9d421d90756fb5e8afde4bc27741a71425a51a79bf5207664690feafac9dc066f990a8055760e0aaa0559120b0a053e050a016f69dc4c28ed0a3d55f5078d96b136d4f823c1c0f74093c742800cbfac1c614f82cb9f9e04eadeb3feaccca46000cd5291e24918411a0c9d8231d4f61c5e7beeca0674b722c934d53236f480fae3f3415ada4677e5b6e97df42f412d25fd2c1748c8c487c9509a6243ca1ce2a5b2622891a930f06ff221462e7209e6c7069504d4107695fdccb1801627219ed3ee23c3dbb42770543e6df2bccb2ee25c56ea4e65c632642f6ed0a729f4eb618e6160a7968c760b77f41050505cf29739ae30941e25667dd1bc7f28ac5201414cbd55948f1a38b6193cb491e6c2cef62c226026803d417e7bc5b20311d653ab18d0e98260c88f7df7aa10ea3f9a1e4a0761acfb6c96f24ac1c04f935c0f28b2457232c958dc766eb16e0b2b09d9e230fa769d49997071e4de0362f9bf9caeb58497cfbce2d8ff694e6f6f665aee96a30c13f882016f0e4e07dcb78d3d00423230972b3d21f1c2c55c682bba906ffea62f6657eda4dc3a28e542b091333b24dad6c0ff63a9b994d663bb7d4d28b29b3053e47a182e12e9a443d73839966d924ef3e8966d7d3ea4621f361e517b26aa8f110d6465724fa1b89955525950c2e668f1a4b30ea053578381ca8a19c5ce62c2da349f0aa900a335380a96c215e43a636c2600f58b89538887fa6f57f4647dc4dcbff5caa8fd6471ff492cfd41376b1821a9d468a5a2d40143b428e0f2821448c8e18d601e3abf3c3f3a722a8eac8cbc3ab9d88000b8efc250de3c7d3e785637230269cd67aaa95d9a0cf3ba7e8743cc89cd630b59e6d8b5bc88714e6c6f656d016dc1beaf8ce5434028002398148bb403faba14017507c71361cdd39517a16693e9c8cde19216c4ec87e46477328d4f52365e8abd882715dc7f960dbb9d241c61298b0782521c9de6c8d66d6c8fe6adb79dc04729e8b14cc6178e05ad90bc9f879952664fd1ca2a1adabbe51e856c7d1be40cb6a1cde605237a188ac6699ffb09686dc82a8991148e1688e99f8dbd38d85b40dc86b333039b80792c0a6001edb188c156e7f7b0d2c13d8447fe5bd97786100ebd1b24f87992014dbfdcc78875d745560b043cb096da79f33d3bd40ce29a6eda0c39464c2b823118aaca132f9d9100ef9c991119c2e4d1636948f005f25d878478f0f317387f37bdfa3b1c7b74e51951cfcc1896c112786ee4f965d2b2491bd611c49a36044cafa5666bab4d7c4ac4b0cdc3f835877437ca65f552fea781c567d4c9d2c0a1afb827f50d3a9bd6620e1dc285a9d5a2417ce7adb52669cec27a421d6f4361c4709427208a4cb0a634f7a08cd61ae4de26f5457966fb48ed84d3d938eb025c5e3bb8d1d445f0c75a9aee019a4189576ca408873edf34a301f053162764e17151d10b0e54659094a3bea2a93502da86f0a17d8181e35b4b9fec02942e47eb17b74a5428b3fd8eca8645524a8a48c497048d3dfd679d9a99e2267f10a2868ef000e7f85bd3869a72280040c237bbdacd3c488c6c038a93ad9f0583c60b68e6b4446670b2284231c11842d0aadb929d1431a0febb051ec33a4cafe22840c826295c0f542eac265a8689daca47329a983d0de1f669ff9787fdc099937937233a8412be838f77807b26bfa2b949ef29343c0c7b5afa9be4149221764fcc1a52b201293934c8e69b787385a3a98c364e7afd06c72c594889763a0f84d3e83a9ea1cb9fb06220ef1b2ab4c16ae5c7909663f39ff0c470a404db927152c05889543cc7005bb6f8ff70d4bed81b5147f1fbb4f2700bc6fbadc80148f4aee289ee8df240203b25c7cccbe317c238ce6e8f77a4e44b4d7f2b625ac823ceeb2d553d9c2323a28531fa7b70cd526c0b1504c0145ea0efee2a70bf8ce46aa1584f7003824a7adbeb746f544a1298b2302e1bba4e07a483de1714a32ad81fbf05500a70c6aefcd662b6791b18d0734ddbdb64f283a4ff409fdfa47374aaafafb3465289332e20f74c948e707290159d40588722ce4fe61a988062734e47a4f1e086920c2c4bdf157cc206a670f1cbae26a578801da111322a64468708973e3780d668dd90588add099c4840ac5d54afb40e047f3bb9bd52e795327a7a0a7e98583df8d80f50240d15a76aba88e7ee830c30014aaabd2b0981a8c50de13c4583d0e39451d516be6310ba87741789434c189e32b724924e669a69b2cac9a228a385470bdf25abcf379cf5a94fa84fa0bba2efa3a384c0bc304800e8636cb8b6d24abd890af55b8fb4d4499b89544b22336bcb04c4820fdc5dbc8a428770bee3936b1478e36cf55607d44f59688fba01b995e54a24d8bd7f875f0fd8a98df01795e115c991005dc8e2261113687d610b445a596da35745c68b7dc150548ec81a9921950590538602dd85a5dddb4001d1c2e37ea4baf625868f8e6d8ac9119e5484a313cdb8eeb6530557775e00c09272135b993930f412ed0635ee4e65924f293d8e7f99017ec37cb41e5438d2122d2d4e43c7b4e289aba261e82d73e6a807dc5ea6e01b14ed9f7a3cf83c4abbb9e53dd12b1a9ee11642da7747cb71caccea03c4820235cf99498540e25e5f12610799d31e9b0f17d01d65eb10b24aea2d8e71d9809a54140d98041b4b8f1bcbcd082141f53541902e7ea5a1d32e967a8e711caaf61c1cd2cb5d4604532e2c69c7d40047afd6c5c69df593595a3236eaec2b481f86e0dcbd99e857a9dc24c99262597e76363947493d941403b2cefeb117c6d79fe6c663fa93bcf154335c28f6e65cb7c802c502fb69640f9a580aa01d31c446d9581d09d2cec45f596596c333bb24999e266833338bbbd621bb28bd5378ac4742e2e8634d2f99269c5012e3420deeab6c98fd6a725c3d44320bd46b3d84b11e234a41f1f6f4bd5d38b2cafc89c1e4710080a89a6e14d93b1809d9d216dfc4a83b29512fcf20a8c11b1197c49b42c4e3040baa8d5faca87dec1ecea146542fa79b08d976c6359d2e530253ce3442367f18e12e2d7b00ce1581528b29f87ca85b006c675192ed1d5d7785f723a939ef911a1c4c2fb2c2aa09abaaa8fb52fd3880616566e85842153da86ed48f0a36259a7d2cd4315a05dbc2217438ddd8c069f42c3e6356a2b8b0718bbfce63067b534baf27250e4f7cef252430d02adaf35c1962edb8116aab0c918e36cefe05374982753ffb80d0f867fe2ddae359062d234836b5332156049fe0e2ce210d65c7d0679d3455febf877908058d75a33bc34d1155e5614b002b16035be28fd492e284ff9b2543a95252fbfbf3405263dcca5495f3f310d92bab89023f95705c100587c35b222992ae7002d8d68e1e33e9fd1ad7ad0fa7fad9b0004c76e7d0f08311138b1b319b385c9693c9697042e82764946185b668cb5122acc0d826e9f6f8b4685becc55c531a38222114199f4f77946855b98181976685a4cd82627ceb4110366edc9343d1094972f09582a6766a05010abcb193d2deb2d37af155d28eb8032e8b086193136b0804621581e681371ca80db7165710bc4a032a1841b52ac852454a9d9b1609066daa851f34c766cf1c5a0c15fd844d9b8e2944fa64e4b1b0ce07785c4eddc14880e5bbecaa66a0fe1da81cf8894dc0166fb6ddc14125a34d9306d808829391871ac4919142ee48c40e8f3bccd5a06d746c0140ae26291d9ee330557f7c5c5421cdbcb497bf27a5006fd147f63eff544707d1e599a6d3cdccb52b13e7b3d654153b0a69c04831ba602224785f67f61153dc60b9220712f59f97a386e378010ac69d032baa2894957c9916c63c9aaeaa24175fb76a67a830c442b864e1991730f114241641eb297f133cf9e290421556db3e97fd04757f88904e27f4c3215523fbdc04a2c49be1545d3ee52eee72c6d2f4a3110938c5b4d0d277cb8bb564159100ea1f1c9a7ee5463cfcf3f3ce963ace93f6fa662b9017f00e4a868f8352a99681e38b04115bd01def38c7f6e07e1fdffbd6a8d22a065371b395f00ba98f954f74443441d049dbd9dea1083100ec143e63ab637a5339c23ad43f820653f567d981afb92f4fb0c3c3ebc3181fcf16eedaa7ff8bcbbc1480acda5520dac104d03dd4b1af0c97210e31168286a591aa889f09966329718f0d36963c71f9f95173281d69f94df3462cc6b75b42b9fcf85aee96164016e5d14dd4c2f300700b7ac4bdcde6a65f989faaf80087d6d355bf466b2db139001aecfd1e333006cbb47066fb1b65d2ed54026f71d0aadeeff602d89a0ec7200dc0658be932de10f78b719366b3d4ea514dfad1b145fc28fae03df2e919b4c311c04b1aa5fa8df46979bdc13f8ef5944b5f0841410a603e00c04a570e0787b9534037232078765c2aaff246d8e00d879e90f44bd790675ca399f0be2d076c27e607dfde8b5c1036f9b5eb4d475566ec6a06772988f9c7cdb294ccafab8b68d1029ef4b4f943e632b9ca6cacb1d78de13ec3f016fba9a400d507ad084ebe518a9ccf70768417e9f08a295f419bc3326c75f86fd34b357caa3a23423e01bf5cf55093051e58aa3f17d7f48606e0c77d0da972b0245467065ed236edd11d70c20d84e357db1cc50fe37484826580929a869738a6e612e4318230741450c96c2182ea25c14fdd01ba03f14335dbe9688001ad334bc9d97fb2d56971d7a9e14f8ff2fe453933d0f67f34ee0da2e39e39df3cb35bbe24ed372c9d9faf2b7f6f8b7cee4be22e3a2c8d91a144b0bd60cee21712bc92792a02f315bae3202def9804f6c406701d7605c004a3bc1eca316367e4983f8d5149f93357985dbea27ead810ea4e95e95a63b65e22001fbdd713ef207dbcafceb5603c6ac59026f414a5db7150036b52067c4a865a372d325e2883e4187886d793e68345bb250360eb3944b38e17caa60871f72b4e8e88803f7f4dfa8c0267bf15ad50c47e1810b57488ed5a5fb12cc28870f4ef9267efbd4157be6b14be162632c6987933554ed791c86426b0cd8eb5b9b6d368ed715f13ad858d40ea35cd5262d3901a4d83f2584bd5601f1e77b2f8996d17eb36061f7bcdb810dc7245d5673d1b56078ca7c2238f6bc5f7ce097b7c481a87be064e5ac57144dbf9c21c580340ddff2894de13aaefe32d9cf396cf0634bf382d20fa6695d154c7197c9b8ec7506e247d98d3df1e99fe02ab492b34d32a26bac2ce32c90c234ad18221e587a05ae23e5359ac0284039a1bc0cb1d8f645a75b464cb545c2d748940a6885b9c672b35a4641e340d4488201dfd4e127993de473e4dcc83c159fe57fee01b80f18c36bb4db41c4797f5abc90273d967800de7cb769e535edb4e7946fe5a41a483a51baea65d3c3b70e24d5c3940a809f239c88097dac2997a6e87dfab5f27d7f24901876aa2472ba3d69e480489f91f9f34bad28985cd01fe617d6c7827a13dc488adc3e89bac0df29abb54389fd666868b784e5cc25079c0e20f6548aaede97e0125f512a6ea302a581571664aa91fd86ff9de97f27bd78730deee2af36d4666c07268490cc49c62b6e3ce5eaa256f08b39aa10060f86e8204d9287480e4b621732055d762bfd7f6b227a658a76b1374564be16a35a70ee2b2a7a087b241e4cc1b2de8fe301a902befe769b6b85ad03fa1437a7127f2eb15668648315f18d6edf703d65d94b5de4709ee70c54c30f5d64fb692324427fa37e2884b40457c3d9a9ea070dd32d642ef98a95bd2d7103c361ddaac2366768c2a4588885ca84ef49ed30a620bd5b0fec9e50a8d9e98223425604a6173a597a0e0c3520c6695981c0b3830224d92fea529e93106ff9f5fc3852cfc2ed6a41a1a8fb5da023e05ade2af9b9bc498ec31638be2b226e3aa893785508dd3304f9974b92c31da26a78604ab35472c9056f437ed387748c23196e9ff1193b0979efe9130d33344160ed5621f20509c359ad0517ad96883f8faebbfc795d2d062783819a3496411bc5ef86b059accc2eb5e034da126a17aebd366d28938cedf084795b3372e3c9bae5ae0a1164f8494a409b81741026647f8c37a54ff96c342183a89a31dcb4fb9951154735d3d0a3b3611192160bf5ce69ad7b09f4eac59573baded954a3d5582dcc52346d30036c5d930a8f0a09b68cca937e181e44df5bda0e10d9908948b365b970ade749336c0c0f4f1c269c5f93b61b27e5cf22de3310095f1c190a2084e1471b60a22ae41e80c5c2ba892a076deab3bd75a45d04593865604cb6a64ac15635e35de8bd048edb6f0d38554996ca2146f663d42afe548c2393070b04c5e8e0009fda196379f0ddce8e392026f5d116dd889cb9058313a532d82d117a85a86fed278de34ad703c5d633010c2a8e18f2474c7d4eee97994096c1e0108520110d0e06a208bb224f83e88d46185096b3e0bbfd44b74526acddb4fd824b4a4d49cfe5b73817b1ef9e33200eb09cc3dcc61028528b63451c01e078167ab04f50dbf70e8f68e1629460e4edab7b7e037f0691cbadc5b9c36655bf0328f992cc0cddced284c1faa3e1dfef43a9d91dbae9dc3a64486aff7434c39ca5e6b8051d454a9806c6c23790748a0f1eea5e7f37ac3044ae3c21ece803ae3ebdcb964fbd27437f463469659b2e8c2aeb8b1c70e3a7faff68c7956abaace1f3bb6e81184afd2fe354327d20d818ddf78f4c7c81747ce2343afe07535e65ba9747b6b223bdc7d362c06aa75f962595041d408f5e5a65474db4cb7a755d349a1917989dd1949fb80630b14236bbd60a313ac405f09ad227936d8be98be57be68d73fdef061faa7848795151594f0d040571ff44776bc9d79dda81a939185e67195f67f984c50258b8a70add15d6e8ae310b1ce0407db687fd739c9e5fc90115044195f23f8cab77ef195fe3a49b6862b4c590de31d034526481795c73f02c3568002ea85d445fb6b1e4dcb7321087113348c84f384b34df16a7f237e17cf8c543f463e9239f3bab68d04590bf17f0f58ee9434c1c29ae782ee3e5dc6f0798773ce79b82ff15124356fedc7f96edb9801eb4e4041a2021e312073b5318744c1a1c6b14e86e2cf99ae5de89a291e5b5293b484068ef72cae436abb4c0b6d03615690d43b08581bffc74019fa8e3444fde35f91045daea9fc478468583b6d571ad30ddfc2304cb15bc04c13a46803bd805e660316d5300e3498e7c1f4196761aa731011d24d9895dc2927e34adc7237ee42463585274bbd5dd711e3a968798d2ca294f7b48c81ef16878c0854ad9bc449d81fcbe961110a57e99eef1858a5e8d9f62d5fe5d2284d9da4aaa637daf150215b06736ab2c5f0723d0f68e79ec41521a588d670c2ad0dd0349c9b64c5496c466517e077f52a8c8afa0a59e551efb253fcd3e554fa1284166098b06529aa97ec918ec4e479388e5e18dc864850e5bc05842252bad4ad93edca857ae6ac6ec5e1c658a488c1b06d692062ec9a63ce8f87e2ed2d1d493ecb3fe8993c42284f5311674219235972ecdaeb1efc74feed50d3086f38719aab3991c3c6cf5211741519ab863f6b4d5f4299e7e832a52b30962c677baeccd79cc823cd155146d08eb699d7e6bf7a089cd5a1eae84708144c64578d20102b1010d7f4a42ac0a21404b02243a01965efe8371569cf7f59c1bc894a2b59f7a30240dfa8157f02e3af0effee7b2b03097f7e0a3689abc8b5a3414c921155d29ebeb873693092e0de1f15ea5f123ea24dedd0d159041f65db5867bee85d56f9248ff4b0f57a5caab5461fbe5ecc1b9b524413685db363589780e7daf644d28ee37f906f38fc4336b4ec0a1b7ac4030f6ab9ae35ba7cda6c672dc764e3edbb60e9d1be58bc870f02bc4c159dd16cb5eb713bd132de1f23095553e66f028e23b0ab0d6fd0193fd3490b0e402287e32419e7cb668d689d25d1bc3c6d0dd9bdcd841c087d8338a7f4b12aaf06c61e799808b5a80f79777b65e7b93b0ef3b66d2a6aef856777ab3c020cc8dae28b6aa6020ed4de4cefad58646e4ac0039b6f0343469ddcd1681462cdc593000ed0867d67a46cc718c773a4f652bc4cfe09d6759737ea995235285b5e7723ba9dc52ca15faa63cd342999dc060f6323001778535acab5afa40562dfa4894a71bd5ffd982136e9844c7955ce9792926896b4344941556a26edc827ec14519845e723ab0a3717efd5c7df22915a050f4959bd4923116b6f13c9ce68f87028926d5fe03a4376b690e63d00634b9b380ee79b2c7ac66a063a4f7fc086be5e874c61e0ba692cf0b1e16302ba1ae2bee104630a9c43c4207b7e99484cb4dec0915783306f9a08049d41cb2eb2ba61ece541598f02df88062c75bfc9064c4da8f3c87cbe707324349625404a352275daecfc314927fa5cede5d018b18e75dd5cabe6041c6874a8c48cffd838d4302ae0fd29be986bffc7b9f450bfbcc1e64134a13dd75001d42062870067017fb05469696e7071e45d1628fbd024e867f53c1a5910fd0bd8c95ea23ae11c2f81626bfce1fcb3070024762417b450aab1f82764f8ac01ca74e552882862a3f897b0b1d3c7a44a00a7b48e73c4e9aa35a9f5b39ed7600b76b5a7f8c57c742ef7712d3bfae7c0377ae182ecd6e8d4e5de5672fde51d42413d1bfbd573f1344a213bdc896f70c8c84709b70e291a7c38b4bb23c49751d7b429d33b0e164982313aa64d236344af68476629c4d127b071a44844e1ec6dd6a0a1bf91ae437db1ed61deeba9922ea642827d0e32321d3163ca075d782bc311028620cdb5de10b8e4d45e1b62abdbe853ad875c157f34c8b67bf48e2e651ef3284a7e6bb356b72849a744d7869033106c2e735d3d3508403bd52c010c16a41b579d0b822e808e0c583f4f4ae0452a78686eccafd5664b524ca77548f28ea33cde7006ff27ef0db2840ecb521fd2198668e6885f4c5719846a7acd9803ae99246e15e4e79cd8db39a1b11b5322c92ea6cc49dfa2f2b1674a0f35ac9f5e63b8a0452cbf80041406cc969dbdd3985daa9cf92ecb216ce5a81d9ef0cba09e673134d1e33e8c5267b35e1358ce2e49696f40af33f4b5346096bdd3c50c9123431dd1339df1771465ce71ab145e6cd0d4486549e53330d74a4651c0869382df9951359a6be34672d310d29c88a6508f11e073980935199953a71160af7e2a5c751848ebf8124e3f623f59dade4f1b947b06965c7f4d6b79b61813fcde5e8a27569f8397c3fbdf70ed4b576486b44e9e4a1a385f4e0593888c4b1afe3a03d778e2a7b4bdf70857dc55c6f03504aa56569ac66f9a9f8049eef5db0354c167423ec69359ebcc80f2df28420a17a6690be3cb9fc58a99501b4d043746a29a8eecfd1c9276d2a53eea1a8468715697b081f2c8eb51c66193aa7f81739e89be6be28c45f092000c912bc22918440a6ef3e798b74075d18ccf2b9dbc055a4498012801edfbc09ffbe69baf4d0416f247685d0a3f94766dd48cf45653559e7c7e5a261af00146e4bfc7143735e534fcb52d0ad1bec84206643fa5f3fe6028cade8f5c8cfabcc46faf22fa574ee86737144a5cbe668cf585375438e6c32d0b1ea19643c12da7423e895fe7bae83c5d10ef0b21c06493eb8931850a4343a9e504b709e2472b39ae4f9e371d20b44b38b073fe8dad5c9d310da7424d7e8be2eab5db1d6ab85cc8dfdf1741200e54c67179fea4075d9a9c7a4f8455069f059bb39d020c22432da124a9b823852165e79cacb30f6473e8afcf8be954e5c1a2894a2c68cb9ba8f5458cae4d17bc89502296aade703cb8493029f15c27ca849215155e7e5921ecd46ac42052507e99af3e0e2f8e4747b98f3339cbbff1f0c457b151c3103456357ecf43c97a0ad8883387ad6d550fe7be8588096dbc7482d49df3c71a68d4feb604ba1038e7be6d37efa4cb9736f2c7b2e84acf9c90bae2f6b490ccd7f682ad6cbd19586205235d03f7545d2b36a04e84c9d61c835ac5a58aea009809b46481f1c79c79a4583be8c0df45da1900dc60d5686bc0f1e55d053ab1ed27b5f036933eb20e24bbb6e9d580eb3f1920e62d26bfba1b8141788b6606e5232ed1f1d356b10965e5714a1840894ecd0748eea628c33608b7a611769dd776143aaa8641f6a5196a7c031eb1fa88fd71f81fe2c79393cef967c5d391d6bb4e0bbaa4574b1b0803ab045256bd37a1d55a82401560b221eca45fc1b32d073cc941c13af82170bb64210d45c1d824a9b10b42304a1bf411e113d9631537283fa843acdec8d9fe1b4947e9681ef5e8af04551824a544eb097c5727b11e0a88e3466aac3df86ce73d65b65dba8689b9f9082b343dc7290b96806a059477fc88c7ac81ba5fa155b91a14a81b7c177812810d1bc075e79b38cd0de2b3ed5508811effd043ac73479e1c7bb43d47270de84d0fefe0b6ee706a219b588404901cde4aa35280180834a662292ea4e635147568b88260c98ee610c5d54797b9375d4c8b5677e0d0b5e4bc4517a1d43b242b8c87352b78cef161aaba0c8622b08323398db9eb18c5357d969321ea04cb6e90636b94b1bab3e76f5b77370708b8748cb291f30408c91ef13424894e1f2e76c3fe4c3a152ef732f082e17d1c1f7ff118299d491038e8ea66f87e00f87eb443050eb8dd41b05adba3ce23757a8a9216fe03598b3d14320b7ee49a978334ccaaa0b46501b5d53c7a30a305243ab6d4c5da81e41ff554dc8620ca0e1de522c4b8895e272effde43973649b097b1cdb206eb57a7bb976344d13a9c5413cb83fc99299770265c988d5787b93603652d303723477363853bee3ebf3cb69a5b9b2ccbd466b5c8207d0b072d75ec6291a8b4fe59e3c138bd1f18614d46f1d73a0055c5b914c643ba73bcbde0a89e324b989050e3c20102cfae9b0172f9cfc3d159847ee34ae7e19f69200ce3ce0fb376c172e49a5cd4000a06ee4ce868c84a76d538946a0812671f518e6b32c49a05b1a38518c63b1ec69a82cad14298e39d0c11ad41ee6c428871cf86b1c66072b011e471c786496b90b99a08631cc6a15c3b98397046961a0bc344ba893c2ab63adad5e0a180d401a1139d6b3dcd81ea8b7a0e977360ab17879d735560476806e28f05d776fd9a53c8050ba91a64d865f5fa41c90a41f903d9b4027cef16d21e414674b0f08510c8697a2f69d20cc18c5c438c5587e1dcacbdbc952242d0021cdfb35b29a0e9ce97a18d1cd927d3f8ffbc406141e802c5196f06d1b10c07be45448a90e79d2ec7d0b2960687133bc97be6938ea56217946b898ab16964a99bdf69f06faf636551ba557f654bfa1192222124cc7b66922b609bd44b3380c72c983a55508ae853a82c5da6a6aa389f771e1752abef4a7e83e9292d19af471e99bd6fb1dadceeeb1e374acf57737821423c60e9f9bf881f7063178d353848c7a8ba2f6ae096ddc17e50cbccf9c8308f8e0f967627b2c013a4d687d4ae7dc192f4f2b0245121067c67b43f41d2f21dbaeeb4c74fb11f52534c5295493ee17e58f64595b66b4e1ad3ed464b540333ec33559f721682d1080dca7729ccecbf3c6c089c161ad5b793057f1863b28f407308f433b210c57740f3e430c63442a1a4fad210b29f9bfc83d9174c84649041227cd644f2652e1c1236e88573567fba269700edac9a452c4babd3b494099f23f18413de25ab1f468d06e75ca2eed4bf0236be6c6e4d6f04b1e714b34adda427dad5d642d1ae5c48edfbf4888fa8bc0f87210892d8adca010b5e0aa1e1750dd34806303ac394f1430a9f4d143ddcf346a7b13bfa5f52361c82fe1a7ed52dc77849b5ad860bc547c1fa07424dba848608fe9f147f1ecaefd759cc1abebd424cee6b98330ed10ccb87166384cd03ff060e654ef4ea7fcf6df6a6566e13642e5939b8aa1a8c4e8c53fec2231feffa0c2e7c6a6ba9084acfdffbe862b982ef25365d1517b03e9a468409128e4aedddc6a51790137b793df1b6fbcbd27208f82dd4690be57e6062b1772a61f502ccd572e0eaa7bca992c31f758de65acd63e78b79619dcd8f5a840723a019958b7828fdad9c84b33a12ba7fd7ad2ba861fab98cdf07f20d8104acb9bc75191d895b34c9b4c1f5cf9c56bfb0d7816fde5e33135aa89e445e5ff9c2b30eaf015fa0e1419886580db51e490a0f9b50423f826b1e032d3d83d2fd0f8ffeedd50650f8bb71ebee0521b6a8259c60681e2f6c32e1c92be19dfe8e8779d538e7dc11560fd8fccc2056400ea343c412b61c592cd9641c521d1016d208b7bf421af809066905c057f5de0762720e601acb3f8373376f17a329c78f029c682cf14dd75d580824d06bd765082509a02456b2cf47a289bb4add18aa608171e69fa1da29eee2c42d046bceb592fb9ea002f4bd4ca6d5c618ab1c502939d42eb9f3ef618be2f69858adf17b080e9907e129f461904a3c9eb2494d47d9e90938194420710b2ddb1b48be0181ed5b2d099a5bba744609ceed9939ff39eff6a14c486c4ccc951a7154071d80792d24b9ae4a9da9cfb9759a4daaa064b6919308ce224937e375f9eef544e9796a621604ec487042c7197088cdd3e7f5b7f971140709c3b7e3f60114a16cbf3688d617992b688b9fd5d87e795d843f875d2f567b454f27b212473d8ef218808b57dca7c6a01d9286cd7ad54f52888b411bb5809505e4c3443dc573d35ad2c23c9f783ad6fa2ceea787a9d5c187bff7b6b590ac56ede50dcdc93ed982fdc516cf7c03d84d7d199fc366810a9bce0ae67ca8c9c81a4f8d4d3a65a5a946adda1a1aab0e10fe5f5b03612d3018a2a25c406cbf8c2465de332d816dcff18003c93a668fcb714c02591986f7b33d8838a7181469702d456cf5891403cefe0df4876c0dfe86bbd866f8028e9a2a094cdbaee704fc7547f0823299ccc5258c1771c02fe9a0e6d7ff4ba58f0d9b8d851b2441d0e3c089115e3c0b65c7a367a3e9afa9611d17714481ab5a56260318447672e5ce06ba58afc44c628c99d747cc21a590794de69b69268448ab9d38d5125d95db75af62286c922e014c22280daf6ae7f34b9976539a74e0fca294501703f21dd5d3cd18382d8a30cff5953d99b80ad050a5d2f1daf61b9c178e374d1940fd2e9f3ac0714156b970dae85d295a5eaff81aa80f5fa35f14f875d79f43bd9ef3f1ab389688800070997f830ebe04240a055c17207bad9564f587a814909fbd7d80c5f31ba5ca154f55b2913ae05d74a5e3d6a2444004d183d5af56f6b7b25d41a713ffb4971d21284481f492410a60c9f39969b8f93e6090c189700c4a1a50dc84fd1a08e81027214ade42750e7ed32771667ba3ac9ff1ae8458dd1a9296eb21f78cbe6ce3cac8c10e32173f0c0251b9484c8e89668e24976d6364c182b54aa6c16809ad6b4c9e33b6873802453a39c617e4da1a71e698a1906b26c3d1016b20f3655c6bab5f1c80414a327ffd5712facb0f3c75fc5085f66225536038307f8c2c4252fb01870fc78d2200ed2e3a3dfe9e9b041625c0b71adc9b20126951099399e1a18969138f064fe2564a483c5ef2b4a1208b71ee0e46f01d4789438a4fcce813fc04ec596341ef64cb9d2e4071c0e66ff2a2bde1132ed62021b51d94fb6f241bd35b065f133c5f83b7f2cbaaa612cbffcb60511e685ef3783292f3ce5a1c11518929532631132b0d1a29e7e49a8fe1eb30c493f25ef05c096959d54d4a73a63827f8ad7ff1ba5e0764d28ca4ab003645fa5b07a53e76227bb517a8002cbf8e50dc353ee25ed126617336115cb723baba81e9242006e598ddc957504a35f6f0a948ad9c3b64e9c0b8380dae0f82d51f73c8af822f4e4031590addf40a6cd8017a82d95359508a2cd51cd4c6c9278e218716747258403bdf9f9f64a31ed52b00b33eaa07f6c2fa89d453050f63e21c8d2eea320395064100f0180d0000000008861c74686bd4bd3f6fa5042a51ac5bc98d33de31763711112152e4962277b405af05aa04371ec6536739cbb29605aec753b50cc920a5594b085317b141d4f50105db1423375e105d89112d71a2612e8b16109d0bb1caafe4945a6badb7b24f2473f6debb8630fb44921caf6e00d13ce7546698932a4bb24a6badf91a06380c908a52024b95ae29503008c285cb891b5bbeaa22c0c853843a8b37042b914b31a32893b160752c5d6a071354e4988c15099a25e15c62b40885c05a52050b0b39250df7451eb7109a31012cba34e90032212d9100929525258ab48cb6709923af0ba5cd44ef0a23aac6a6c690c6a608d140595c8c1447aa2c21eb02e1e89a0535c8429c736e42987d22e73bfa4899a37b927912923c75ab3d95d7eb11c49ccbe82871cb5ebfbb9286acb278a51d718b92b1820bd92f68068249e80cbd1898170c778e5a8b536baca277d86e71561de044baa57463489d19e1e2a16b868727aca7235b9eb850445d2318e29113dfc18d95e5dea605175a6bfd43c937331f4c6f603d9e8469fe63e6cb8d450961d817b2db426badb54631502d25b1c83103ce838c9029ab1e53183c359122b3773cac69adb54649924b6b7d1271d39199db3ad1ac9810b931cc608acbd775e505090d9e4220cddc206613f11445ac4491ab0e2794846d46968c70e11882702fb8d1522a7594645ac203905c4b9c738e06d468418d26d41ac8d332a7925c9c73ced73827b9c436309f70403be7a12fae93814dfa8ee93bfb9cbe5adcf89203041855082556535e4765545959516c97c5b622be05ade6dc508ecdd066320c97b4952d39482eadb5e6224449adb5d65a6b0d93e46ef1aba7aff4ec4155a8ccc842897b8b9af224a40d4dc611dc111d62716857458e02b0829c22976684ccca392380c906505c5a59b478405a97c4bcb8906dadad5d15640feaac43ad611525bb96386a814ceac84f4887436afcfada393de444decde8456bada5b24fd45aeb1db02b5619f418220a82d35a29909c4abc695145d1a0bdccba3640d72966da96585870912d4038b5947a58ca18f689c3328314b41e4a6aadb5d6bb6986294d5bca90e6a46a6d9246ba51e462e30eb1db46d0180a3242085c1107a240c93115250a598bd14a4d1157eafc4704f098c4b851e5cdcb4990589b87163da24e6451fa9d4029cc4ba4b270f32addd2536badd5230f780c7391311d6aa14b88afce500b429c46275b128cbce042c4891230af200fd0c83281e2060bac1a1fa6fabe40ce391f6acd39d781c98c9a4d872a8c84931811312f1500ba2176e40356dbd552df4337cb57d9b38ca943677c6ca18a318951b64eb91e47b8c895a1f87116ecfbcc8584b3ac3934a196a8267157489f5558c4168abbe5426badbfb24f24679818f448bd9aceb44860998bf2c2818f2745866d504d5c377c17c5de7b9323d788364c3361f28674406af25643cc040a2d1d13a844f16224c90c0f1bdc1303e0ea994a44081664022e0a921e105a303410b14528d4e21ebcca6b02cd0049483576dce842925663e4c2c3706e0712b0847066c812066fc02a2c613f62b4bea0d831b643480cae221f891a293721d2d19b198f195d8658741971c5c59817b22ad38a8c0b009dff9e04bd900c6744722493485d3c967d895f19484ad4984b8c4d4034ac58bce032923225891557901188a6205d57ee88db25c5af0c284a4490d12811266ac9b25584c30aea448e2e1e57c4d2de7bb305b34f24c9710b90beb7606d09f68c025a54116d80d564c690046001124246902126b8aa9a3608b186e58dc9cdea8b88ab1bb01b545340265f55479820dc20b3c7a1696de69d632cef1c5e0fc9182e737a902dab2f23dac6f0475388ae214a529819fd18a95203aec95c9a8c0b42a34965adb5d6239c4648a9284964430a09b02fc0ababab9f94142a5d6d349a049d48d2f0416b4d6291d05aeb095d6badf590a74950935b171d5f8ab07030da8005d88e2b4d6cd74384240dc6809482441a640d21914989f10ba115b5e6bb404c3bfea16c94e2356e2be3cc5600c7115c22554149d6932d59597a4009d281b62c28403956600dd142aaf205c6abd425590f3ca80ce1b042b2c6c5c477c513338968404042b8e38497a823271b8a20e150e1042b0c88cb8795124fd25a6b2a4e65a9d3b6bad65a670d09eaa25a02c407908e28b01f28a8b01c7999d29054a6e1a8e23c9b3e9a84f838d20445da4f0d6a48b464ea45972c0d9c73f25291731c01202ef03bb3a2b286e5618371abc599d0cad987221f5f825c262773009c0a9fc34d4c455a8aa8485ba1826cc7579c980c10e50552e5ea80b164092e05d7972c1b398678fd483204cafd01838c0a0201929952957d22c953492edf9614711f9c6819425192011652721eb24e68192952e663096f146e0cfbc4211c9ada1bc5bc252ae724b9c2b65b95db96f85b7252c92af88bc4f2030b16962e9eaa4506ccfe11d3e49679ea1c952371426048433d2860e42b4890998c2767409c462b20505341821b4cee4bedfdf6e29c733527ec13d5a448150237e0588890e1e2aa484db735006a05d04510105da634f01e7c8d2cbbd0c8ac90384b2104c611d16d0a2ccee8298ad25097d18dd4de7b93239212f3e4a4d020b7b17443854c708c6c37b9994122317e4d5ede664c41c2e636222908902d54416e1453d90b0724ef1af26e78bd9baa9b2f77849cd49aa8010717ec14a4f22ef4cae984c939e75cd54dbe0d586d890a538a9a3285f4854990880fa7b42525245c1e5a6badb5862aa5ec9992f32267ce0524a22918406ca400ebc0e34993d80a28b032332d38740b48ddc18742b1382d7241293090226c069b93164b34b460c8683d3d80e24d0b5077d07602caf9b9d8bf19397f69ad032467b9f2871073cec925546e46a5dbf0c72b4d4e84a04823e2a1a109181298d1d987282155daae0e0a7a5fe1de572b7ea39421101090882df24b1deb0d1f0d47c305559bc21994d3a4da23d368449036058332234ca6ca62acd8b06409657975195932f364138506649cda1aab9ccc1e175d4f408890646d6100945cd205422530b610c17273286528c21c4090d2d0a86c65192271f242475a1290b2ebc45b227b812aed256613226a5290825f9cd03acc28b205b8620957d50f29122e0e9c6dba691b4099a60d480cd35a82db62535203469a74c5c55397361249ee0d02909a6d6c5ccad1b58f1e5d975d05a5f60e1ee231c1cc99d9a7b5661be3dcb04f8c9323559473cef9da6b1c5843d298a973fb50a821c7988828566647538a0c31811a9ff48d01545993b9a0141b2a57c138e75cce29fb44724fe9fcd6e09c935ce35826d45d50c55e59c1d5508abae00ac293242baa0cba8a5435ddadb4997ee618622b8adc50178da1383518bf24fd6b4a834c2b84aa30a1c5dd0c4a33333333b3b33371c417e79caf01d7826bc29e1964d4c03476041535d818d31b8c9d9c73ceb994d61c65915de41927b7c6d70d1839a7b7c5e0af2c9b7c2849b192624b6898648fc1741091b436d6a405c70e104ca3909c8e0865b8a4b590058439356585a5a022b230e48406dbdc5395131af438b4c197b4d61a051a4a6aadf51b08664075d9a1f5c16ce8c6891145ba700cd980413a60517a954a157221e88262ca1a0f284d563acb0059544a634428b27cdc24ad35b964efbdbdccc019a071ce493327c04d3e41d35bfbd81ac7225ff98814f9f553ad96747cb8896991003e90c5cbd3970624c2989aa00d15e1028503c88bc6d2cdb682ddb24f24bd4c952f9158994ce562dc6dc416a2b5d6426328a9254c6bbd4de87e06fcb1256840b60032b2d2398a9b988f2daa2b222a5d566c3d9921a7e3458caaa6b3b3920d691c8aa0f264d12c2e460f293b9e0851f1d4c012c4a5048750b835f6358373ceb7b24fe49c73cec7b42bde100d63caad840c9dcf469ef834369639da0a286bc839aa83b65b1bcbb036e4a4d9022474c8800b4494539915165059aa3c510ad1b61435e343cd95c1246a25e9a0b2838d089136116c8bc9b02d48d9063562947b97d194596288ecbdf7c6b0f7dedb0d080868c4de7bbf8d619f3824d154b7de9bdc03e1747de5877da2c6d033e035ce39e79b0bed774d4e18259108490d875af00114d8bcf9529065be7d986f717a2b0ed3b5e67b34332ff330796dd50e1564f3335ace727df716f42bdedc8bf14d8a6f32ff5d8e90b22deebb8fc0b6b8f3b58eef5f76d75e7c4dd687ffee2fc6acd93b54803fdf9cb39ccf7d0b3ace7c7f9a3fcd6ed8bcc96a7727fdcddf34d936af61f34d33ebacb3e6ac0ffe9b654c996ff1d92649f345f182699a6b7a7bdf9234dfaa33d9a777f833e78f591b08bfdf7c0af9020512fec1bf83f57536cfc389b98fd9f601f7f130a3dd1dfc1967c0588220b5cd6d5eebe6d7acbbb467f798f5c15ff3ba9df91444f896ec362fe20b36bfb2ff6cabbe04b625717e09664ef3ee510a19a312d8169fe91c64fcfb1f9ce906743caa593184d50218473f5f3180bf158797cd6a38a3f5fb3ceda9c17fb3a27fd77d7ecd2a769bb5fdde8ac31acce221c6d91728649ffe076c4bf6d89674c0b62a0ddbaa3ab6edcdb06d8fc7b62fc3b6df63d81607866d71544ea6eabdf77235aba59fbfc5672d5eeb986d39a0e7eef1a38f75dfb13ee9f337d16b9aa689a62c49aa6a4fbc208a59ed4777f229e40beceba7401c13c0b7cf7edb03c0b76a005e00ec05669ffef603409200505556ccbeb697d55a7cd6392b4190faa3fbfdf647f7f4f7a3bb0be49fd5b2da9db01f7dce9ef57d81bd5eab762dfa7ef410fdac86a2798d020b88437198d5ee8d15a7fe5dbcfa3a4b22ec14ba7885c4ad7f85f50274f1ea48968a8e76dd6314d8c730b26ea18b576148fd0a0caee374f10aacab9fe20454f12a2ca89ffd9ec3ecd4b5fa62f9e78b5de4fc1caab91d7b665b0e6c7b75e904f7d3b407bd62375fbcf7cf21d631ff3e9a6eb8791dd30d2c19cc2bfa679a73fe49571f1dfe1dc629fed4cc3b5e7b6726e0b12deeba9f615bdcd373d8cd3ff3b7d7bc37b33ee9ebfeeadeee0e1558d0ef9b2c36db11f48edb73adb7fb571ffc16f4bbb6b99b3fa60fc83de57c15cdf3acb73f7803b9b777821677acb3cb3fb8a7cf39db9ec3be9f7396739eb23ef7f1f3f4cf8bc67c7b7ffbf94db5ce7cccde359bac8dcc65db07e0bf7faff927d9ddb96f660982d41fdc6ffb83db73d8f9a3eb2eedd1fd657deea3afe33059c8bb9806ff4c9af6e0f73159dda398260b4d7ccdfb332c2f0b4f1488ae77c54199151f6121c6d1afa3e255bc13da93ace3e7690ffaf9f77a7fb7f2c7ebfe0c398b3d66457b8b8328a9aa3af2af7aaf8ef521df7cdd9f183dcbee8e389a2fe66fef6fff5923010854f7b84602102a07b4a3ac8ff9fc53dda37fde5402fc75e92a0153fdf492af63dbb1abac4df9fbf9af4571305f20b34fbfc638e66f914bbf9ffeeedbfbeb9c0307b4a78feeb4e7be4fcab6b79b13a0fd8a5c7acb41e4d2cddfb1edfdf5fded0576fd64fafab26d073a74f375cf53b6e58076f351b61dcbee8ed8f98fe9067e0efb6aae38788471f493ab8edae2e003ceced9b3ec4ed86c768ab9345b6395fae666925c403a189b060fc3c939a7b98043a34a15545b0db9a11e2b19312e78cba2c523aeef3b046f69bd7574ceb9ca4b5487ad40eec30826434044d828a9861a0c9991015d79210900d6b375cd01811e5e7051d2b1446e2dca1a932a38d82b84298dcb2ddc4aad758ede9c735e632b0b915373b4d61a6b8dec58675b6d02044384a436411bcb1cb25faf4984b2b71d4131610c2842e690043da6fdd1e8eb2e6b7e8a2fbffb634af1798cee0e3651076cdbbb976d7fb48e6d498ea61b509e2e1e23fe33fcf3aa7e611cfe3aeae6b8fccd13ec89f9f3cb157f86cc5b772fb3a61f13c3bfd5529dbff9952fa8bfece3df92ad6a9a5f30ff37ed21fbeef96abaa9bf7c415dca3efd3d936d7baa8ad91fccaae33c46473def937cfcedbd240cc963c4d3d674ebfad5a57c414b659ffe5e4f5597ba7e1e237dd4eff3d7a59ce43ad8dbb1f31d077dd44d2dc56394b23c465a4a4b712dd5358fd14e37a03c47ea96da8571b4baa56ef118dd097b66487321b8f130aec2c05167a67b6ffde9ea83f7b9b7dee98955cd5f6360bf67fa699a9efbcfc798436f974e705f97f6ec3efd11eb887dbf1006629dfdf7d374c34d37deebee49990e15a427527cd47d78584dd3c7f8874fdc1dfc1cabe83b459fc7b1908a310de73d0d8a817dcfb02d49c2b0ad4ab2b7f3d0e76d1c01de666d605e7ffa986d2f867ce1636430fe619df4f7e3139b73fd2d06f694f7974579e9d38c3329e6cf59de9b180bf1d29832dd70e2aadd8a8530c0ac3ce89a0163217ca2e327d39edd7d753d1fafdafc11c5429aa729dbeaf4ee58fccb17d2e724dbeb3ee46ff275e8eb5e4c23a0636dcad77f1ffd7c0143f68dcfd9564d592c7477d65eaf3f893ec730e216431f5fecf1c742e89f8f7f5868a71b4ea089818763c08085ee04cd8aa3cc95593b6fc6ca2acc47d3b4cd2a4852679d71ce69dae3e0f3599455a0e986cc4fbc3a78714606e6cf736dd5dea1023ccc407eb659d8db1f8c571fdd5bd0af48de9b6b3e7312e7a2bbc33f3f38cf1eee65153dcfc8c0b0fcdb9cefb33ee4eb32cbf39ff94f613ecf35677c3e0e667ebe88751e6c6cf3ad2eabe8fbc9ac79d63c6b9e9e36ec0fee29eb93ff3eff56c7fe8822e73cb3692fff59c3336b5393c1e6e62164b6cdfccfcfc0bb93ffcc39b7115474fe0ef0f999f5c9af3b39848fb9791e4dfefdfc8cc0729e9eeca3e7ebfcfc7c08ec0d7bb2366c9a662cdd9a8bf29aa1666500b93a207f2b561565a083fc0fd6f47bebeeefbad177b03ac89f7dfa695852ed31ac4a96afaa97647dca3f3f1791e425337beabefdd1e9eb589f53774f129fea7396b5313f5fc8907dfadbcf4577e7fcfd774d2f99fe6e15310ec9595d06febd1eff56edfcfcfde4e7a2f3756c2eba13f6a78f87e9e7a2f4c774439a558c6b068ca3df5c19505555d4a1e63a30c35b3ccbd13b9d599665a9ff07fbec922c4b8c35c9fa5c71ebd7e69fdfa29fa66c9bd3f59f24fb83bbcf6d5196fc936575f9e5df11e38cdd7c0a77a734734ab6bd6a3ac10ee3dfe5532877851cac536eb8eb986e284554ed1d2ac01b3f05bc5bb1aa974fae3ee756f72e9f7c0a77673f89a1b7325f6295f521ff547fe32f778ccac6b02dcee9e5b732ecf9e49fe43ecbdf64c9dac4bc7e73effde553b83bf74b36a7b732afbfcde9e6a3bab4079f9f9a5f41b7322cc668dab3d309d0bebfc2ddd9d84c3798e2598c8e9aff52e841bf7cbd9a7bd5b7c2ddd1ed569bbeffbe4ec7da90afdfac7077f6ebaf49d78d9e15de34bf8299338e9bad702768f5ee54ecee7894d7ae10351f7d11eba0f7d73550f669b59ba63d356ffe8875b2d8876b68ba21920fa8b40c590b62ce18c51100000001b31700002800088683b234d10335f614000a45ba90a498501a8b47426138180682c100502000410000401080611086623196ee706b24bb86af8cb951f749e57dbd23f830dc0547252728c144e4aff9753cbeb4de4f851d4f0661944eba0797013593b8d0641e5cfef5f89cbda4b1d905624a450bb6e927a63fad4b85ad3dcc4544420e2864eea051610dc2d551afb24cb17e03b471e0f2662ad6c2c26d41e922297d75f1456e6963d95e63572a676ad6f8f60c8d9dbb205ba78fd6cf2e8a6c2737141d1c80b00c9d77eb1a94988cb8926dc1555ce6304ebe95069bb3431c29e66b6f7591b9948ecc42599464e86bc15de05b9109530399d9d9e1dad18c24b3261ed66133f9e78accec9d025c3788cb18b357c8f5b9f8792a1e2196bbfa8afa4eb2667ee4b050b01530194590eb029296fd6f553d09aa49e1e54f525e2d05c7f79ad572edc19a7c89701756138ce2e021da0c2f2ee11432e0356edb228dd6ae3fe2b322977529723b786be1fd4542129eb4cef15cc3d5b175a643a5c80a2611fb2cb3dfd6cde077c4b8317836e520b330c84e606fc1d354e21e90abbe792bd43ea6a3eebbfe50221cd287ebb8461ea524a5755a0920e9a2a0c5997a4b3f301af1410aba3a6987972599fe894ad74872bd5cff0c7997027a184b3ecd0f5ced8ae403823d001975000b7c84e4224c38bf5839095941d5ab20c146fe1067fcac790035bd52c8738d6bfe3ac7cacac2a624fde8ce203756c66b0badeca1f04a04f40ed619cb8b4920fd16447d203e7982bc0ce05c881063e6eb78bd88e05793e5f74835726607bf24264617fc0e46f3770c2f8215370e9eea7a0038fe09a21c7d2f362f24142b0ebea63a8ecf686e9ccadb95e7a7e4ec6f54a3b40e2851dd1af908e0664d5e69bc1e3dd0c3e83f29cba773a13c4c24a3804d2af3364af28bfaf17b30c8daade85fd6644b20587500abf369d7cd4c6b87d4592225c411f5f9ca14a72304b53a5758e9c07449319db1af7c0b5a9065df98b42acb2a0f7a3a3db607fddd1bf4d6f343c6d46be22112a9feb21588858a6f492c46a98c315b6a4b75bef4329955a22c9ce4da732e6cc4e8df314b9f8c919a96228c988ce5f4eb8d7832c4dbefffc0e9e7e7a4a0528b825da28a7cfc6427bbf5b9136e0c49520da85996387fdc6c0e1969ac24ff12e0af8863206a47b665cf7f88ac15e8827ad3104ed7d58fd660d864770e72c281c7aac92ce4881cc0276989a66bcbe9a499c081ffa81e7aa809a4940622e132dc346a86ffbbff6b1c08908527dec3ecd8a03256c277e2c4e14e4c586d484419d45788d48470acbd97a346d1877912bf1ca06e2d3c7f11548212a5232b45f46eee012219e04c2256b98f54db11aae2783d40246f86a8866a7105c5787d7e789f3a32c79463a21c7e96138a2b456d4736bd84d09c1675ce6b5e46b8633efe26a73c2d7a2d8a6ecd7fcb11869af7e948da6a3c7eae4247d3430dd5c961a2abfda960f43792da51f71c720ceade781ef5cd3281eb4cacbb14530989f3b5efa227e636a6175b59da01c7a8f399caee74b87fa8e485fbd3c6dc2ed3417745a588b7c3c8c86e0d1d9a21a83816d8608b0a2bb82c0a583d462185f32a40ca9659ac42a860a2b70cb44fba35c09a38b3ad8e6cf4ecfc041876a1429bbcc5e510c3060476150aafbafd01e4c32956896bf2b30a21228c7bc8bc74124739dc59860e06e96aeb3b2b89993cbf213f123a33eb486b073087683758b01b17f209610703fd70b8330939ee00c9157929c63d60944d14596ee0d7d9acf28a5699c7d8605fded0898c236213c19cc656fbc9d46ca2074f55dfeaf83f74e5162a69d5f54e639574a89100b3782850dbc47ad8e98d6f28d2814900a74680358b763b8a7f6bf8400eca16769ee5da0d0cbc6d2edbcd6cf13edc07df0c6e90606f21c609f034cbdf022ad220fa1518a87b48c16659edb8c68b19470c00cfae6c075275059944211fbbbd7bed3ea5ad8795d178263748ecf521723780dccfe47a3f3532b6eeb8c2fdd4a8413ff868a41418fc8f998a4a886172be22896040875832ccf48b2a0ce46a95cdbb921f9d20c1caab5eca025510e60d5afc3619c445db4eca630c8a1d737bdafaed6c0544a13ee2b1ef6f9082b7ab30be0d0e6edc0e5c66c7ce27ed7cdc894ca1a4a0f73e79005ee09545ab976673d369bc4a7b817da3901929c01a2f3676d33d3558a59da5467121ffc89dfa53e20c7458be408ff96923baffa33641b84f0555e484f0420db5a20b7be5e6a928a0d3e68998e9230f96a9abdd800bc19f6ad7765bfe9558196ce5e103c3081a0f856539561e7b51a4254e3030686c5d3c5492c04cd0e0c007fd5191a8b12dd8bee11bf838aaccd54637821741eaf090cc39b14acc52ca6c46d1451d17c1139d1ea665c254572e4e33ce513bd9e389d072a7f9bb0ee4966a7292c4813fe67b57fa8b99fc49681a36b0001703003c3f3405de0e2c776da18b033484ae5d10ac8d9125082e40a1073bbf0c28bf7e4a146979a913f54e53e6e2de007e57a516bf264ccdc5106dd488bb20be7e54a6ba47c898858f26d347fa98a1d581955c5d8cb518adeb093d24167915624649d41c6b25e3bcced7e62ec93ca7340366731beba6e9a3bcf59dddf09cbb2cfeaa29850253b8a7a57953ba7e536664a67c63726f78fff27c7a69c10b0584799b96eb1f91fa5c811090be48c1ef4ab231e60805b882affaa2532824075ccecec9d1602ff4c8a5cf819a47440f6b575b2aa7244bc1d4fcc74d7e14ebc26332ef9ca035feda29d5903f1ebd2dbc233d29a97c86beceea16cb26c5253bd9486c79926d570291e73ab7e64f22ad594239751f4f18f32818612681399ed0a147b4c393db0de22d912cbd9ba4600fadd5c4f3761b46115358f5289549b76611cd40b9227bf236cd7b2b5f909dd5df7fbed39830ea4087ecfb6570f9d76aba35f6479cc3cc060924d857d67090c5a1d1d60ecae6b2643766dd6cc2bcb96a8e5985c8149edff7114f0c4dd02027ccb6f2eec561124abede3ce73b9c44c7d74a00dd59e1606ade37b7d1ebd976da49eb022a9cd8028712a2d20ae14fef6744f47f457dd2de16b4d249960a8a6ca914646de9d41f1e9e651e0a766727c6dbad1c7dfd8f8865a3b539f14c857de4f12309606f73e05c333eebc75debf17b23fa3fdbb4e8f733bf3ac2ac17925c95ac7d7a4c18024e8f8dbbe23cb78a9e3d30dee3eac87ef915ff1ce40040e2bf218cd009c204a2ebc9963066b40034c43fb457a81cbd467472e502a71777890b619f6ccf189952ae552f859083e6214e6a69c891fa9ffce49d6fd9b8771d186e70a2536433ee9758fc6fd5194b8eda1f7d650e36cfb823bcfa7a0653c5799e55da2224ae1fd97050bfeceba57748e9e04c496b9f09854cd1e8c8fdf2ea306c2446648a2a8f9922ce97a3b570eac6b0078f1ea9163c01bedd094b041a2de827a54d8271ba6827597983bd9be5f2d7bbe8604ccc5f716fb12910ca2ff4bea038c6b2be5637543c3e91e7c854620d4d970bbc4e473fcd91bc547535fe54526a4783c00243add16b1e990a2732d1e63aaa7a08b9609868f9c6aa5fa2a32c46b3e67fa27601e226a2fb9e915321423ea267773c74b2e9ae9b35bae0d75db2f9d5f2e9644b88aef0b4808dcb2a7e5edf1d6d3bbdbbca613126dbf6ea46fd44c63b0fc093ce1897049d3c379161a7a14fa63d91c0be9b5ed818844c8ed9dc81c4b49a6aef670601ec19e2411278e98270d19cb9de188132cd4cd7e9ee6c4e44ed693417ad89c56785e6eeef745497c638bbf91b470b4aefa750263d1141d87aa9f135e1aecc542a712a1f3ce9bff76f0ccb684b78889dc3fa7c46e77369ab97363f03c713b12b95cc3955c50640a47d4e67c07f429f66573fc03ec58cee88eeab2d0451121e37a656905761f40c9d4886fc1348d438667a6532ec2584c9953f3028e406444174c45c1003e1ad82345e2b560b09826cdd19fe7ba56bb8b6c2e2901eb7646cd051ad7c7ea3fcddec2db90128194e204ea2423613c9699c949eccddaf77bb2d4d0660c2c37d1d79e8927c46fb2f2b0a7399a83dd224d9b369a09d866c27d17ddad392cee25aab8389a4f1667477fdc382e95195dcd77268f5461e7839a0cd052018aed1d5d394b524a88d252767129ec6b553232d96fd88cd123b5bfabceb7b930abd63a6d14bda2ebafbda6f6814e7a6cc182c7e37396b5eb374f97c208905ec936bb89195814f9346e1b9bb56b1dd69093b68639db412ad17520b259dcd8c1a5323e97842cd68d271bfb18e46c9ebd88459d7c83a951dbcd962b46c7060da7d894e87ca215a37a57551c082c005b7e0e01b670b3eb9c2f5785a625def435a8c25cff04ae4e86a17dd4a77979abfa80f19314065a545c405db15cfe0c90fd5a840f11c2266a0b4c7cc6ebb67475fa55eb6110fb5222539a05b5f79c60cc5a94d7f2e686ab53d73fbc8314bda99afef07cb2ac97c53290d4562bb45cb6e4736f1198d5a8e8f94a0d325a3c589cd54a4c715c2a6276c730a135005faa99f130e6aff54c022d01b2db940e0ef6e21df2c48e0793c5e74a6b63f2e83b1732766aae9db2f0a7b8aedd9d9a8916113673d71ae92a5713dfad49a97c34510191af44361c8d528185f67893ac8d55f9c436efdf2453c47485941d7b5666ded89fa1cc0f9225cab8659d4df833c087e807f17fa1219c1ccaeb8716e9612446a0fa356df19c88843d5d5acc1e7f6abe4568e4cdbaa73c8055e1dbad21f9168b5f35b3577adb3307fbbda4de561c9365252d883baefd6c668271ac024a5a9de2f5bb90a791cd0069582998c8717ceb40a6c391f5a9b7a71aec4dfde39f103ff5d686404a2bf7905f9d4ae820893df0ceb554dab736e3d3d773bcb3bd427d328c38d37d0e770b72afae10209a27367ea7aef37400161e90ccca759b67f573c8b284049391ee4d4d8d1a0640d6a7e966464d4d33a51996c61c3e74d2acda62a9f5dfda4d13982ee48294030fcf2de9563753ffa7dcd80faf464779a99ceb4d0d1d777eb29f0dac749aee6d7b19c2fcbdeabc84e91184505746d18d03cad9b6160e867cc99abcc2c57f93f1b9b1e95692a45a3fc75a7cf580addac1882cf131ab10ee379b7c0e22234ba745a9406bfaae0d1a1e4ee4ce28c072ff25c5a6ed3d87e21157307c82f2ff48361c54a8fe9f9909d50aae91dbacf152baac26314fa0f64e180b880e2b6285b717552cf22b1f56533c1048a6b046fe230d80786e68a70022d0b6427399d9c6c7a3259022aea4a4f6fdc7de3b34afc19d85b98f00407ee57618c6db2a7ba9a00ad52a7d60c171018f7168c47cbfa5e511afc895456f806792aa69fb5d7f857a2ef80283d37e88288184b96b2ae00d3ed4704835c2d0e992b6a24c0c279284f9d480c3f8085e1eddae8f066e414c8df3ddec5ae795972ee11a473a45a1679d5273dd93d20553d06786e2325c25cfd6fd4ab373d448123cdb1521e9c26f3aab260192d9ee1c93c8e021fce4abef1ed762ccee92e71c5e92d77960fb856c479c32c1b0571d9c0560ad0e2324a444bef6eafe3713c0b852ad0ff5bb60dda7de28a6b5f48a5e80bcb3b2a45e9f596084971aaa86ccead4151eb4b7d8cd7005942dd2a4555ad0f17e77b49d0c27403515223918de91a4a268d0707ddea0432eed2e2a9c78d6d6c378dfa9442d904f14d6fb811db28b57da5c09a169a9f7a2316cdded16c2bf674165f021bef0766e5110d6f38315a4254e7ca3026d6865a270f91575107aa606d20805478d2e5dc741f96890d181ed8459210b262eccebf6bb0f2b9b4cc6224637d89ac84157ef0ad74affda6c47ee731655328f3618955a18f1940afac49dcb94d9b746a0c5ad4f86d142dfa9b5906cb21daad96113f3873dd06825443d1624d098e305d687c48bfb6e4e8eda47555a00921dc0218d77b3ac8533afba1123358535697965b6d20eb55665ae29f47b6fbb93893d1e74d3686ee3f0a2de05d11ff27497e7c8c54c8236961d774328ba8ebd09a7c3862ce5495ac1cbb5dbc529481845609eca2cc9784e3da52117d9b6946c925d068fbc92f68ae9de5c3e810d7be50e14d3bfa902b2d771524f44b108c2884602a925983a8791fb28a1077e2cd2626c6d805b53560a26d4d8d28e9f03326fe204c45129ea7bee6bdcaa15717acc744f530f2a0ed3a2248e72d38e4575cb4f8ec33f3467ee6c15104febe44472f940bbf7a3038217c0f35abc0c6fe4d0e64ddc577e4e35887ab8cd97d3a1410c88791da5b8afe0a3bd8570e2b5264401d0e484480ebf15af1d30c5af309bac83f3634a42665f219f543f95225e825d70d7b1fb37a545630a083feeab24b2fcd416516d33888e401c18eb2e058c8e8586ec7450a6fdfcfda03df3046f50ee8874065d32bee7ab04ef527494d46c6da403183f9db292df2e97d95075027eadfd52ea5ce8dfc8e82876097d83434d8de267c5dda69aab532d545a2122bd145eba3e5bb9b331b6c67a1c1975b1bf8682e5dacc282510116a32dd9e226ff23600f34910c7c174c90f591acc9d169c96c4da3f3b7591f2b40c584c7dc429d33455eb34e1d38139335ad408ff8c505767762488b27af431daaf44a08cbfad30820c3c767e402bc4a60be177b0529683a5cc8db34640f6694e8ab01be1d17289544eaf6b7492b2f71454bc38693109b80e8c25fff3fe17a2c76edb557401cc00fcb6c12ddb17504f94d519020b9234c9e02678766c9ef0a25c8151583950a076a957957f66e374349409b49516352a0eaa47fba79c44a2d515922db3dcfcaff86cfa6736f8f0ec2326eb5a0a8bb6dd1796b03ac074a8ed1eb6e1719344e85e8b6c1688532752eb4e5340cb6e1d62002573e4146c3517438ac79155ed6024642f66d47913df842e2765b24b81b5fbf77fd017427d8178408686c34836b93371fbf18a2ac6fad7821b8b1f9dcee8afd090c67274400fd8b482dac61ca6b65e5e76904c9812f3059f9f790bf278e359f2f41338cba4e829617c79d09c862e4c3d720b69b85e50416fd16b377044b8be08bc69766e4b15a60096cca5c88cf046055c84003db853eed7e153c59f5dbcc26e7aaa9b02b6e40475e0c5c05dbe84d025d2c92ed2f0b2b2606565dec9493adea72fbd3eb0d753921e9df8274532cef9307b37b3d51f97128e8440095986a378f3057c7594241fa44183962dbb648bef6d3810470aa897ac3bad3ebed8fb63914550be05b11e9611a8fb3950c8211d3937e96c1432817d802172cf0ca268df4beeaf1b8ecf8d1e906dc41654d8a3e616136079bfa7721d74b81e8fcaba8917716a1956b8239aed39966b668ed378a88014d95e02a268a503b25cb1ea845f159dbddb1e7266ee2158ed48ae019932c83ad258194d8ec953ea4c50d834ad10329cced6789b53fbcc5bd65f29f892c745558e08dcd47a98cb3050aa28c02ff04039904940bf1b032aa76282a571a5d104172d0de2c69b87bd2ee7ce5635dfa892c5c15090f177c8c7138a839d4b5c22c6a3045e06630f6695507904a2f4f58261c0633218b50d409bfa92d85ac726c99311f83e83f7164f238f1e547ac401bd03d39fc214430ba89ae8aff05326e8e4efae50e05dffa8f8229ca26048b83a2d5621c6d80c86722dd4cfea591e2ac1be8f921b4dd0ae8a2d28024cec64a7c38a9a2de045c54226381717eb905b1a42d38ac4c9fd23e2aaa56731f331937c74113a67d367bb37a5dfb95a15f6152b6f81d3e59a19d8de4a1323d4ea21624c1b76c63b5cb3da7b9d1b612135fba3a63cf980d12315639e98508800319b852f805322237a352a00f2ee9cc830bbe01566de5b1bc9fa257bfe4ea993a0066db65c138d3d2d809fe8f68d861be9d5a94751db4799f832be1e58d6c9d64c985fe5776923810adeabb3ec1c005fccc9e64db891a58505cc72924033b92281a41b1b856496e83f7d0fa2d671d36412fb4c8d1d5ac84d27d09b7a477da7f86f6d1085f2604d5a2686adf949697d04f0a3312cbd041438d2b4c58721a3f800bb9261143bdd7807bad5b5387e69da934a4ea8174f98b27305060200b7a669fec51b91639cf2ea55fd216c4666e5c42f6da13dcf01625064b152b2b769eeb48c238fcfa939d2fb9741abd0d88b3db52051ae345e0f814c4b9c8ef3bf9b7429ab6078895d4f2634fdddd4cf7450501cb5689fcd1e266fe5130314b2b652397bbc10d6d9aefd8aeac87ccdb7ebc7eaa1da49356ecf2f4633bc27090bbd76a0838c5bbaf1b4ccec73ae64500000215c2046337250981cc3a3eb91df95a49ef4680cbffc2bb5cab591e6221882a9cca21456bccf97ac3090917c9d7a7902ad12a9ea4e37cf17297c66803e770e5ce82472413226481d0408e8cdf79682cb9a03f2da36f5ae80d17548ea01ca922ae461546c6131689b4f6595ea0493baac51104a9e2b4a52aafd0203e18381d2aa804b99bb45484637f507fb475348c23cca44ad5e5404986aeb4e6dbcdace85bc36112d5c80a2a6180db4ab809b60e47a0a2d47baac2422e156d67293d276eccba21e09fa3f94cac1ae41c5502a18db51e8c5f8ba76b62b799e6d0a2aa406d92b2147acb1cdb366d9425ca3b08384d56147c09d3793677cb0454776ae3e74d85a65eceb4ee4597351e7a694829826daa8fd4cc11cdb3e23d3850f07314637c8c3cec0a66e6ca88e48723d0703e93de7cb3584e97918e23ae2adcaf03dcd26a60ddaa930cba20c96d8dfde951686774ab9145218fcc7376f4d11790468eb0f51b318dd2b5a21219bbb9d6d18cf031886c54fb0bfb63f2274314fb067fa4447a11920c6b9412833071ebe6376e96a7d5fd769df53421702f19e178abe2f9af5b3542c5cfe3d140116010d26844a668b0b9526b124421bc5a4e0ba88d2a5f84aeebcfdd199f7611d253d56e5e5a26c6ceddfac5d0c5d8119999f23307028248c2058389c8c7f36582d5ff4fd07c6e6acc39cd10053dd0c05b250147b648c64f5adb8de375268effd72e756bb744bb2ac6bca5ea496e0dd804289f895776d135cb862ea6b116b8e846e890869bf81793428627d80d36214c324afda99317d43f2b75c9a11cfd85e408d8cabae6a94b74cdf8194aab94a35e79e1f029400a231dfe9c6e267a7ee61ac7c5e14c3a213d2251c42412f1d1261743454a64ce510c8ff45f5d1e5d2e2459631bfbfa60224ba9ce3c2e260be16e7b0f6e802e7a5314f1f1e6b11da08525b05fdd5326cbf2f2d7660d39deef7e5e1638cd72ff548522c56c20d3328259d7599f95de98d0e99ae0ef10693a5884bbfd9d8ddeb1c09755b5864412fbc83344ac4402aeaddc8674bbfa6113a41232805fe92ee1ee5559758d6e0f49dcc04c0679006a5cf709238fd203cea8651756a122e07e005cfe26a2f1431ec0405480080928028f546f48322054a851ce42a4eaf718c80c53dfcc5025745b9ecdd14783b44b5e1697c94b18e79527ec856023e0ab6c858b4ce8127c3e88c6839af407ea4ab51d808a558b8bfb16c02c4e051f3f255e200c177c33514cff357d9146f289eca57de0b977fd115210e05a13ad48f28cb2721704b505167035e672426fa0c003b64e879a2d52685cbd95f38ecf81c53ce560155415d8caf918df78c0e1b8f9488a3ed6344edd1380ca27a278232116d213d10c75bb56df460b6461800acc14a800a3c78d64f4245b7ad9dd03c8666b0ce77d6fe3cce961d5949fb962a2dfbfcf3f8232c8f14700c53e82f6cd2272570f0f6728180cc53fcb21b3baa3f993a8628e26ab20580762f96a38c31b1cb9ae863c9d0fb0df5d876e3a3a719babb37cf731f63bfcb01653e418c7120e9e917cbe6e7f899987af5b568459299e44b312f948c2262f3c66b06be2e8f936ac63bc179d794658c7c24a2e05f71b71c049423947244008ffba41dec5960901f2430bc4554baa6225dcc5de100beda54ddc09848d50bd3b1904bda579e5a301dbee8107c98c655945dd7d126aa09576ea6dc38922988838faa9911197018726195142128201ca0d21af23cc7b11dbe5904b00dba4b5ebf32ce250bb5b10aded79501d497cd6112d57e5b98ba8941c839c74b5309414450c9062b5053f283a78348a7b9701ea1dce0971ed0a56ac9d0c95eb1e85a0344681fa4a54a33c49849d4b5c48720387d27526f11449da415c90d15c6041bfda48fa5c998667077ef05c353a8d108ed6c3505fcc99d36046246d594d60162fafb8230e72e7ebf3cf2545df23b348a42a738af67cca3bfa3059ba960eb1619b688136af979fc3df7c8804d46ea8287618287ae5b3c4564e0ba6140ea8d1090f7848e3cd801ec9d42131a1a884277b66909b0862300b9db1f2f7c593522a401b5908290d66baaf9bbcb305e245dbc33bf1eac0fa4735181f919630742c68787d9986322a5e026d93d21309055b17ad2214a5004991d62cdaa4eb5209d0f41b5cc99af318633ecc92d54cfd577499f6c4c1aa557e3662964def6969d2224014046d4343d33723d6b2fc4e262b99fbaa5beb0e62132f842c9cd8783a871173a842ba23959f9d5906c64a56ea0a05945a38ede7bcb492118ceda2e574a7b5c8aca9e5f04df010c5caeef6e0a37462d716025adb71f7194e107d0948681cdd972af1b6668de39959a39bc80967804e314bb8f6792c62d3d272d0cad9037753329a6356068f8ec64cc19bd14aa9991f6d914fe0ee53eea9521d373f2a2195139af6b283073329d235301e8641349b5c789e1477a0c72690d3c48e9c80d0516fdaa9a3b792146fb78ab48be2326684c9d98ec890b8e50776711a9de88a13d57bf3bad540cd2bf86220d7645ab9118dc1ab44c39838bf86647418746d9ccce36b622204e50eaa76a82edf346951503cd5d5e8b3c66614bc6f62ff3df8862215e9e2c264d1ed7bd160c55190b9b3332053241ea8a3dc0d38cee41ffbd39c6517a9a2391767eee87b34dfa6223a6662451ab28ee7c32d6926328adf37e3697f41ce28e9b315fcd10847f6571405be820133698da0436572c40dbba0c25060f50da124d38209d9e00a8b3fa0a7cab9a58ef546936e0861fe49bc411ff0fcbc9885c15ba7448ac23f2980eb808ed78b1bff52a289c1ab93d9fb799430da8f94020888fed6f334f715ac8f0ce47427126fc20aea4db35e392c319707d19a5164cdbded34ab91a4a522dd0d8ae1baed6deee3d130605b6ba2bb73bf3bc0adc72daf63b7444ca5af52ab84372b5578581d116fd021efbb2bfe54c37bf1be2c764b1d2ae9820655ba84b405d93496b8cec558120b8c6170013ca81fe1021f777cad2ee99743e579ce8773d54738a57dc40e5ae1fd4a4e9077c013df0c4578c551505255798725cd66e32d16aee8c543ce750d6cc8f947927b177b1ff955e910e512a14a81611c83e8e0969fd74726914f632ea655f962c6294ad4259a50519fb1c558adb4bb5b9295864924ce76b9b13a24442d514d0e7622e88b18c94150b354167fa30095a562df5aa9a63baaf3c90972738c11b58d14c7c30dc1b3e9d86bfb7e8dc71ced9d17ddddfdd5dec4c41576c93e7eb43780c50acd5fca9758da7bd648c6eb3a899112939584ee8d0e600dbf0b5e4bc659f6c0ba42d14c9d5341582d112e3e825d929abfcb9050bbe7e2415d4ecd43b24e8a478a76d09062414d29898f2411e7d79812e4498acc76691ea62055d57ececbbb8a1078615b0c111b8d161ca558ce93c9a60bcccb8b8a0608168958f0a204653623e6d81baf923e5e0cf72770d9442e3f9552b3036a103f157003e69faeacd747f260bd24e2174a5b432be054803d3e0c7d0c07034a6c549d9362b6e4ff5dc00c5e810d40af7c01de7c1c47ed9da20405da64312002caa3799a1feabf18933879e0c394f5cf3c3af956b077b6c8b52fdf526478df2d35e802c0ea5068c1b3115657628c6b04cb1a3e46fe036ce0a7ff55c4b962fbd007a9a87578b7ea6ff9eca39149bb19c93a064e38bc7832c93e198282d2ecfcd649e434feee4cf18d15f4407ff1a1694984c1d16964168a5b548a5c9d789b93d61e5074d013420908e20ca0453a219834806aac6f650caae38f59249567f9343c2a5589dd8886decf49d7e0202887b27ba77481aeb06bc5389f8f603f7fb36690309845303d719666e12ef56d99b3343871d46a1e3bfbf735cb3896b09ffbaea233df64067df709d355dc665027a8bc7e70d820543a1b2996c61d24c0be2cc72031351fc80e6f36691c0cd93571feb4afae752ca80d28a90cb30c6430fe0b67123a91545e48f11e52969c774794bda8981f1e70a5674643e9784561891023dbf66e0cdd4234a343c7bd7052ee8c345568a483697b9e24a94f4af85d8007dba5238c704461ef9212906d2da2d3a8fb782a7a5b6cc5f95a8e789493d0a7802db34a8f64d67ebad1b9a8091bcb9440b8ea7e7dc8726e2c59104dcae96cd7431af28c90219b510c4b6a5319aba2252cc3e535c300a782575231cbdc30a1dfde6644e2fc02a4d1721ca8af22f5e5fad04d3247a1dc69ad702a57d8a1dfb04f16eb1045d610177baae8f041eb6e72ee24a3dcd4c35b1a7bd11a36beee76a27f8bafabeb0bd8c0fe45278b596fb06fe7ba6b2d929ea40812522d5a1245820dc247adde667f60bc37c91abfeae88dc5e5e1f8cc6786a2c57fec4570326d8c3be78d57162db8471076bea45b2882fc653d6df72aa658497535ab5f94df0794b2ee5c5a4db87acb2b4457e768ad80e316f4994b313e421c5682a110826e9c9a22ebdf46977dba86c1b85aec2a150fc99423e8f79caf30bd68b02c18d61d3eb5870ca47b0be348f4fd1964e503b7037031f179aa5fb9a9b8fc6803fcaf20afa9fab8522ace75e2b5952a74fe9c7d6ca5e9d54b293291441fcf4879e86951906d6a18ba68ccd4538bb4aa2c11d5fea27e32bc6006330e5c408a6ff6ee53dee40c76c88b500df529012c326763088af42f4f5ac57398b3915082cf34c4d0284993cea49495c7ad07728108d9c70aaf52d62a11a699a2ec38652213e2c2b3fd4d8dc4a2cf536adffa9cfd10e69ec6ffd369d1da17460b45d0a61c43f9bdacc8b161beb4c09b5b032d2bf68394946850edb19444b3d6b8ef132983810e8053cb967adc0b285b8a95adb610c8fde24e47d67c909e80085bac1000611bae24e7fe67b3e8f524a8e53a2117ac5642a066c1250b6af190d2a0e12889dd6f364afc49247b02fb18da54050ee450cb40207dd8c6cb11980fe82583820877a2800cf70b36928267f8b1124055ac3c00c994f3af9e163922780c60a584ee948dcd25c0a46948a38b780c31f03aa56333ed2465bc498ffe3ff534af74f8057ebe59c3b85cfce68b94ca21af9d51616144655f247d181a94720003145ab6cebd891a0592d4e7895884d18959c3c1f833024bbcd6189e29b1857b0b59b1072efa08548ba671923f6f6b235656f6f739abcd7795a77eb258943dd2c2379991285f1818c46517a816a9eb10717eb5484afada0711bfc02c7229f6eec6d8387dcbbc2f6d29b32d56255c71a38c4d54d1b409a3903991228fdd7815d917c9726060eb066f46e25e3af89ea9215cb1778bdfb0ec716ee0c9818fa884cbba2d8c4c7f5dc7171212b3ee8da1161c91080c014e67bf417fbb379fe4fb9ad2458c2e6533054c48d8eac89484187100289f402f93513a18c3167b310963aacedb83efc0dde0700b4a36f6815e23c05a32c42d047a8f33ab54305e19a503f808a2d062e0f54f717eb72298b102d7294de80620ba28c3fe4e40834b918e06329687682eefeea600c3f0a3ba04b4864b6712db3b9a525a9c9e357231f83294b5cf66c9267b4bb9b794522699920c6a0a750ab40a34b08dc91447ec1449537fb3f1e54529a53897fa4751caec7f5d1c9fb33ba3eda354bc6e477f6558f95786ed183118067d2abaaeeba22fca70ac6fd72512d1abfa7096e56d1acb2e111687dffa17fd5d79ac5e19968eaa5bf9171b6157fdd196a55e10e4dc150cab5744dfdfca62300c5a1bd74bd5180c4356128cc130487de25763300caf14636885d9edd039e75c0e900863ec980324c648a974532006a2e9508c910a758cb15482d547d1e59a7a17f34acbe99c3fb169cda773524ae7e8e7b77a31966ed28b26cb8b9e246ad1f7b3ac88eabd6a8f92a51e76adb2b6aca286d020121535f5d3ed0ebea6280a3a98b5acd98ed175ebd6addb2184cecdb0b75d5314d6cbf22f9df547bb84240a95304511e7a673530cd982d016a0d818b631d9420f4541ca51f0ddf970967ad68d1c2b7ffac7ec62360748142a3e45b9f70ce7391bf963f4f83ea9eac35deadb378aa228f9d4d329b29eb6850327562227faeb125dd785bd88e53b5ed48ebef5cab3ac8c30516d995567ad530a898e5a66d163db275076b11c206d46f169c5049a13287eb5e364fafa2ba9ef1d57c33a4ea65ffe55772ea5b48a7a6c3febb89a956f1ddbf2316ab1d1e8a299ac559c3ad47c5aa959e9502a654a9952a6941eaca34ef44b85ec750c7f3253dfa46ceee553207ed99ca5e437dbb24ba958d72c948f0afd68eb54c83a29a50baba6799c4c7c497fbefbe671f1256d1e1bbfdba7fbf6dff6b151f471f454bd18f6d48f9aab597959472f7fa55e49fd28938f65b24219aa56591bfdfe2b13401198557bb42c6cec21b3de1e258753557feaf610ebfebd7b1ac440c8f83042af34886138e7637584620b3b520cd929aacd65484185f5c1362652f848d1630d601b132856d8d9a6b70a7380789b69efcec20cc70710ce6efb81f5f2fbd6fe72ad5786ed87d60dbc81df2c38eb75011481c1faa32dbc3938ae5255fef5ef8fc140c4fa9ed6cbef6a0c86d193bd499326cee33b4e6408e8876358a924a51fd1bee347384f13df7122ed45fc081edbfc5c1b087a12bee344bc88f3f811cef3c3815880fc08df7122cee33ced1aa8891fe1473811df8912c88fe81fb27e38901fe14730901fe147fc7020dff1239cc78fb042e4da8a6ea251f1a10d8a7d41b40c7313b6aab79d0bb34f2fe926aca87e4a33662e6f399703a4629f56527593e85a0be7985bc416a26872d9ed262c3b572f73c5b95ef45595030476131ce7b4aad84749ec9f8ab6e825b4bb09cb4fff555dfa2b4b5fd116c7dfd56555bd8cc47235b9786bc5399fa6a9bebf957413b79bb0d5bb4491cb5fd5dbb89a215ebef58be961967ce5fefe700ad355d48e7fb80543695be192f512be3206f49256e2f2b14546df6570be0bb5ef5fb24b5dd8c7ea255c2ff958bc7a49576e457f7f5c0f2b4ef55144e95531ad90f41246420d1144d5a0d8c8993b6524963e6cad7b492ff9e1487a897713360be825544a5c8ac215d1fd0a913fc13f16b6582377f77ae3cacb7a7fd418e57bd3441ecee489a6e19b98b975c373f69bb2d1c7ac9f662ff25bb8d00126fe0edfd60d51bd98855c6fc9eb7d1bebf5c142f71c20a2e82ff227913e11d6493f73acc8bdff3817912a8e7b6a8ad7ffba8907cfae3f61e30e15a6de9f830ef596da30e3df9abbd7dbe307917b7417551ce79e5dcc7ae700218204d6dbfaf527f6f6ff681c7f226c641efca2eb4da43f7fd7eb4fd8fe53d66264e5b37cf7d2559c6f5370114fe6d0a40dedc7c2bfed0c56ceb062a8997132f06fabc2ca6f8d86e9f6f841ca17790b170ea03f9c09e6fbe5f3cedce1a90bcbbe94c33f7e8ccfa369e4f3fb0f03115ff4364e747ff4f527acfc17918523befc254b9fd5eb03f65f7980d4efa043751da4f74ae63ffc71441edfeb05418e33895f8a8fc5bfe253b1ea507fce2b5e3286c99808f3f4093e0d16bda3ffd848bf337f1b67ca46ef2263f916d9ca9332d2bf60d9955199571e7d9a812f457f4bfdd7077b676cfbf632662dcf160e37fae66a8668b93efc9744f596a04b669a4fbfd9dbe387cb2dcc14ff6d61c2404491429815a52bee67612f1761b9c61fac553dbfa8562f5fc42211bb882a7fcb8b6a4b35cd8f4ffa211cf4c187ff5209ca8f3f58d1df1e3f58ea3192e859b2951a7f84435da2e71d57c34da6d1b476ba2725643e2d8c8c7a2ca47eb0f23167c24088e45f6f3d0f6702fd09dec998489fe053f5328f85505667c23022ac380cc33bcca1998a421d793221f3f5cc511eb1b12b1471375e823ad2fde47a287a9ac5f780ab69f146cb0102ab469d933d9411ca07d28a5a02d2caa57a17c8cc95f51072f5fcbaa9a694d84b35744bbaa78c503ed0fa764fc1e76f4d1db1d4b756338226f31e02adc6034e884683f4b1b2b87e8a3f46e866530e52c5fee9289accfbc7dc431969e7ee8fbed4111b5f9459385af4575c713e2e917a69eaa2e8874093e9aef0299f8e75d617ea5daaf39954146a897ed2509a8a3ebd7e423d650487e8aba732be9e66160b071165adc703aea67f46d06a9a14c4beee1f57e37e3a8a56e3a28882510c63961f9f5aa20159f7b74703a2a88a232996235925632f9251eced273fae8683b8285a0dbba0c9bccb40b49fb81a0f4420cb2f7af893fe954908d15e58a27ab10b5aa38283b81a083c6940363e907def016a89f857a596983189f7b2a7699698933a52c5ac9f34947ed24fa82518066cd18408bb2612855e268d4f238b23b916277ad75d1b0d97bf6e65f49873e19a0707b150d3b475d3eeca5c7c2bfd9cd27d741f79340d5094cee41ccb218e26f90c00e74c3ba449c8b22bb97ad9b173ce39e75c741c7380346ca682e3dc026220fa250fb9fecb42b259c8c2f8d1aa381d638ccfb22a8c3146f7c3c98c86e96f84f90bad0e7fdf0e3c7c371affeeb9d1c868d477fcc39962fe03e05c3f03b5942c2459c8c68b6300be85eb213a179f3a177b084013e120860163de5bcf550e6220eb2e0064f46bb6f25936facfb0e72173f91d32d1eb90b53c4c467a53c6f2eecae6bb70c1e3b5a82fccf54552cf2fdf5a8d0fae5f38a86513b272be94ced28ad31f5f5a2cad96596d353e74e52086f1e22cf52fdf0fd9392b5d5a9ee52741cbcf92b5f662256bed05ff28e3f6821fcbbabde01765de5ef4c7c791d5baea6d513888818856e5201e1c44290bcdce3e463e0ef2216b31af768972133c6446347380bc79cdf9e6c54358c83567bd3cc4e7cf36b38b7578c8756a290bc13e46c84278486cecdb1523eb44e6b9321c3aeabe9e752edeb9e2a8e25c4f57580803111f76935648432cacb2c5ebe9d5b4b290cbb4f2ae0663794b8787f0103b1fa3d853cac2cdf2d72dfdeabd99c5e226b08c857ccc959d9e85b00e36c258c89c95e78565cdf295224959f497a89a28ccd5bc8b2c24bebbde2fc84258080be1211ff3cf4246bcb2b3d2359cd428d20264df4ffa357830513f2c9524d71a38b0764cdb168e15d87050a348d35837b0b26eb8a92765a28719f5335b7996ac7a081f8f93810f27848de190f11fcfac3c5d6fc9de39dd9c3578660d232ec3b11acb9ccf5c83a767b2d4c081eb6ea951c4c2efaed0ea86ddddeead6f5851394028f85cc388bdd4c3ac6776dd728d9e0a36cb7cca718d2296f2aaaaaaca551cf87d7bc892aaaaaa1eb294dd5a4df5c4c944075453cb535f61462e1717ff02e1c3e7ef1a3c0c047cae356e8d22b6fa95ccc2b162bd68a58a317e74516fa9458cf0a9fef0dffb67e9b07484c056d67f46ea5949076571bc62925482793d483a0c80849329cdf26acaf15e39406a2298e86ffdafbfddf8b773cc3cf0ddfb74f35eedeaffde7bef357ccf7c1061df63fb685ac67da394dae7fe322a07ecfd751cce49ec0bd84d3baf3f5c8702aa54a9f2effb5f15815b1cb742e802cf9549d8db83f678cc2ca9237df2964e1d89ef5d6d0c03be6794112b7b9c5a8261f84ffffcfc2c51c23e4b7aa7775aa765e0549c8a53712b7cc8d1e0c46c0f23358d1b1279888cdd2cb7738e2dafcd3316e23c7233e99d9681483a9955effaa7cc6e641575d9bc321ccd5e3f1d950f70ffab5eef7fd53b6dba59573531a0d9eb71347b5553f538d7c3afaa00548099725c0fffaab24f39d6a6d9caaab771fc3996ca7a94acac2d677247f9d119b2a333812610f5f353bd833e4e2673eb52ca8cd2a6941e234958477f7ca4cf18a69429650c3fb1a1109888b7371250a13e452ae40efdf97bddd3dd975c0d7d5841a9c4c9b877199f9dc17f19c3e87f19ac97f93b63dee1dec7bad213eb9e95b89a52cbb82f29b1ee7d0ad6180da24153b0fee6d23da11a7261b69340469f38da27a34fec23bef7b1ee68311806d7981f9c0f61fda5ae01e28120418204b1d182b711830dc0ef9e04a43afe1b48c4cde1acf5e90d61186c72e609ad51be05a65ec0ee6701c3a1fed9fb967ed3c469d1c79915bf5974523506a904432ac1b42df89b410506ff92603c0b18db8f39fd3023a369bcfa180c63d6f9353ee687180c8363dc57a3743b591ed1a3c3cc3a5c9b4bc133f28b825de6612891a2f3bdf8919f8d7b3d4918e949c248f391c2df9b428f0b187cee9e248c1cf131028587a74812468ef84c2913e83d9e9e223c459298526694d898e7d228a58b18f348ca3d534a0f18668bb9eec7ba672297796c2c626365f97336652bd6e653c4f93e56f62a306735ab050c47fe9b8fa2936346dfbf6fd68c14bff86e65a5418e63ed9fac9fe826fdd34f34691ad83c4ed1ac473a64f97db82a55bc0b187cee9fe96837699acbae058cf9df47244d23957013fcc3f6d2208691c3440b3ad735c1469aa631d14210eb9e8f3012da2c4981d8773cebc355a942c45e36e2e227bad37141c5fab77f4ab7c446a28c551aa1410cc3dd0fe586268634011fc2772d0a0b81cf63088ff71e8fa611c2431c7c3cdc674a8cfe1afda4f1a708bef7de73cf83752a0fa99eff5dfaaf5e1ee257befbf651f4d67cbed7bf77ce5ab112d5dba2b8776b551c59ad874e424b561cf7cf5a95855c55baf7fcc69458367a08a91b652b2f33d8ac1bd6db34cdd8ae64de32147b2b9bd44a19f993e9d30ccb64cb60a4120c112ec76a0b0ed3bd26fe84b3b0d6b06e867555469f1c6d973eac578675f323fc86fdf0636beffe6e67c56933fea535d6ebcfba8f417fd28ae3ffecac31bafe687b5d460c8601e1ab6c1acec8808eba97c16f32a0bff7fc41e71c7ce7e0730e66b25e6f3d2b7d9b86f1a5cca88c2fe7d36cc29b59513202a9ee3277732cf4e7ee722a9e5a509264a424c968341a2161252b2bf5f6a0499228e1a1992bc3c224489228191a25513242c24958090f7192c6496243e2c4c66f489ad8f80d09921f1bbf2169c2c66f4894f4295e5662efcaf3a5c449af7a517bebcdb0f03da9de190be1fbd1a85eb7573e7c160114815575c6ba2befbd57d1e856cfc6e7240c045671aa17e57856543909c3e0ea554ea2c4424ee25e5d24aafcd945e016879f7aeaa3122c31c2c73a6631c6883d24c2c2c882e3bde8a757ef132b41fa973552952d83510b7b9b9ea20ccbbc4fd5b364b265aaeafbb164a4e7ac12bd27b1485209e6297ca8248992249d83f5480c3dfe8c7812aea78b381eeb884cebdf43ea8894fee5afc44ad4e85a81a5fa52d52be535c4b764adc98cbe610f474f1a3dcbe8faf72bf1c7b3f2bd77b544efa938923fa2f547df91d05571462f471527feb3f2d53a1a413a1a8d462347e4492c2b4f1079c206468ece08b7e38c70449e18392346ce0847e40967c468d4ac11352deffe5d4d4b1dc2c9b86ff951bd259787f5a592ea952eb53919f82cb5a5aefc7c916359796af4d5bf7a63ec1011e2b8d8b6eaaf21dcb3d42b4b31f679cbbca792641f8c7d6ffe133d965dcf41dc951773a21765d7e98797cdc87035b265f831e9883823569ab464ba8f2f0bc7c25dd154acb715d6bdf690a4e450ab59f92b5dfe962cac97c22749d2cb6fc15a5ebe4bd69a8cac0d7e4b46caa85fc95a938155004560b4fe78d6fffe78d123f64f561cecfd29cca9e94f451f9ae2de433de478da9555c9ce7032ee63bd3f5e4f1162205a4fe9a19ed2432dd4403da5871c4f4f69a0290cc39f044ec6bdaceac5ee0ceb7e62a51286f1308f93918d06dfb7d1d5fb96c25e7b8d8a4f0277094005d85beae3a8de5249545fbdd855af949622817c39b1b00e3a086f54cf56a4242525f5b162ed05cc46d65de62995fe4fd6cd58fe3b635d0ff514fe7939a25e19120a120a120a120a120ae20124d4a2dc06c4127900f10812e21124c40388479050108fa039ca58de9aa20c56d925c187855fe1a8b4156ce1908dcf83812a0bf529be876fd57a59e806db0ad62bbf7d6b35a3bfd747f85413b290053e7cf7c3599959ef0f8765ad65669df3af14401198ac3da895f3392bf9ddca8ad3f19b3f0751cfc6277aecf5c18a926cfc8a7b580f34185b91ecedc124eb15a7aa28f557a6c4cacacf7a49363e96ad3cccac87580446cff2ceba8931be8d9b908a32f8518995b7e919a995c1af20307a96c7b2951765d6cf2a8bcf16aca3487db370505f55d925d9ea59322b937daa7e25a32d533d5b37a8af7e3265c946bf9215c1041332be12d8c7ec669479cbf05b192dc17c8c123a6f03ab58cbb0509050905090509050d08f964ca6333276788c99183262ccc49011c3fdf42c60cf624d23c3d2f8c7f7695d4f65f2e78cd55938e87543c66fd60d5ad95a19955d195b37e4533fb971bbcb7ada24c5487d621931285347729c0b466c0bdb987061889d8ebe1e2fe2789a8875b7b1ee58af3ce4cdfbea8f7ed60db6d6c7f6323e15a997a9eaf548d197adc977db2aceac4dca7afd59a72acefb96e3551cf8cfb6ae9e851de337ac4fced56c62feccb0235b583940ad571cbec1300d6b625ac6b1901d35d45f6a634ccbb81bae598c9187340dfc266c9c5b37cc42e090e94dc33a4278c88e69ad1bb769696f4ccc080b9873ce954ad601a936ee0d11c2303ce745587d38fb5efba9faa20195ef512078e50091d56575f5d75320eb9292ba4bbe6549f8de494b5279c96faead2ed1201a4483681014cde57bef399c1bdf9f08cbefdbe5f7324247555f5129a59452caea7f34cefc7649497f46a964f5e1ae39bf256d5a45f8bead7692fef55646ab83e0ad6fb569f5b4befb9951b25edb62dae2b804f5a6bb7b3b7cf74440f7e93f6dfa7a2d299a0324ce9f693f3855b462a43f4361443cfd2b7aaade1fd76aac5e10e4fc95b53e59f5d2faa3a9efafb2180c8392b15e2be23707510e9047a93c3a4485a8100da241a31c20ad43812895918f9e028de8d0685e2ad443443d64f4d7e988326c527f1f66e768cec74a348806d1201a4483240f4625f573ce1c20f1452b706ef5b055ee2fca8f0f7a453d29f1e714bdcfd9667c5186737db3ec237791bf288e2ed1579463ac73bb082b1a89aa63a3d19b8f61d57bd0e8f3df5f994567bd1525618cd99402eb94927371cc220b2b00db9850f1c452f16339d64fa65635e5a02feda5f4af7a85b056651c79e1533915c338f83be79c7bce39e79e0ffeb1fcce55f2537eef4c8fdb8148f76d92fca6c86f82dc6d5fe69cbf7b771eb4bffbbbfc6692cd4efd8d7fe1dfcc44f59ba209f69bbc5972cbd8b28fac71bff75e95efda0e586545b517eedd3f21acbffd95ef2f330e6febeccd5d7702f6bd6f4dc37fa56cf95d258d4603beac57f64f297380b5953ad8e2c80fd85811c00fe40471e884c8c56f2f0c18bf6c62b01f78ab79bb75e33893ff046b7fe563e76eed78ef2fdf2b25849572c7634b007b5580c5674980283431d858618d695732ab9fb469e6578f150023596f0294c5748ace39216d4df36cf5cebaa948d6fa69b22c0562adbff7fa363593d71b95e5f6c2bde9aa37dc5fb7f05db336ceca7c0081f227cbf9cfd2117fd62b7a6fed85f5adbdb87e66a27af9ce7f0f65d3bc7f7b2bbbf3ef7cea29ebc6fc7e5ae1c86ad211df3f567f7f985dceb1f809c13560577e13db4eb4008aed8ec28bd846c18d4fdbdd12001f086b8fb91af8fd5202187c2a600d8a2243f6f27080e7dc00f86773cdcdaf06c239ce3594d9d2861ea0b44c01024b102a2f9c94e8210bb7fddcd64486bd27cbff6e5d7b2166b81ab7d11e6b1a0358fec62ff94bfcd2368b9f980d0458fbc9c2891dd9f623072a6cfbb19207cbdc9e5b4da9c9f0b737b51a9b96e1a7fed6684d6c7362db0be16aa46bed17d034ee5b9f2e006ee08fb280dd92e512ccc7344ded31c33e2380891e211d3c618a2bdc50829d335039b204305049821620172043a14414059512cb504541a594be0463e3621f8147804cfa6dfac4801f1c0be8133f3f5feefef605dbd37614b6bbfb674a7de226a365f86136f33025378282ddf663f96d16d0346df9191044d3dcf6e3f6c6d89e61fbdb605b86ed2ae460fb65ff589bb6e952d7481bfcdf35ed87bff50b016bb631a9e28835d9c6a48a2e58681b932aa8606fc9b627b22b28cc2705d6bec53927c3dcb46f4eba75e3ef4118a3941445393b725319a9e6a4d46a587bd138f34b7a17b1ed5b8f6ddf78da8b8b7246e486c7b6175938dcbe9df6a2fd1451ce30eba68d1af3af28c15cb5e9365ab16e562ce798857246b26e943461dbb7583748ac8b0bbbbccb94035afeb65c2f059e8f6d2c24524b8b8b75d3faf4d2f2f2f2426259797919612f2f2fa297cb7a79a1b3a2647c7981cf5d73f3e1e2bf3163b95671983525cb9524038cf6a925148c9deee036a254c2c03f764b509cc01dc37048247bb12323905204edaeb4adde0e58f72f076671aeb46c833fc7998d6036b56fd674836d5bb658e36776156b9be35804aee635991158f7aed97819db68d60ee99a5f703a817b05dc1b03c140308cebbe5b070a6c5bcb3bdae2b87681f3e6da7b6bad55ef16b8b77bbfe3f8f3e71ee5bd0b76ce9d3be79c73ce3d18deb3bb51039640d2a7662fff7b5833ec3ded9393fae4589ffc52eba5126d8c04afc7a1fb73ec1435763737bc6236c7390464c02cbc1042c8af0bb6f5a0897f1f95b5f75e7edb1b33afccf1a38ddf0db3fce6abecfb7eef7d6a99f520d9c75da01ebe6c35f263cc1a26b2389cc96c4e86cf0f6b4b12b0ebd6bde62a7e1ffffb7e9c57659f9afa426d34ef2f7feb130f52301c7e6f3fdc3219a4c4b0190590c95a946a587f890d9114239148561c48926235463a4ff8a08613b06143c9844718e1eb9682be9b61082c60a0f40814232d29839bc3132023744b119e40d96850ff9e6a34aa7fdf604c38614422da5630424b45f8e27b104688cda0074e2282ef50ecbd962ad2d217d603605d07eb584bab1182d468c02741114290c0d5f0c083946d61fda1bd700f4b2f2846725f83bf57399c7105d5c3b651e4c3c2776ace3bed7b0f818537d47f55dbbb082c7ccf51397ccd9b9004a6ea81922aaaf0c2a4e4ccc5acbf272c1365cdf598895760f2af5b27ad8cb658611eb0f119b3f1298a87999625a45842ce58af557b0f60aa2490f620b214a10c10c658a593f207dd469630f3e20425a4222871862a1ae1794565474a2b96fe347367c029a10c824949fa1356ef694684cdb8ea9d315a9955bd2f84ab2155965269a5d621a4a4a1e2108fca9ebc955004ad57dfb9fac1c18350ec3dc6a209596f43d66d0382407313ca54acf310033115d69f9fb826aea67aafad84b110d23b8ec25cec1dd83bdcc4de522b8180b35155067232ee454828f110c5488c85ef30d1612c3cc863c113c2638333bb940723739352ba8184549c8f7577db5ddd076bb1ee884bc2157144dc10ebae08ebdf59748dfb692caac3c2c536b89fe97efe74b2d7fdccd8eb7e6c63612f86858b85d5e9b02ce186389f2e0212e703a94023107209831042f8de7a940efb6115c1b3dea7b7862ebc1dea8a35b83869542ce145a8c4b3035f59027b4163e11e62d7dffd61bdef3d1caf181446701b3cd9683c1d6d636ded05e7c808dbb16086b27583f5bf9365e607212451acd1785f5a41c9d5c43424f529c60698f729c6f6c9d957bdbd70eb27b0afb5537c0d32bf210c416708987fb324b9c14f692486d20a5e637f6666e6667e8f04f3de37f93ecaf7cdbabfe7ef555abda578afab934a30dced48bcf79e6c01f6228c5404e260842fbe577f380b2174d69d0c5cc6b52d815435219cd55343587ff6870fbe079f6322adc8c7ba9f9492484fa7d1c0f7eb46b0feee737b50920b28468a7f5b58e73e8d8a75efa875b363f9e78a0fb642048cebd580fb587ece2889c508c90bd87d3ddc970fae624d399cd580a59eb4176e87ebd8fb7a32fbea45054b0fc67f2b23d619c1deca08c548bd13c51856b0635de4a306521440a712122d8b49229560bff760c348654cb8fc17dfbbe8ee50a475d3ef482598eef604140e85f8902da9c42fdd87fb60120c9348303014098654c29a06def7fe54c684bfb4ac1c2f5a37f2a115448e7599c32bceab9894d414a4120c77c37e3c78026484dbd400f4822fa8c1b630015449b1841440fc24073f2b4bb88d0c8d0ac982364523c30b4fa3626261c90d1a153f8f87b0b095302af358058bb159d87bf5beaf7e62d1d2e12f2bb47460c07e4549eac7e3a14a952a56be6d96aca6fe5845f02cac38af6255ceb82a151577f7fb1e0cde8b2c3e3a9e0f3b010a4e783e15959e35c4c09a36c9f695cddcfc6aed857f0f927db04acca5c430ac51dc24c55a1389ec3d3d0b2ba9bd70dfe203d207d84a8a7177185a76aa1febbfa204f3ef5b61a1fa896ea89e506161868484eaa781c18916543f2d3b2e0bd1c54a55d3fa781dc4441fbf1b8deb639f68fc6f278865a2eccababd786f65de5ebca7f37df59e8a39dc3a29a3f5870fc6f8a8eb740feb58ff91d065245827ce815dc048ccb8048175a21360a0c23ad6bbe706940af4b1fe2b4930bf100ad6a18f4c012c82857f211460ecab947c80edc524853edcf3c408f4a1549c0e804e2f06c67a35e3f48619947cfb6690a220e9a9859329483839c91e855c2102e66fe3faad97accbf95786f5ffa16b18c063c6130ce0e16adc6c22ab7a2556ba8d0aeb2cac3348516acc5849420a46c8199683b814fefefca13f7ff8fcfde5f05aa50a23f1e6246a1d9e1c0bccfde46475a7d110c93a07c8e75e1280144b4851b2de2725d15ffc9751ef37107bcf41da64decf2a6b3bb3d94eef453eace77856b29d8f5bbbabaa1555ef8cf586c0facbca8ac0c166e5a8ac3b8cf13de8ef30f8d7b8df73e68308eb2df3c1b2625262587c979e615960ee486fc0fcf92fc9b627d6abb7454981b1a4186d522c21c512de6e03d2c37f4d7cd0f348a42bbf6bdedf93605680f95f9275afc2bc93d4bae12cac520c18cf384bebf506d64debd395360d238d19ccb38ffbf1a1fead01b3a546e372b36e1e83c9da4e1b3d7cb33e70b3f220607ad1d6340e731455992a49cdf9524a2ac2222a2bfe2512322d242c8ab981629919afb86192660bfd7f9ba678e5dbba51a287af6e5858be5937d95fd90de96306d93047b4878cb17032f0f989cb439707009599123dfcece149cbc087f0e1bf29d930e513cb549402cbdb3465ddacfc9c2bb639118320d84b0900b060d8dbb485848bb769919016900d18b4cd891814611bdbf93466d6bf5838dac25859c064730a98b7e9f912c494b1fdcb9a2d68c5ac1b12c502b32800dc3627863821bd4d4f1734cbe2f3b062ddb0c038acc444294c171a33d157d64ae52bfa045f94fda00560649b133150123375a000c8d84600321e7232f063b2e6645ebe590cd4271960598b7583ad1200f8a903fd2cfe0ed98b75835506ea79993ed098413760d23626572861af8c2dd5038dd96807eb868132b6a38c857880c50780c9bac1d6c5cf1d7cd02263eb830e960fb62d7ef24063167b80c58f1f4b19db9315eb5303c631960fb6a59fadba9a05ed05fc6f79468e18b1f0e70e34c222da0bf82d6bc078a84ff0e1ef60dd68cb402cd427f88f05ace5f9e508f624d1c3d68d6ae7cf2aa3b266a5751379d8e1a9df81871daa29c77cea67a52aea7aeaf99d0ecfd6e930afbfae1a001a65b46ee2941917e913fc0cdaeb679c3f7ba055467d55796bcecd0a93b53565cdbac89a6dd1ec0b7c0064cd864bf60187df925d4849cdae64cd625fc92a635b6a342e11b5e65bd6ad5e9aaa69ca8155d5e84d2391754d1f40ac893ef5d4acb0aa9a72544fd9748561b4cae877114d439f9fc5ba29351aa5f6e2a5df7472521425fa6bfdbdfe563f45d65545a65a502b7b69c09a1342aeb0b76760e18d26075b7941324b279f7a4a66d4cf2679ce7a4760abf854f5da3b3af48d3578fbd8fed9f2f2e55fbebdbc54fe71d969c95cfee5a7b36e5e9c89d4604d73f9c752d134cd56b0fdfca4691a133010b1fd0ca56932b61cc5f633943edd583f62717979a497ccc5a5c5c55d5cfea5e55f5c5c5cded4e2e22ffe2dff92b1e5d1270bf4a93f0821624e367d5a409fbaa50c1809a3d2a66948dfbf80a661f97e0634cdcaf707d134a3ef17a269b0ef9740d388be9f47d35cdf6f81a6b1be7f085753fafe1fba6603f4fb4bae86833899fe2bc3f2f4a98b6cc0d5b011c73f5e2f3664bbe5bbe5fbe5d9b6a5e3c5bed43b0297b7712e190fe9534be59dca444859ebf4a99f25eb217dea5fc97e9435917e51ffd56f659d449ffa4b591b7132fd34632a195b9131948ca9e028465c0d5fd1fce36a18a88be02c6c1be987fc63fb557e5205ebefe7214db3c344ba06ebe7241a0d0ed2efede3d2d4889de6240806a29fffe46a82684e4eeed49c9c5c75b2621b145420d98161f0372741748d0f1218a2070980864e1cc47bbe8083e6043ef94213f636212a57d8dbb2e0224e4d737fa669ee0e969fa94062572c57e1e49c733b3010cd4a4088209a13176648831165584111234fb08115a1640a54e818018a1776e27b4d4ae73c4ef99e94523ef94fbef7de7befbdf72084104208638cf067b618987cf6c650046ef94d39b8c7f21476267abbbb4ddffe1cb85b6e2d7a89badffbbd35f58efad7b73ff0a8e783ab0421a5bcfd3fd846c6e0bf9f6da00244158ef063e365cd467f0d06822d7fc9d5d46832fc7c6bf45facd4625b142c3862bb5ecc470d1002147b6b8cb03e99746cffad619bb4a0c7baafd1355810fe050861810de8582e51692a5ec8420d5bc4eb61a231e98211f64213f6fe6d5658b671db150d188293ac014a60913540892c9a068a29ac60a5e587691aae82e52ff0587662b98a4987b3fd389cedda80213ce953b339d6a66fbc9f05a0020cb30be813dbd41a7d621f356009227d6ab686e52ad0008425082188064357d8e0390c91010a2c3ca148902136da08c307530065666666e641695edb10f6ed60be5601779052dffe87a6815f1ac280ac10ad460827d35f6a32a361edc6d8f633ad6686b61acc5119095aa67f2801e37e219eb88f0d2d1d4f0706acfb917246cec838e39ca4a6c0f8b1fee6baddb763e7ff70dcfde087858a5acd8c6c332fd4f2538c549a99218308314c814194294534a18402e89cc1064338021a7e88e854465c549899312363c6d5d0e61e1804c852a0f41443c2689b9131a3c68db13c05288210032054c1840c98b071319b5304291409c28113514882848d9c2254a444718624a8c041156cdc192ab014b0d042052a5c01064c104214a64049d48511ec3b42d9f096adf7770feb4872c02eb55d6a4ffc96a2a47d6af938b26254845edd7f0183e98a44df367ba96dd928577fb0fcb6db0a22c7bafb1e24fbaab42e33e5700ff263bdfef0510ab3b91d33574d39e9dd1fed6defcdbfabcf07e18b2e1dd515bbf7e6b8a2649c97eb6ed983f9d73976ef4eed4563777f9f1e639d8b8fd25de9fd2ec3fa24ffbabfd2653c48c1a475cf38ae4a8f42bb3c7eafb5d7321325236ccdbdb9736fddcdb52a6c7c475d0dec0ff86c9da5e3c5c27a4760a3bf7bcec555cec908dd5154455155d8f753626eddb4736ef2e7dc6584fdde73fe2e73f7eedc73d73025b4b48b9b83757730e6ba23b05e231c2053ddd745f85c4b49496965d38898d97d73decef96377e7dc39e7deb9b716e3ba23d8e19ae29785e7f720f40761f5a68a92d11d3abbd6ae88bf0cacb157117e840d61eccc545192bdd9f67bf8cd568eeaa753cf4eaf66fe3bb54c0c16b0bf31a42760fd336e67d08035dba0a721b615325af7d2f2fac08039db9878810c7652937af6a860ed9f0a338d4665ddcff822a4d7bd404b27a57c9c5d370708f04524b07b8bc4ad3d39193e0206d32777bf3fa64f0eb36ca74be7f88665d61ccef8574ac991b317845d927539dc6bd65e38ebdb0bf74f08bb24189817d61fb3e1fc5521487b37b57bdd6ca26484ee5c3be7bcfa8ec9fed839d1e87bad392923747ecdb17bded1fa9df26ece91ec86e0b798c9923e2bb0f6d3c51c5557cf62bd99b36ef8bbc64100dbfdaddd3784ef26d71a02b1ce1f6592ed4db1bde9b566eb321f406cb70f67217c6776af8372d0dd59392a6bedc03163bbfeabced089f9c43045ca3138e8e037f82efc97f39891ccf9ed67a42c68c292192bd06cd08a82f9cd864d4fb62527e39e720728b90138ec95019b8f63bee408344b02f3e118e27c8a728bc20d0258ea6d5a561f5c859ad915c2ca9f15f5183dc2e8103ab5c34a8ad1083376931a03c5c48035fb166d4d43b167ddc49c679347c8593f57f14d547c938c6feae8ceafc3a23333f5e7046936a67bd7d60d02d8f86ee5a822fce8168e99589fcbaed630df1830f71b906334c164c09d261030c01f7e031a0842c083851567bebfb769f9f1a7e89d017b2f048eeae5cfec52dfa3b2f3275b3aa2a5a62001092741b4294840820a765695a5ac1df21dacaa87d59d4f559969c28a81f8957fa3c94dcf24e19b22ac66e603888d55e4f830be4dcb8fcec6afded558d5cb94bc6c23c724668b3980a91f07f4e9b78799293ab7699a2a13e56f92ce11b633846fd36e6920870a12b4fac0c22eeb4fdf15309afdc06a404e7303b8adc91374ec0f67a18e4682e407396070c2061698a149cf02e8b7d5801ceb2fad0cb405d04e820501ac21e06189fb16f380744e6cb78244297dac69aea7367de1bf2b33417abdec5a4f992cfaa649df54d13751f46af6f90062e195f970163ecd207c43180e7dc7917ffd63b32e05b202ae628389f744543674bc082061c37db378b47032050948389982041e1bee8d01a3efde148c3e0e7d6f7f3dbddead9b8bca5e6662ea5dd7f514f534bbf36fc54fff65a649bda9a2de04a93731f5aecc07106bb17d56a859a8e4cb174c9bd829443310000000027315002030100c8784c30189a80a26f51400118caa5060461588e32887614a19638831060c011001019011daa4019cfec2b25db599508915818aebe07b478a4d07421aabe3004a299cadfecab867b531b92ec46e6e358cd3b07b68ca4f4f0fca2426b53a863107765629a9fac1e0e324b22e991696c8ae1977498a9b3e615ce2baf94cfffc9c4db5e12c98cf70f6a7a51d78fd492191e7dca543d02860a989e455c503209a40ce3623b415000c2c74a7b8ca2728392a301218a97bde0151a973a41509a0c262e624e5a32644fcb4f2b89e794357558dcc0fd346a2048325bd6102a916bf653c30bd1fbad41535e879e0bea4af6ead2d5950a52bac6894fcc87d7388535d39840fbe7584eebabcaabaebf78b051038e07ae204998cb3f73fb8c44d484eb494bca76233627113390bf27f9e82086d404422c957dbadcca081e2d66e2b2a2df4c55234584cc93e72ea922f4bd481f6ff165e5d7d10b90b5632eb71895e8ff7210b602958ee97d0523d5d542f2633f9c0ac240f7cf84582ca7f092209f83ac3a86d0914c3be4c485263edc8347348a3d81115eef44e6c7a354e3a2807214ccf1f9bf6d53c01f2f982952bb869c6d019a73739c98e5e36b9d6db87319ea4b5ede49804f2aaa391fa8dd9f53fe98fa4bb5bb5b5fd3bd90cfc10ced3a7d8a9e719876bc1f9dc1f6aa6dacdc36c596ed4f138500a6ec9967f8f54c35d490989c799471aa158acf157492119024d2287bb3474eb8c4cd47e7d6e421d0c58206b6281386c1a52b121bb6089b3781e483922de78f71d51e9a7c880e978a550fdbf71e3ea60eadd0f2d3b7408d9d27ef6680fb6748626552d054cfc784f50bb720ba69821d7e631e1aac50099fa404708dbd35047d695dd8b716e07c280f3392a0ccc6a0bfee0e7bb3cc216857898a31ac056ec370d2cfd3490f4870e42fa11bff079070f0b639d0852aa032395dbcab695d36c83345540560be43aa2a6a9965cfd01602ccbd4bd3838b152fa238207b078dbed55e7704865628592a5c40369f2c8fa359036ac0e03ebfdd311c627e9a9bb8ea265269aa1be32091b5b9428932a81ca4fcad6b0e43770b85042fdbb14a385849fe947ba12db5c689c21254efe86c1b0d0e1032ef8886d917512bf5328e08f3f6d0003ce784ab43f095753663fd77854aaa4f0a78d1c6a00922181e065cf004392799434bf0d97b3c9aa1b91c3945c20cb3d2ca116741470ac19a25cb5bdf21ac54cea988f92417efbd36bf41c559cf17e2c15f96eca61e9e504befadc4c7c71a333e0dd5da2ee40ff4529a569685e669c9051ac3d50811645f105c1749e7fc9e3abdaf82c93fdb8d80dd83975316f00464d521a7b4da487baf6b274c16b156dbc01d7276b2e2de521b3f05cc899845d5046a75e2d18cf60aa438f1e16013e19770bcf5a943c667216c8c13378afdda36cd58f23d775e4e3c0e5c307e63b625713785e3bdf53655cb8ada1f92d5411f78fd3d85964cae332a5ae88a765c625a2fd649d5d602f93fa7e71c3d3a81183a03e3a6add5049191bb0cc6a991aeef01e2ef8022249aeb9d4268477f7a001223473ea2651d2bbfb79371304f8c061974c7168b3634494741ede3fc9c9849234b52c11d8fe2e5f590bbbac0c343279fb3e0e15482ffccc6980dcd1906c00ea8bdf051ff84798cedb4ac8950499f51ff908901774204603dff911877ba1177f57fc73748ddf23df045a70a0c2e7252e92eea42b8963e52cba41e7db85a33c9ff33ae3c0fef0ab5d9e4102d3d9cd8bb8346bb51beda3c7eaeca540fe0dbc533bd0ffa7cd65d4aaa4a74a99a94a95eb1b24692afb945fa9b8ca25ade7d984e480d3291cad2405bbc28dca050d47a34a921c2956cc36bc0647e954f22352bb1d4799b25ee1e547242e560b4781a3f4d8e169043b8ab2ce5d0b38ef122f34e64997b7ac484fc922507a82ff42c156d44ab7bffc71183821192fd7a2ff79a5180ee65bf5d18ea33bd5141c5a7f8064ce2f6e8f5a14b560dbd711873d2010170945bf2bfe9fe1561ae4d9e6e53df1c472539e65317f72a84358328d846826644241dc4b46aa68b42f00910d905f7ae0ef195f34ec13636258b264e618b8d0e0b58f00f686af4ea8af0e1248159035592625dd1a3805f429788b32f412aa35b90c2caae9a2687a4652da9f3a4e4217b93db098a919c28f9f658b8595f210e32b7383fe7ed0b13e5e4e4e8c3ec2c14165c2be524695244bfef1fab58304115972093d2ef13191c60fe00a200172c5c50b3ec6585efdaa8552dff6094582ad1983e1c3f1dde885cf1dc8a87c5c86c4dfc60219e2372c7528832d48d728223645a97336a0429215754cea391603f04205d3e3074914864c1758ead8eedf2c76f4fb7e8b913ac7d1451e9523f5abda93246e3f6edea7460249313869536061ac32c6362e0fc0205ab5900f1fa58f91b7cc9a190d3f770e33d0d3dc212c33cbd6709d539d10578f39e9ae609a8043a9c041bbf82c7635502c03432d393c318beefc423639f53d977f275c519a0e55b714923202d2f047d048b0ab803fc22704eeffbe21ae396d6d5ca1de9cbcb135df728338ab2b35303e8aafb5e26a1a8ea99c00e3d3bd22c10c8600907c39bd9c4d443809c13a875dff54f859999aeceb0c753769e285062d876d124390a34af2b86e782e0d7d8175916c91a87d3948d68d6dbaf7544242aa29447b0d0aeb138cb78c8ff28ac4bc9b420d01e8104ac5775474b7da3b4a6fb3d064ec4569f6b09f8bd0cfd65b41f682420044551e5d13a8b1a35798f71a3c41455528852065ae06f882ee2b5024c759475ad31943e96902bc5374d16c0434e32b4a574b38ab0af9cf694065f5fb3508f127006c9edb84ab63e42a8884a0a5c21f853cd07d9fd32b0d1d9ab4851a00f2bde3a60bc2217c0cecde13c4a53c5cb5145b4a5cebd68a7dcd66e8c174dcfb35f60538b11adec6e3c81202480c211239ecbd1083a1c7e514a6efab10d3421afcf0aeb6650be907ef6a2486fc483d491e3e8f40dd07341b0f1e7f1724f1a366b22f4d9592ad45d7fab18376caa45d8b86278da15e1b2adc1a1fa64f6b9b3dcb3c3df966c7effb211f89a648c49874576e284795de1d98c93f5ee8d2510b5ee8f4a66b1cdf4d2dfd8d17b97c0525bc242fd2b004628ba2da234a74ee8712ce5be4aaf23e88b2bc502cdbee38e4212ac67e35ce930deb89f982b695f8fb7cbcef0a757a2cdfeef87cb41a1100d5872e28ae08626825ad8f642b8a6277f13db1551f3595e732f86fa23784a49de2be1eed30d995855b9a864f53cbf5c0753b81a1b09f0d2202a5d0212d1b83028dd42c91dc9c62232ee732ca674406087250d3015d97e96a9d1698b168cdb20a7189f55d0233dc7156baabca96917ec63a85bf42f7fe6fb3bd640e2944e9dfa04a99c66e24e0420ac680af9cf53576cf54346d75bbe8878bff972be5798486c9e4a5f7833b8e39eab41d690f33f8feb78bd632ee3332acd7e89bf0e2ebdbd06702529caff9b356623ab22671e49025e99a48836bd08d66e4399f7d87f4893ca938fe9251d547d2b3479898d19fb27bd0372dd89f0b5c028f5423af90b79cf4b62405795c3b9448b57ecacac0199b32573f90ec9ba9484c0716fa5901cbe99de1802db34971a8305ab54cd51632ef5c416c19a6616021c53692a43c9f5f6f52dfda726ab7ffdd244a8e6ded14fd786bf99b2ee06e80580afd46ae176a46dbd0428a2da46442c266a4643d24a66abb077f36432a3796df963605e05e7592ab6945b0f852f9fe19f3dad306e3a51d42f0baee11eaa33286687e069845e770c7b14fddefc9e782593eff6e6775e1c89e31df15e512fab3c741cd1c421df33f7c657d889425981bcbf98e93715c0495b6eae1f62c10374ddb1bd9467b42feb3f3bdcca2b7b42c6dbca6a7702ed7f27c237ee986f25c5e3b92dbd01f75895e6cb0facbde69f32dcb2695847c00a0115a268cb55b19b0b8d09c8f8d01e13ea3b377cdf6add5637d112e235dc84399bf84ba514b9fde676398eaf471b8c7dd4ea421104daf1024fce76b8ad3f243a6de82d3deef58c7be466d3a15f7a54c83ad257ae9ed2c7a6b5935170a278b5115da6d27dca7fb3e2811a5c908607784f2ac8c8b0180a4096b89d1bf497b02f60d60fe06271135e9418612b848891cb8753e31152993f816e83f8134573bc0cc2bbcea35643ca6b9bc4e05f4c433062c47043317401d8e1bcaf90d7919e60c4059dc5ec45f876e734ebf6ce7f6a4e28e7463952e31c44cf91d941c328dfe03cb47c2663e3c2095a5af784e66db8bb3fd1030ecffcc64613e37d8370d5266ff996d169d0b771f14a2f1fc51426be19b40baa14c7960d5af6b3a6542b5d1ff27678196f04f40cfb883621953b684b3ecce5941d25983f34c40b66723fe200186cc8cc6c2311a3bcb331947272fc477a0e52f7e0476c7081b99d17e4884149e29ab01c234c7607936e9d44d462af8d17241bf6b7c6eeed8b8f183b54273324d7bd8127df400ea5fd1cbf57f43887daa85859a43eb5517d58dc3564a5abefd2a7512ace33067d983a2ea816179320049703c02c807f265a5f26e6d93e879d992d2e82aa93990f8ffcb2fdd7501e98ccbe100b93c25ff5fd4d15d4174ec931550161c43db0e19e4b5ec3984c18a465e0078980cee75e96cb40027fa9df2f3da6f44febbc3b70f1ab889f332d0ccb37546678a3bc977abfcbc430bc1f4ab76d565feb1be416b19462e14f428bc946b514bc417fd8266cf1857654a593b3bbc9ed37f4ea8d6a430392ad63819a1b903d8f9fe75922b8d7d90bec25149f9907283a11db2b9b46523820ed28584308f3758f823f6c1b1b11e74d90463e176d762d4747bf2ea68291e1f5a59c389ef472277a00f29402bb30f1aa6450a4c12d09401e112bddcb715081c6fc1cd105ce61f40cda0359a1fec7ce3518d5a575dae0888e9433bb0c1380f6a6fffc47d8f268e899bf12847bc803c19e754173476048dc2c146ef9c6dd3f7b4346f118dcc42400183c55df5fdd0bd2d80a3bdd3334e0bdcec952080ce6bb6ba89332bf25f0bba7d97b23916e76b1b4fffa03f97f2540e8aeb7946345bb21469988257d196ca1b67cbdb5a90d235e83ed32407975be891ea3b464205d63d9f6d60828ae85a4add0619fdac9b56f54b2116027b4b44324094cd35cf9b12b4aa22bccae6e27c0b83aad84354644062df69b36125330fbe32585f9020d2ca6ad3ab05c7a9742b3428d86dd9db671e0ae87110585601736363d4294c94420535795be1b2c9aa8c2d4aa24ba6761fe8da75a4ec56a17c70da70d5898a318edad957cb4e1351de914220d627af329db40afd430a403cf68892de9835729930fff6cb7ca32f20f38948d9fa61f8546d175baca56b8286cf89f260d58d26a20dac04b786782d98df4bfa7034a6556979fed283062b23d5f25acd46aff33e9ef03f5c5a1febbad08b1ceffb70048605bd578bfb2b78ca47d7505035198550013fba3df5fe725447f18e598565ea76dcbbca802b9825c484edbd0f31ad23337c2aa62cb852e082d337f959e172ee6ea58d96d34bb49631b815af38de81910815861a0dbc9a184945290c7565863698b8a5d9bd5a0b3c85cacdcc20c22f287ea9aef6834679e548d7cf33a244b22fce7a8cd24b98149325fa9cc935f196984b098f0e9a9f891d404bf90bb8d5adaeb7e75cfa706d33a19b08ed93f7c48722919eff3329aadc9c05d08703f366b241d84545fdd631f3bdf74a70429944829bda222a9b5b621ae80ab650fdc3c6b6b386fed6d99cbe5d341fc910ab35d075b93b5fd17d0572ac454e9424a36ca9f6cfc6232ae9b7948cade26081212435139bec4b9fe918a4a2b7bd918d15865ff7c5db8e8a57c9b509533cc6f9dbc369c33594205964ef047ed3032480f57a51daf0f50898532cce9f632b026f03ada662cbf2cb674d429a4b01057da37ac63ad80cca8656d30c98b0b30862ce6fd5e6d9b6c8ea013a1d3a002e2361bf80f831f9afe2d19416130425c4653c9c0bd15601955d98e9a683e396f35861d06943780cf4acf14d0f60bfd74d7b38e6a2f9910d264ab490e397c4225a7778ecc558adbf1643892aeed80ad5ff58321ea645da1b7983cea071faa7aa0229c9301c97806cdb3a0f1816914797e2a40a0cdd60cf39724154e8d7b019be4f8ed3aabfb2cf2e57bb45fd91ba504954643d926b6cb445e72f6fca556ebbf829fdb261276ca380ba918784a3ecb57a538f6221992f8dbf1deb492d38d684394e1967e88c9edddf75fa0260c05ec90fb85d18edf66124efa0c7b9be74316ee259c59c29e4504ce9210493254f3bdf19935239e383373e78daba2ebae1b34be290ab10cad114f2d5feb5cc91a21a5554483a399f0a7e3c65aba272033735920c0505ac2368071a91ad0362813f1f9d85acb68e322525f9e2e283d6306caf04331f4a8c4cc269f48176cf84f20b009f1f04b44ea3ef16ff9bc5f6bb32c718c465f152d09058d23faee9623309fd4780ed1085b307933f544ed547c536cd75fb0be641817479708c43a2eb89762eb1b9badb2b1c1951f9d04238908f342fae6a64b15097d621b7676d41c390b8d7095bd9bed61ece6c61a94f1c8d397be96f1e4b3cb45066ba476aeb49c242b5acad77635f29b624107725a87f625a56c2b82b885591e4317f2e5c6609eda1dc12c53aba8484a20ff98e164aad449bf3c2c120a184a4da60a1ce36a33e42a1e205b3e9717a014982065ef7651f87e40a41006103cee135290fe95affe5ab92143362180a32895485cbea8fe01de534516c6f0089418d52522c8702ca9cccb131c652db6139159169eb4c648d2809e2238822de9bc7b24dca62515c14a48659ebc5391ac13e56b93452f6118be74710847bd141f37e015c17d2c02cc0251cc3ac37175939add8107d648d107fc5144455026b6b7029187841ae63613486b7d9ae5469d1496e6a8684b3adcd404c094ab9e4010124d3dca930c8d05d12daf9b4ef219500a66a48512aae667e27ac4845bfff9a67b22cea4b13d9a412139221c42d82e4c60a8b211a2b847f7bc436b544ad30a6af65ae6b90c9545d59911c0ef867021934f5da7443a33c33b0156eb1941640a1b378d37869c616426570922458a90e610e468fd35d66c067fcde095cdc243e26b417fb15c6ca3260b08c184efd1c3c8cc9653712ed35eef44d34179e892eab5b45a8d3bc53855ed42e4bffef31caa3f3a8d17c70df9cd20292712aaa4e73921954a36cfb468fce2c9c4e8beda9042bca173fce9be524f9f2fc094bad23d3de37ed096776d9138a23a20b6c0789916d970f42ec862512a959dac4690e93cefef7b952f0fe2058c44f150c570d28f8a5d0c07bb7981ca264a988a2cccca98c449ce7aa59d3e5b28f337f46502d7d78fea8102b68065f16871130e8731f171090cb40f2eec39408a89c75418543752e87bd5ab0a2081e803a0ca141b47064c620c15654c7d4be212fb582d1563a995c587ed54f3a5ef12922c65df2b853859451c9381220626a7e912a18549a4242ee95a26915ed88253e5bc1af85e617e15aa965de3287c9a968d833ce6502129b7224a643d9bb1a01794f86a5b802b5cdea433c8fbc68f600b613df2bd41f4615907e308871254b23ef85e4ef9abc4d9dfe99798dc0aeb97d107fd542123a11b9aa592c84e2037e5ec29186971a8c6dce4e78d95922da1431f7c0ff28683fb5442bc64c6a267c07b216647693c3cbcba1279fad01fdf32d695534e49eaa4e86cbb52aa993854fa6e44ca54d3ed305e2a084b0718f6a0178d00e6ad8ea17050af510daca3afd2d714b6d2c553962d01d2ce5f168697dae36f44b47ca46f4c64de0fa6b8dfae0b2091b979aef9b116bb0cfaadaca5773fca38e8dc290a82de2002fa7cc6c02b23181c58431a6a99dc541727ee077926de7707fdf1e0b62a2978b3cdc463aac40d311f840e42aeaac2cb040171c24bd2a30f886792cde82c55240a2d0b5155ea02f92a47b324920f7b472f62cfb46f332c12c7c2e672f4a41511d9d11eeb020d95bdd494eb1b10afb4e65cb8cd3a18b1ab40066c03321172c42f4140353866a788852caf0006c957af148522e768279d766bb4a119e0e07134b3b17b04483f7feb45239c19982e0573d9ccd422cb00f52f20ca38ce3f7b41448c6ba2ce114626f31588f9b65124cb1b5800a0af29b8cf335d091059ead14ac5838ca1af54a79fc66c069e4d580d02d09443f4271f097c2aa332716f93ec3f53445ece18c16dfd03292c5575fd19f010ef398d8883b0c48809e9710a2b381f11c05cce06463a866460624a0e2d1709197620df4f6c2f602f643f023ed728df5b3510ca1e5d8edf365572cad7d2027a699fe8fc2fe74e008339a39af39087303c463846f981121ad7f05b26d9d90f06213a43eec74d473fc05f1aff4e217626ac68c8164a426934e04af43332f456ae0dc4a68d91d46fe5d8566c6fef467bf6608ee472808f070960e69fde29c409f142f233eb405258db8a0ab222b3e4cf6972645f59fe54c38f58c3b623eae44382978d1d5f1fafb6713f1e2eb520642022215ac9db28113a070c6bab209826f78da6c7bc806a4584507aacb8c7810afbb6729d80c161b370f0d89db7d04393cafbbbd8bfeec07bf8ed649cca3798a6d3ff53c2c169965b237629b2c5f15250fa6b0b34dba153cf8d2092201ca65fc21c4d10cdaaa46fc260f229b803dcb84db241403e1246ae5c8683f2df23158841e0864c574066cdde8e87aa4faf1906c522be92aaab1d44ac78f339a4c70056c8b6b622d22c615661d118a4d8507e90cfc5fd9fba9db592fdb7f28cc277d46cde02ca40a26be1b4938d77769256d9354fdfb6792397ec2a8c5b1818e5576ee83cabf445ad4148590890a3f1e5c43a7e51abbca0fca7a35239a67e7fb0f5190848ca9f610251d238027aa9168bbb0c88592bd10d99c1cafcc5d1ac3c2c9761bca28624fe63d17e2df1618a4d70bf73828b09ffedd886ec562e8861333f5ed01564996705df23f8f1adbd53a50d6f2cc6b5b6cf4af521395c060a3988e9ff9c313b027656dd6204e8e13c21a4df0ada3a9510477637095104a833c351f2b5f557c0c5905030dd6628222ab1d365900375ede0f8a72dc4191a7abb3c391aa951cd7ba34b40c90ae5d172ba63de98dd269bd9eb469564e4bb3632aa460893c553e7ae0acda13e96958f617aeb8e12318c8ae6b23501a4490ec5f68de024113bb729b9ba0a2352036f22ed279c4d88c0e9b04b3484c5583943977d161f6171c5e13d7f849a9d534d72d013a42d5818d6abceac463d06691cdf8da77a09232b2c56657d985cc49981b173301f3ba4c077c486a2ac5c3c9f1f3b1c7855b5c5ec2d9e5764a374b3c37ce0ef5d8108f08ce4cd9683037fcee0cefabba7f240021c2567013ce973851b294d156555dadef26711f46ac1e128a5299786ad49fe6a2288fa6985423ec1914b1a918beefacac8eb0d0531e753856d51b20899d1508753670a2335b8698498666bd07aa27149af5eb610779286d62ee85d207fa239dcd1669e61833a443333b28a0d6db6f1df8071895b16852022dd4e3d78a33137749f6cc0436bf71e2d806488e206faf1a8af7315222aa40f6113895ca4511361f1d1f91b8336b2a1ab9a1adc40dae0889b154a6653ec221020cceca2cbdb752fe8f194e78e668aca00bf0ea3409045bc51ec442f8dab980eb7191b0169e492e2c56e65c929a7ccaa5ea94f7739c66c1c0fe4550a3f0746a3987b43461741ff7b42e484f40fe7e6b424c413ba644a715259262f1b71fa80277129bb760a62c5f98991b2c4d6cec3ace91d5be7831b1b47c22ac29d21600485be28d4327cbfae15dd433b2de582717fbd1b0f0538ee668f88080704ba635b3b8f76639e9981a72fe70f8d2fdb59201f9cd69a36d18b8c7a29cd1810470fd9ab19f74621101a0ad822c69c5cf79324b80eb65f120c1133cb23511c7dda91259881e6cc56bee5005d4670e60c11d8470006d2d1f530fb2d7dde4cdbb910414cbe9c842c0e7ca4deb0c78864daf616c4500583932643d85644d76d4adc7870d0db3a51f31e127a23feb3ba42a6c2e9269f5cc219fbd3b3bf63cd9f192a783259a4cad6a91929e925e04946f6185ba87b1a3282158529f7c8bc113a219d1b5c963c2e0a2171aec49f9fe5294e56e00b77844a3f64c22ad1267e90761a89e11b9358e6c8cc286337e4c962df46bb5b5a39996c9347460e82569ad42ac872e3aa620f4900b1faf4dcf256c1f8d05b81aa68a728370a02c365a705604661bcee782f75100625dc6c711c1fc99b3a567299d9c19a5d346ef637284b07ddbab10da19456b84c44c60118937078c0f484aa4d348e971a90652d5a69b576f7a55818899ca1112c1dc30ec3d84e2a537445be77781479f85d43b58a87f464bc761a2fe0a575001cc13324125eafa786e14c1257a45da4c11fcc1e449f9fd8ecd1297d7d7254135e174e06f2c371f6bb5ee1118180d9a5b8b0f74517d10e1c073ec66aaa143091f196be9b0af03f115d2b812f508765fdb62c4b6b5adc71d30ba4bbcced166790505e502559145ed681806d3586084d94fb2351b28f11b078f45279afb3c53d8e292733e283e23124549ae84ffe85cba7703b15d161585bda2affb634c699c1935ebfaf2f5244e008fd03e0c637545890440708d0a8143f46a707a0271236fda05562e6143e26524d251c45b44bdb907d001e4b3e652dd0eadab133e5a3b3efa6fc45a4d09a9a08f81092e0739ac48b4c1e5b7c2dbe9ed7493e845a176ebf5a216f051e61e9357ecdaf4351cca4d1ca773b1ee3d3e5dc49a35ff7584f2c6422bb6305088ff8c02ea4c57803429c00de49dbec6b333789149eef1fee484a34528e1181ecaec326311f070b93171fd7dd1b9cb9e56ced4024c7973818358ba22d3a26f71036784a12cb6e313d10736a932f386420e1a0af80f34ad8827083805acb6108c6f3b95f631edacb1e65680e3e7cf84a8083440a04824a85b82870a2791ce3aca38911100a769449c77c9ede91172c403cb4bdf6420bc199833b2378cdca0ff0341e8e868448e1b2a033732b1da6ca60d9c7e64dfa541e40c239d9444684e52e81426534efa9ed624d929e8b842904b37f399c1cb8a4a0d9ce766221637d4db48105855d15b20644012d32c85cd4be2a1ae14f41c4d14d6796b27c6e8c1c0ee72e0797cb6f76625fb5d1ee03a29c4805d981a7d13c226774541562d8ffe522734eeed1763abd24099fc532fe0a507fa98322a56f39a87aa6dc7ce9d7c01e005b1cfd27956a41e7aec392ed358e3b1c2bdfe98e6a990434bd17f8f12590f08cde1ed16d75ef3f8a16fdf230d1424b36c67e4c6b5aed49ce7f959be4b4ecbaf4a1a0492a7e343e4f1d988ec0b5f987ddf18a0bd491b6b4a53c02d83bcb43b4e2c29b0d2f11bc72c7619d7493206934f3367f6f3aceb3f93a9deaa8181adf7eb98983120563dfb8e190fabfb13e0600ebacc9df51b4c7db53124774de234276988bc3a7aecf2ee022d40c8ecac6994ff325176c58a169b44daf1bf1c817de23fade073608f2020ad500c48fb575f9bcfdf48e9cd2d349d3e0dceb0245b114934b2d8c04de5661d27769ca3f2ed1a118750108cff90079bb90976fab8cbe58e0f9f00fc234fd22844a42e215db8648dd55da30f4bb34a59413c384d7a8f6f31499d72a3acd7f1fbd99dcf44986e547c9b2022204c3a51ecf4fd937e4884b42dc3b83fb62f12e19f4be4319c881c150427b7e29beb552f48af83059262856623a714de22bb4ddf94bfdcb08fbd647bce37e7404cd00f92613ac329549bd15b6d66edde3d315aa933a61ab9614a7d8e450887dfc985217dc4940397f33cf0e018237807496bb64ad5018a42ea0055da5049f730175bd1b941c0a5bfc8fdb12d6ec6bcdfab46f50581e5c5d1d18abb400d2ecab95a0a3a8bd32a88cb496fe383a5042b47a68334930639b6e37a39cc2426172750828f3f742e0f16483a70772e987099877d10572fd344703a4cc9254b9b3d3c9a6748e388417333638ed93f5600044354d4ca964891bb6b0f49dde6a865745db0eb1f347bbefe6e4197d63b15794cb1f2f472714382f9d87180ec0876127fff3b637481c00120f1007998a3f6958eea2bf69ff41550a10c40ce1761c3a2f27f4862f1758280e6e10b1034b1d1db90ccc90ea558e00229282d3e4e9d90415e8198d8534fc62fd5b737a8e0d19d890640c7bea423924f06695cbf2f788e5dbe55e8eb53420107dc34fb7cd8dde4f3bfec3fd2e87614ebaddb1d41646369138acdd9685d64be14b829daa360533ee65157113d048a642cb4a9a03c4e6f18f08f17e1ff52233fcc965205362a8412f609d685120f3144d6ff88d4b72187837cc18038c55214e258099fb80a0b160c4ed2abcf9b6eea524df3f79e8fa4391f7440f3712834002d5e13918f900bfc39c16f97a6d559461e430f7cf92cbaeee7111cac18029f7799ceacd579b0d42a7c6635aaca1306713921c6916474c4f524bbbbfb686957646ec918a06e3ec08591b01c17f1d8e625c36fb929924ec4a88f95d2fea6960da11b844bbaad7c03ae5860791dee533c245d503a46b16866dc31b825b893748e14b4720224a60ff4c788a6a087e6c6cce317af9cdd1a9ce6fcd4021acc60b20c5f464573d17071ce9ad3ea542bc9534a58ebd3ea68d3dde93942adff0c97e9fb6ce7702ef360586b33d2649bf0c4ed774af614dbcd3aa09b3993ef52d81d4ccab155416159ca6110280c97eda38f7ebe57fc70b81990f4091673b70c6e22befc5da54da0f7e2225a0fba18ec7a9827d594c641018cb12342f5bea5cdd2bb2d95418f974203dccc9e8af94a7c447af6d57b935dbaa976ddfab64c962b526cc7a59f06cf84a90159ef6ce0eb3b92833d2c9491187ce4530ec99cfb4cd038ccbe0c1803b5c06c251e8a7a8656a91dded5828ec55d802a0033cfb9747edae1f3a0d72608b85921e40d9de34f7fc1907e2c1610104a7840ecdf2eab0ddc3dd64f5dc6e17eade384721e0a62ca5eb0003d54b018eeba49e23479cd76af894e3d29d6f00e3367676f0505a616a75d05f03f1a1322addbeec039dff1c1502dc133ce545177e018670890a35d64e4bcf103dca5a596397cc792f578c66cdf06062c32085ac3738b6bc9d6691dbee16eb164a85b8e7f2962aecbb9ae17a820626d07ed2cc93ce4dad485d31655a23de02740627725e3a2d5041ed484ad10cadd37a25c36d86db58cebb8a5d571651d17ec314d02a9230933531ab94289a6fbf2822c4cf062f624fbb6f1ff8a23e831e430a4b78f44a0a19c4dcfed2eddaf08fe8e00f24f4082d883a46b4a81da8934fcfa9b3d5551c75fafdcf68b5905350d033e55906591a09917c1396219bc4413e30675ec4fa6a49f8189572d40381f1d53fb9c443328687e403ba931bb9b7dc72ac89802383a68eaba70290ff2c1be6fc88ab32d62d8232db3d85290d4949699b3778ea04c41134403ba5ad8936f33413826a9b38954cf8af73305635e1f7ffcf282d2371c2117b0be0a8ce4b1facaf8d76a8a967be7770c3de309c111a233af287dd8155f10985c033c703e41c04700f07deecd3f5ba6f5af2e20624ff2c73b04da8caabcddb10022c8842c21e9d06065cb20e7f3df9c0a7e78be50ed35b56afb2c582837538970467853189e05377f77cd139b0819b8605d93bb5689f510b42e25e36b17a0721bdd13d15b129da3b82e03c9ec9935d625c2e36e1095872f129b25b2f2097d9bef24c5b2b7b26808f8315e82d0157a9c951b7542addaddfee165adf59a87cf66d84c962a5302d8aa3e0c4b79b8eaf8fe2af92ef9bac3c7c89825b6d416895e2203f88a9e025c906eaa9e33f9874c11d0c0ed3f908bb3a4af9221c87709c3318a9d250e4210bc7b01d4c2e61de4b7ddd302d95794442345b7b01ec1d6e5a6a71422d30707948273b9787d2bc16c7cf32ad2494bfc2b1ced293157830ba77c965f5f54141171553e5fa0fa10482766a523d061e1c5f46e1d20d70428461b7b3e5463c5d0f6d46d64f7ceb88186580c6c56884b05b5f0744bb2165f6ac97b93ca650ebe70a341e6fd07032f1aea03581b25b2ab3c0a02f91edfe1e7d2f82087f079393a3efa0010709934df362eaa15e20be81354d776134da862f7d3a607dc93c2bc0a1a88d45737523b8d99c4b24ed27a8e0756cdfced42d24d11d9e50f153c9914e7376d61116630add58d10787dab5295170dd1781154f80580b2296c7016f0dff2fa0a7dbba351dc2b848ade144cce2504be810d4540b80380281c60dffd1cca6f886a47a9f1ef3801e90d151d1154180892d25a063d2a56b96ac783c12dfc62413179d9fdf33d1818124e927c4ae1d4f9bf8f25aa276a462671ef4a11cf0b4222e1721ec5f64d17d41de39fd885a68939453b159509d8cc334d8ab00ab360048824d28b79da9dba7da8559b3eb2cd090ab32f9689a2e0895909b40ea807f3d59bd98169e723ffbb6c63a3c93722138e23ae30f86900d918ac5179a7db8504920002a4605612fd8585700d4d62c836eee76831f0b888db59c58ffb77a1180fc505388b343ab353926dec99f7fea957b04a1c32f10c2674ef0d206440ee1e6fc6d2dcbf7a112043b43414d267c6631037324cc16e465b520dc24379f3aa35506ae67c8a92674f8aadc5f132b52c8b358b2aa2a6c4ca2ed6fbed9df484d339a067f278c972ddceb603affdebadcbe7ebc0f719a46e4dba8ef5f5495c89a8aecf94cbc7317f71f0742d3cf276089c11b8304c8c70981b13f11108f5c60046a268072edfb7d1ea94216570d854f5484961ddb377bc17f8ae0059f6173421ddfa9c5431869b0d1cb63ec5576db45c986d59cadd423a211b924568b97f99ed748bd4009f758ce23599bc7a81fa6694ec28941aa647675d28fdec6a33daa33fe158d6e1b7d5e1330d2df796470e1478b15be05eded45b03dce11d681e5d2865f3d3d070c9d711a22bbf702eca0359f6c399e36503a45437e2b3b2aba8b0145145e0a04587b01b88a742e7d64433054679bdaa28b1d31a65557b09052df66c4165cd424f03fbc9f89ef59bb010b97d974c2416367ca3cee55266ba0e9b11dd4c8ff7b4c338b8f6439bd99862a07dee9fa1319421247838cb70cb2f97474031e082aff9be88831fce1a8656fdfbe944b4935dcec3eed5ed0309769f67aacb29c7adfda679040d8589c4ebe56cb33ef7141e7db7ff3d4bb6941415662cd6616a7e7d0b9f066475a764e58a17f1310bb9dd653336cfc9d2f9cbf2b6aa03485516574c1e988d8b509695278aac86e37061b132b546b9826839b33305ccd196cc9a7a4e55a3e4dde815a3037217750eab75d69963b5a85f06f29e2e3aa135494137e47474d32a0174bcb8b909775e84490dced7b60b44f11c5dd2c8f2e9fddc03674782744a128584e9f921f0a5d35209f164bee8cbe4a1ea9934116d605e53d536aa730a96911a6ded5b9931c5d9446c0e17ab04ef6814a6f2781f159e5b24535993060b06964f19e4d89efdcd7f0dd3177cf0525dffc45435417a00edf01ac8051d211258e28f61a76c82e3eb751d524ae292be8746ddbaed3a0f62c2c2760ae4e2bc8ef97d6c089ac09709010c72cecf29a720bf718bf59fe2709729165115c0f9fa1d4daebaf439326d1e1e142e88088f48146e8d707c8ee4aae1da9cd0e47229feff049d0601861bac4b6ea3ce095a9ce823e7ee05da1b5c9d6f7005bb4b305dbcd10014f241f3e4e5cce03946f5ee11482844f992820fa64344464c8d20148fc7dcc0a4c75fc465e910d96d8a822440840e1a8365ec8e63319c534236eafd7387fa04edce1d053d51893b9eb95ba4a40454b4ae7f468bb16a2cde7df4ce72f1721ed57859770afef63f504784e95f7da17b67bed0752750f50a7641032d40badca62f008278ebd4ee2e7b86944fd62385a1c77f6617fca5014fcc95155a6b284da8f640a3c278bf7444f5430b8731fe42f628193cbf543c32fedaea7d6bffed24e65e947f67bb08ffe586fc25d78b45236bf5be19953b926764922f2113df087d642fd3e201d78ab3abb6e922a83794dd0a939744d00b359bc2d5515a80013c00f1d36bfc00c5e2e13f78ab060b6edfc8150b253bb18e8fdeeb33aaf06898d29ef6a7ae37ededc8e31b613752dcc8a2f9fb9a4227786e2aa631714610514e142f50c6ca161c42d201f005426f9fa7e83e7d8854943e337bd156aad34db8a6b6d2062580b14238e7a029c01b31aea228a0ed832cf65aa0ab9e8abfa81a3627a0abc5d9850c73b5c0ea8602b307a20aec08467b0f8207cf2ea52f96ae1b179f4d0ab387f57405ddad42b84307a1b47cc801adf4b9918300c3bf6c1584001503a624aeb07942f1fc42e47d5e003d1c1b0f658da4f501e9d9919c7d6941fc903beb062c18c7ba0899b8316e10907ca69936288fda1aebf399edc3ee9e315a75297b5c4e10cba3c2e468a60c079f66928d038b300612ea624818da5f301eefd79369c0c4373f196428ee8205d1a5766b7c84f418464c52157a62453c40f2403e2cc290da24effd180f31f8f571a63a0c5e70c3b9c5bc0a04f1cd67d1e5c620dd82b8bfc15e8b90fb0e4585457ebfaff61762092170a1cdc6462b3aade4de42744f99f8201b5f3d5b3ab571f55db7a3ff392c84793afc6a388406ba84aebce604a75972d2613bdffd77edb7ae89fbc717cdc7c4c70c84d4347f472fb34c96abe2bc9d0601270592d8377408d2c76d3440c04e1f5f416131538f896369c3a80dac3afaf127f6fff543ef27e81cd075554adf27a67d7ccea699a6d4cc627c1c7a101d08c455367d9eb8f6294bbd579b253834211dc040cc8e34ac72b7b2ff45516948962c93d74f89ca24e37f0c548fa8911aaddc2c40991129d7bef38a065d814b014cbbf9f41e232085355241faefd65e140fda53cd3befe86058760ec2b8dbee9729620e333239228965c7e2994a5cda298824119fff613a8757714c5347dc3d29f76e4558ee505ef4bc034c298f44ff8453398c0b29a797c3b1d8bfb34b8dcfb0a3af8041f77f35f0eb709f0d850aa9fa53cd16d9bbe45dd75ed7572bd505f36d62deef78171c2690a5a6aa2d882e86ff5135b9424bba1f511f73d931d9e8d224943d2e4f9c7dfc78ba1939dd522601bd80f254595d0a8ad27b6735cd8537ed3246abdce5ea074ff196e66058b2a1eb2b14ed06d75f3474df8ac7f4fa8786ed7dfb75e3c08854cb73f7933e30852db0a2060628579664687323bec6a4d795a6dd072dd7a281aceaaeef6b996e25563287e016e21f2eac476a0e89a37da5b24b1758619ca9d4fd9654a215cc8efd61dc49314650118028c90468dcb7dfc70f609bd6cb17e80ef929c8931ed5bae485d3f250b4c8043c8118a50a7366d0c150cf35b10f3206217cb9397f80406ae932e79031221436ba58290b89de280f7c0aa141da824bd7e1330bf7d5198575d0dd9857c09432ad97dc30db5ba7d8f719c8ba39c00614f10e9e445161f849748a51e908bdbba90a81f0434b2bdb8a20f5f8a22262ce20e4e888fabb229cd62b9770930ee4aa44dc07f650da0a538a85134be03ff4f9c5723a31c2984f53d6a2555f2b9880bc28a5707ac2a88022613fc1e454cc60c08726036dcd16a694e806ad4e7773bb0538fd816a997ad295dd4be28dc39d07768e1ebdc827844c51634af9b7fc8f05b82026849b2267b604cda4bfa840a843f66769aecf08f482951f3a21b882d4db971aaa5036eb5de65ca0e4ed3620b9cb0c6914913e2ae6cb59628106e9a4dbd8de4bccedbb12d9f540123a4bf9c349450e0e10272f2381f262819d12c406da1563c0db61448db497552f74d61c18461761a0976d6f7ab6fe20753dfb5c47cef784e0ad5bfa62020cfefdc6468458560e114484ef22a6464075ca0b2591545292ed3e30238543f3b08e30a848082cd4100858898b14dc246d236d97d1050c520fa7627c768d8d682fdc63db9ef4608b2b0d1008074a800298b5318724820c84d9c1e90eb9f0f8bc574e1691f0ac33457bbc9bcb7149195fb3dce492dfd6a61a31ce3e3a069d77d818a45312c94306b23dae1acfcee74a258b701d2476d09c564390c8b926c46d99999c0423fe0f669154e1a139756be4bc353e73b0d0a7703a47035c4a80f8bd8839739e22e2b830c7c1cf9cfc7442fc01383f479e20f4e8b4809f3f11fc7aa3932436ae92b004676c74a8a03fe395de86bc2a30179db5bb4c2c8e526edbfa83a7e63e428768dc2a1195380a8ea4b90e3e77576930fa008433da3b1d1d044fa7babdfd3020c2773bd2d93619a7b58878a5c1d8d54e9081a654c0e920bcce90a233d474121d3b5ed22901d5834128a392e3e30918cfbd8f756a223f5303f61685a0fe058cdd27c4efde1dd77f85ba1074c796df37b3ab3462c24c9012e08fd3ea1b37ec981426c87f965f11879f0cd9076f2c26afbad9b0bc96d8badf2561ba6b014a632453388ff3148d3170b60e0a7f620e030fce2df18598981510c4b05a122dfd01cccfa0d1eb3132a7ce5812869e7c1cd613fcb78d779c551cbecb4288ce059d4e4e8720da1da87c69d366b373014e05b69a028b249a183fdac9dc71afa336fc1aa0d855174e09b06318edf085ea832e9ba52380aef1b5021c2bf910fdbc7653401b45b1b1ffd256ba8522f2a80711dde4daa7c66963926865fa6f699026e83c37d55c17d734b3f06dbd3e53ddc4013d318bc76dc19b4077b10f1370a548bc3e3d6be3b458b9ce39a1277ce8c825c64b1d8797c117c2b777e9dd1a3e1652606bbb9f07dbbfdc0645d6dba36b204a40938e7a83e33ea3b5f98304796c29ecf0ac273d8a3fc61894ea38ea21da248d4172ea10ada39d9bb751fe1cee289b0804a5cb8506888b3f92202faf492b17697cf299528bc72e2a08088df1b3a800f83e5684ef2a3d90851a935a2c9eb0f0c970ad6f085db0485e6129396624d89f63704e17f56529219366ce7ffda18c4f8d17b14bfee5707911a72b307204fd9763c022f6898669d6b14a98e12f08c7ba28c3bc18c5a1d53d14904679f1e46d0ed2d8db733cad26d1d351e5244efa7068c687143ebe9561b8fbb12b7aa68af39d124b537b677ea76bc60d3cfb474811b453501ba26a9f4be5935cd9b3795f5fbf7575caad2feaec345a7d0e1831ef7207e93714aedcf17d125dd3f695376a9a7301e08984ed2ee38e76c2802b90381ac03591fc175d4f8572353bb60f217787c7dccc5576efffcf5f63248596ad3ee818d6faa7e53af7bce91f31d630f9217cd46a9597d4ea87a5ddabf17a0114d6cfde0df2b059987d36efaa4c5db8db0fdb8a3bdf6bb7ff40ba688e4dc08d7c45892a523281829ee882130ea5d4e39e602926fe8742beeaed2a0a1d2c90c5ed0f5716d526943e0e1d98ddc1b8f5b60b3d9a0c5542025646a6d123dc19f3440741dd292a092b6646469477ba2826307c0e01f2aefa2220c6eabb7452f65377401a89a1d7f4a55cb3ff7e20eb92d790250e5f468ad37ae0c6a33b79942455f7a39a8ce01b22cd31b0a15c4e49c4d70f78af92b3adbce15b7122c36fc50054c9121cb95fb703588da26a4a1621e1d5643f7d90f63b1266725f29e92452f7d627e02b6a9788265e83c2a9faa3e6bdbe69c38716c07ede97ccc2417682403373228af5f9723d5e0f6f45e8d2acbbb178a07927d0ab90f5ac5df6b5a67b410822e74256f8a44c27452a327d86cc86ac928ff8e44854892c35d515222787bbd2fa87c56738ea7f5f8ec95f63c109709fe309e9d21b344701f18825abe943b2b72a77fe2f33730baff222452109b50807822d642b8fcf64dca9df9aae7ae2914501b65fedb8ed13b9a166cd7788076d9b3229933ee7e064da6cb38e012d6908472da0611c6dacb1b734ff7feeea0cdf4d9bef31b7e4e03bc770d5eef722254a174aa5f9d53831eac5917900996e63eda06eae2a7cca6448cf7edecd0856658c608c0f3da4a1358d334d4f9d07d81266e6d0cb4865a44bfe7dc4392218ea4e07e1b62ee2de7595cba898c5de5ce1dba76c3a3f0a20db2698baf0687a08ceb3b9485dc60035c6160c2411d9ce7f0940d86c8a391b1d932546705b68bb418c30d422b8a68ffd8323a839a6683979f31d95f19929fe4a133ae262217dda6f11504c7f22011de72fafb2e5df5a0cc1785c3378c9ebcb563a61c56149607c7be1558cfdd968e1b340f6f5a3c0be4b3477ad9a09d09f025feda475dde6d22f382acb2afabd97b097bb860a46041bb892bbc3e1add8d662b011824f366dd5148f42e447bfa213478c41dd6f2b0d247fc104f4adaecab44ddc0ec035145d9ee3dc4404d75063923cc746a8d04c9f1a48d989df3e3c475e4ee2e68b72c22f0828e56c99c14f53939b96f178b17f8866f619c4d9053ddc10c6833ff0e6fcd1495c31bdedb438b1e81222e88481799d41b5c9b0eff3824a7502a94e692d9ee6139482aa7fa508e48e6aa20eed4c952fc0ece58efe032c12921bf59d1f6cf40d84d8c5ddcf52ec8644217df88431b212b015c450ee622c64cdac37c9ab1b548087848754cbb7c936796571307c48b1535e89fd2e58f8249f93c1c6d931e1b0e8c2e822498a49e0adc3722fa23a249fc0933fbbb881609390deacfec077753c8fab01e021763d945c6f62730dc043a58ea85ab8608bf9652096ca7d3a5c7d00421f0e71a61d61241c2657451436193d273797667cf17dcf255733f88d2e27a6ec8dca5c88a860a5eaf6d5801ecaaf9933a559459fbd8fe685e3b356b4b7599909d13ffcc442410cc4381610cd49150b02346d79b97d0a4f34f7dccd1d377b27f7b7c64a159be28129cd5a279b9226ee4854b079188ca737ba461395f6f243aa80b992c7c56024a1a017d99cb8e8772bf3bdcd07861274b1411705bb0dbc3ab369086f11bdaa1118929603200010a5f76d2bbeccc526321777d251b9f9b7a513f2362596b93b25a4ea2723419e8256f706bc047241087f030a3cd8299a95bbfb7ec5adb4037687aca5c14e88e3a047a8ec3742b367399830bb1c8951bce310139b831d52de51bf215672f53e47cbe208243d49ac13455993b9a420872e8a0a8a1680a44f825b61f759608852b9024abd0345c9d03289ca4b9907b09573422067fc2917b4413443f40b5e90aae5488ccc0febe58836a015e59511bc0e23bb6d44375f83471ba8f146872ef3adc27096f8be34c37939099326dcca450c6eed6cef048f33e064310a946e02cc1a8685fbc9a01292bd9626c2e59846a48859e4e05683daabc65627e2e3c2bd5de471c12e04e945ce3b9e7623a49af816a8d3e622d98b91a3aff59b9a836b490d40c0f95d1a1f30cd16b0a203c9db5806b94437bbb02fc74de9828ee6aff134a6a647ffab5a965795c0846dd98325025bde53ddaf2038e65899da0439e293cfed5cd88b56e779ace5b403e85c5a07371f837606daa6adcf8e73e5698cbb694cd86a941d1a3df83aebfd0746498e7724011305deb256c081610d1ed5d071de90c1937931c673875036d4248b8aec2c1abb09b4a5a321c7256fd7d8570036debb9f1359887204abda9d853927ca300bce3262038d976a4a33f486e95bc1f82992e31fe5dd700605d626262d4ab013028b12a3954db650891184deea9eb0a28bd4f5be3ad88a0d211c37ab5d29aefca456e2758159fff9dc193d36a9fadd7519620c3bf3c7e0ab9d589daabd80741153ab415d323386a0aa4b481af08b302034731b545fef51607630367155a2e7cfb4fb7d096808c98b526c3e0cd7f27125cd38bc24ef48d0be532f4058acf2e9c2d2e4cb4439aa08300203aa2ef48b9c4ffc7e5475d03fe21a68684fdc650aa916de7d851babd9999310409e0f15cedbaec864bf8cc4f2afd25785dbf7aca062f02e8433311786d75be35346f19cb1c1c41627f6286252dd16cffcd44b3f05b0b6f7aca3fd7ef3b8371861383b7efd05821484c0cce6f4e7be6a8b497fbd65c9eb485b1a3bcad7092505292a9f9c38f4a6f0e7a88f6edfd38916c00d52a2fedaa931c01a4fd3f7f86d958cd8301c1fb093d65322778799eb28108c4903a6b4273942348630205ba474640a949749cecf3b24835c41fd0a93da229bddf7d0bab8873c35558dbe7e642743f63512489615c9f72ba825bbcb9d7ea586cdc5dbf1a6e072c90080a7a820509af1f72e0dedb949c37de849caa18238e9e2967f57d971b5fe2b4be2452c6fc89b2339fd10f6c8d21ce49002598e9cdfaabe475f9347292187c8460e41841f4cae873abfbc85c02d6b3085a34d9f7286fe7ee243773eb6cbbb6ded2d0cc84c12b473f8afdb50c2df47b53f0795ec282fbc0eb9c785a4863ed7f23cc1ae18742447cb2b71ef3cac9fff1ef6b48918fabcff4e434a6220cac3f2031846a05895a97ce3c4c63672e3f00453ad5a8b467dc8f7eff1b1757155e414b960e87979e300fe4780ab04b86ba42d07840c32e0a520409870507b4f4fa93da0f5236ee49c2952838520b6933fbe5501a59e07761d0717e6e00f0457f7bb0efac00e3c7980f61371ca5ceb0136d207ffc289bffde1994b3f673676a2a991fafffdfbbb2e864a7c4870ca70780bf7a6b3f755516aa0f0adc291f955bd38a6d88a49b6c6a42ed3dc390c31d5d9804708b02dc6e35f0c1cb57da48088dd8e91596e49344efd4969604ac59a4312245668d9ac503d4d521423b56972c710c7b88232e5525418f37c2719d943cd7bfc69cc81bf7c2dcba165dcf83388c33ef15f5d08a048780391627878c714211b71b53176404f2cff43b04ea6c3723240b5697dbfe0cc9a3fac6f67bc8dc97af0d35eefb68f6b93c0fd5e25e1843c609c1a9c7ca78b5c161ce4df6d49edba7797094b489e9bd80d37eb20a461bdc09da8594109cd5d5460aaf5bc8f61a08565f535f0bca958c002a57ded9534d71d10f2f49980fc2728a51312b0008927c581a687f6cb7d0e3cb3ccb033032ff33199d8bf0a20de616e851034ccfd51f8405bc603fb5c43bcd7d68834d3843577e55d07773d4583440b019f37cae21d2561224bd92f7a35cf481daf3527a0ce4379c45530a8208212fee940ab9c95d6032f5a8c0d2867f4320d48a2833fbbf3476730c96c989c0128100c01776d9dc7a4c47a37ab04c1c9c36042abdf9238de87089135fc7c3e609896cc036a556cad9f74454548b87461ae584772b7b57138f07a22d15c19758d9d2909dced1bd4bd58cfba01b2d7374d4c1104dca17d1ae16fa6f871d5dcfefe7707e05be8bfbb278567becf1b544f73b56c44d2f542342c408f32fd7c609455e3c998e8cef10efe13acfbab899d3b8b1b99ac88913eb50e3b60372bb26eb2c040e175abbc904971ede858f5aeba798365028ce74c3a7a22e552428055db2dbeb883ee37f4a04314c37c6d4c0741c82aa28694cc0849c0adf0eb8cdd9a322ec53a92a265f73db1dfab00f66f87bd5d82858a02e7b232ef6c33000eee45780985c958de9e4979ac253b60d7963927a8def81c92e5d47cdc459ea596c6141c821ba8a5e22996f12b3299ab799dc72083b4f78ef8e0cca79d8826c1a781028585f0b155a2a7ea8cb8329ea259c5737f7593b1fe4d9a96dfe07f59654f5d130c343ea53a38a9a9301f3cc596f853796915a0af109e4287b7080e8b37f085ac39a512d4f494507adf258bb9f1f7a3a5d4befecc96f8fdca736b0bcdfe406b4ae19c43de01c097be8f4cbfc5f04992452c06272ad1e500bc63d593ba02e32fa4cdfff4a830dd6e88fe326487c3704c51f42417961f6b411585177ae0f9e7be13c7121e3cffdb973c3f1d3c6dc559dfbe16b2dabbc9c34b5c3361d098c6daad4cd1154ef0225e2743df24f09aa8ac4f2f5a55c95955f64d40951d72163d96b200374a4c75fc8a41c4a71bc20a1751259d95313d1693858192cbc6f9c26de0e544376a45e2c64b5bdf88b16ba374b8025aff1bb2c7572ba030bc2f771ab3bfb226d8537b88365074663ffdfc5157b83cdc3f27978d02a2a16200d26e8c83151fff465845425fe8dd86b8c7773d600ef2ce50f6c75f0badb28f4912a21769b619df0d460f8bd3cb9e536a6569705d838e6da6921b0506c0aa65d5982613ccc04b104cd388d06af82932414a7081be45b070f18c2055d80ed69f5151494eb72a7e66db3f1660791bf5da4fd4938466a26d83aea3535a43d6ab30410d0cc8f0992441b10306f0bafcf4599ee9b7804bdb1e4f6ba2404810484226600775f4bc9d8370e46876ffd20078f1b242231367a04d3404d92ecdecc2eb643190237bab89fe533f293bc62c7c685f4dad40101cb784a8df534068b3da274458cb0bb1e38d678b4061e369a50d4e47629c404240064105d96882c000578d60a003c76fb2b6f63a86e9ee405650c0f4b833db812154914550015689d0988300edf7f75add8ff6b59bfcd087ff10f9a72d0046b6abcf300ed2795e7e9f727987aa65f343b9be49ff7c13d752fbacbb84a1386acd1ba032e4c2e620f6524a95235b0e84a40cf68f2e1a4acb04cb7c1124b67bcc470e344089099e377074af8bce7a6b7b3c68a5761df7c1a91003996013d1be219ae26bdcbf79836a44136e376e5313d7195f8d78f50eaec287b5e84dbc3065920d55c2387f9f7beeccef202b3eb8980c22feb61ab3ecd09360afaa1e26441f50617c5750b1226ffb140f4bd090bc1bde405d54886540993af696fe80583de1be557a64cd2838575a99a6ceeddb559bd7b932354717830c821d9484d510f3e2f9ae9ab8abda4a468c94f37072235ec12b118274928ba03358d97ca80bc238fc3530afc7e1af805362722dab86b23ed5ca022c27e44aed81d31ef1bcbb400c7a99b8300d395f442b3f77f16377e58c23a250eb22d2fe26af86f5e092857cff21438f3df3617c870e81742a20e031fb21eb438d8270b8e7852e87511d52b40a7d54c8e31ffebcff6a90ea261297f13f240e6fe885cae1ffa29810638f99b668ce9a685e13c814800dc7357e97399c546813c04cae2ffcc315e531e597013634426c7344d2dd748868d617e6153c3999408c46792e3f12781642c09c3cf2a59a0cde911fed8540c4e97d58de79c3d243925591e738ace66803e0bdb1fcaf6b00501af1449601587e37d12e5df09a33f7b8aaa79e948ef0f148bb8661fd7b1a6b5ebbe03600c5c63933ffb85fb11c11a99daff88dee9f151cbf1284c9197ef55068011c5cbfd2af3585da0410aac4f21d41ce2d0e3ffdc86f4881fee241757f067919faffa27d1c6e146a973c35368798c9fdd64ecc506cdfb96066fe80efc0307f30156845354474b89f41aaefa3e03a4631ffad5358ea5d7f1f8e52b76f4947b2f803a7742acd202ac7c140a9bda15e2a8d565cfa74cd57857ec090b9ffd415abe6943abf93e117a63f126b99bbf0b421dfde6bf316bce2beee03ff9e03d0492b19ce8de7f4f9608673eabc7e580b10de4df6c7198d4fa9d7d1730388cfc149527233347890e039d961f888959e7794eba56e106022a6f4095ff2f9cc8233cef8a51dd81637bebdc83945e70992f1ac1b2a46705f0fa22652a8e69b4a65276ec41416584602aa0ac41cc8423a5f19038ad67463c8c9bb1471aac683f062e92817b24c569b42187a8d06e34fddb527f1ca085f204ac7df615756f3af52b1019ffe330f44aaa80532198e4f91bc43bb3c3f530844ec5c42388c899c410c7e08823c74944da36320d9b56b924da3cc139c02d944e91189b7d71c0365becb9ede664cc0ce49d5062aa7576f0360d962444747317ef980db89f77b66d6742bc80083a0acc3601979d21f8b67406b099350147536908c7c797db8972a6c75f1a17399200a4a8d2d089301292f46fa983a2523b17fc71a23d90b38f9059ed90b8c1fa54a71cd59001e77ddbe27639434167b5a66166ce11308a9291583758ce1beb877a652848ba291563521ac72348ca29064644faf4212c7e6d0aa718efe2012717e62c8b54671c74dfb8add90320123bdb50901f0eaba445cc7c95df300ac6aa64cdcfb5bd21b2bd1e6dfef48161d9b039e0971aa61e2883a92e83323c18e2923a7128ffd4763caa2fc0beb1bf64272d1fef248eef8d6aca165e1150b1b3da23e4b83de2492b0141420b4a26f1cd1958dfcfc0f8d7315f569e9ee8651f6e6277592bee0adbb7cd1c3dad7f468ff745952b368e17832c22405f0b0d4473a505d0cfb62f6b5f376fe994de8d8eb64299787d0ca80be8c6ddded1f0d76df598065e1b8cbe371928ca3e551a60d5478d5caece54f834440e11b121ae07f6106bc32bf948bf1a2516b0ccb837b7f6405eb8e597ee31a0248b9230da68b6a57f92b303eb857ae41f6c06d6c176d931630b305f068b975017647ba6306706d6c1f4aa98d32679f09a661acdc1ec60609c8caadeeab00dd8c9197ce879e48ba6c05ca3e9c7c8717ce99af107312316e8e8fea6ce61e6c10e0de0dd38e5dabfdf10ffde7a9494dd03effac68d9e40fa9eeb0cd410c3186d126fcda4f77c95a26c55182a365494051a0c7a46461c93331905cf1c851c51ac541997092554fd93e4883ca29111c775a63323293488e1703b15e3a7721b758555769b108ee204f64a94d95672c930b4f22a8299113cb23501b8a27b72c0f9ef2053d88c3c6e418c1eb4378f200efae682dba25827eb2521a01e9b412de35524a1fb1b5393421a19cc26b72515521d2e7ac72e9a42fa477163385703a64a380284a5eae7ac0333c978df5cbf23c07b0aa21ac035db763224a5d5a1b6d8f392287f82b71667f49b6b948b4d80139088f1b96a8595f1dda5baf13612a7f4f58fdd31506781468e9a7256491d08e08f67e2480d19b99e92520c5081df67eb53d7cb4afd4e1011af3420eabc1efdf1e6ef6ffb1161fe2b523887729840149d27dec84711c5542cf530c9c792331353bf046145ee41591adb4cebfc197b55cad207ee123bf01f928e31d2f063ecac733dc84341b0a5d104edbee9b20de985067d098a74cd02202c7ef0b37f66a61f029ac182f94f3c69f0687028c43a9704c216d82822b4de17af5a19e9937417c98497635c840002b0471a0a32b56910183c1168ce5ca30f58ae335c9384ad07238e41ee70824cbc8dccf1dbb7389b86fa720bedb35c874de6b55b7486e798653d3dc394d3ab2a6546b1e0a03256cc9b80aaa79b63f13e7add14310f777232c442c57c442ce546d449f627855bfdf2b9998e15a5b28257c04c616fa8238caa610027c5d889d2ce3d55644d06f12d9f799c7d4246e4f5e10ff41d5c7235106e993ca0140da254b036ca422a3b2494364643b0b3141fa9caa30f77068b3e4899d3b90876fa5bbafdaab07968b36ddcadc961017e1a37031031c0e35ce1e26630c54a9dbeab97dfe0cea9b334cd834d5be1eb0e2606244cace2ffea67bbc3b86f8c8894553b52a86b234155deb24ed2d82ff296878f26e0e35103e1b9ad0b072f39a97ba425118aa5ba5a9fa0775cd96de29323a5baeca4dcd320fba952f84a5fd0417385ca28f8a01edfdaf250a07cfdb9508cea29bbedf26512cdf41a12392a8e461ee36fe05a3485c635cd0d40c4955242a23c7ee02b40b4d7fa14fcbcfe2f566f5981c29c458b94adcb7d513c66e2e4be5e124f04e263d6dd6aa7ddc7b61cecbb9eafadba8aff0e8be3efa82f5063ecf2b861de814941237db554f5545979900cdb2633cdeb08bc4358e04e8494d11bd4a7ef3fa6317529923e991c4bc80cace4fbb1d376e170da9f46930062986ed050adafc2ead92c96b281bd7a834e3f827e7f521c4ba88f12c5d26bf368c748bd00a31527416ff02929ba3b1468215813e5c56e67565ce8f5c792b5aa09d1446fa66f58238b740a30e26c701d91e056c08283cce61db4381a0b1e60469ed7d00b65ea22dcc027afebffe8746f9076ec959e38b1f2818a3b1e3c5ea9fb4bc370d15b5976f002c88232c20f3adbd272b604da91722e668d5636d57e5964f4792b12378b0d670b06fec4b72c83b99d3159f3062d38e6d834cd3fa09b8b688dddbfc24d926cd08aaace802859631c43118972e16b708b02f4397b8501333b8374d07b6175622f3798e32e3b70583f51930e3498e7529292026a419acc60a8d8e17bd6eb1e90a6089580e6f4d4371f796749193112e40a10b8df3027e233dd01a4b66b0c572e7be9b797f8d65e7f877d8a688d157de3f36d85a29e19cc64986ebdbcb55008eb379d176d757bf528270c0f6b41e9edaeca341b7e68a0f74ae0e05ce502af5c275a56d5cc5fb8b77cdc87aa4b61c586a985fddb29e0455453143e934363d9fab03a74a80bd31f09f04b37960fa05ee8bc00df1cdf2c40fffcd5812d438dabb2956b4b53851298f333d9ce99aed2ea2f262405b6473b0510d5e4fce875d01a074501daa94c57b91828ea22b4589734654ec46097f053f2bceddee82e278ef26b28a86391ffb9bd5750842a00d51b6d00408a8c850ace1357e42727ef345a66a65198db9fc25382192387caa0912c097ae923e4642c4e6af56459f017ba28708587585c7786cd2e504d7e1a8f925e28032992426c8d47514d0bc07334b64ac929d19fda5aa3617f1fa55422e772b47b234525767a0d26187033e38f96187b87b7446700f3ab69fb70197c2c36b65d86e87e491c951ad777968685c155194bac29cd67d01725d63bfda30f9c69ee2b05432d54aab1f0ed0c61b2676ccd117035ac19c99ad028aabc5920fb6f3d223a1dc1ee6edf1bfd99745c4291ceaf028ab56e183c04064c56a3edbd2dc607c6fc70bed93c4963c1374762847892dc3999434e4b7049dabcacf09beb670c36d09076e6b66496237b9caf248e72556007cbaadc408bac478814e799ed7cb285f849f9d2e7c7e75415df2e5f42465d2b294f7d32671872a2c21c1f4e502057e73d56319c41fa6eb7f6905b38853e3458282897e15e989461722c9ef35f7182be46d04f8f8a9b72513e71d684412bc591e673c08ebd5346228a140641453348d3182278e7cc65392f65405b96364c2b83cf998e25d1512e239e1a7e23abea869217ec0560f1869f1ae2a27b1786fdd511f1bbe633882542f2959b17a1e031bc00bbfc4500035324f62987b05ddccab55dce6ca37e2342a6c97a3d2bffb3f58d70011bc731480110f9ac379eefa500609f59ccbbc195d917a20a48c83ee2878fb8c2cf7a3c769d6495e030841538777972f57b90831b3c33bcfc2b6ab22038618d90e318d7c01b673188aa5320a8a7719635f5609f7b0b626426d88ee2d49f326850094a875a199ef345d78239ce12aadcc5079dd8a2a0d7a1f1bbdfbb0d84828e2904be96a396375de4a58170a03a5fa6bf4c11b4fc6bc99e8208ae96273b5b58bdf5c977e6d833f1aeb6866e3b1c923831339f76e673eb9a1dd8a36b2089201ad8c4cbcae92232474673165efd71f534250f3474c980d93ee909393b8513bb17eca849fe41a0f153cd62856f5de9a2d98c49423481809b464f6681d39925e9372ab55afb11acc981055f272f8c0eb96e3d89b1fb302c0616970e0abd58bfc974b98692f5ca65bf64be4c082b3ac19f8bf9f87e3f08983d38af6c7597cba8865b21d68ff30d97c34c9c8e957a7e9df6b8bbb24eb764b9b452033aa330ae20dce07882c5ab53f0e5e8a0807fa97162e9965b25460c734f2c9f89fea5df8a84e53fd80d1f8328add271227328f8c5a63f6882b2e1ed17113f3a6513dcceededc2e9a11df1c4170ff9fd4be0cf0e99e18f08cb86421d2927c7646d8fe5d2fd396a8c070556288d669b1e1b51e428c56f5820579f0e8826210422035239a080a1a7fe893e9165ac07fcadb8fe6f96bf6064243061f0a8a8822d33cbee804f313915f4903874032815a2d49d8d2a6e35a01d72687cda576c9b8ed5b190d8be83c66e2dc187bc523f64041e93313a5c76e16fb4d8f4291e33a15c626a2cc719c6cc8f407ec9dcb9de7f7603f84f9414a3af2d481cb130ca41d3810b530eec52dc5220e2631c231330903b987a58333cab9fd48186efe058fbac2e327a3b092308e9878d6778cfa8d315834729a3d4fc4f93579c7a80b80234c5b57d16ff568f2ce3eaa1f6bf8aa53a3912984f9339d77002a3af52ff2c8e16ca0b7609fb5f8d566b8aca477c51866630f0a66e4226e6730d1a5b530638abdf3398513a219edbc19ccb5d08cc3f9addb9a37927e9d6120c1ff34d97b8da4985923405b5706dabe87080dd421cace11508e9f85be451ac5a07663c18197ff25d46400cfaea22a3cfaa61dca7bd48e50071cebc8df346f94eeb2941e7c4a99eaf1413160ab49de76c8f41685ec34b2b85694e03d6893f4ab0bf8952306041d7414ba52288220855b1a17eea810fbcc9cf9e32fa51231661b798926c2746d4c6a6941d8164df386601b3a615fe30ab6384859412afef5c3b319f8f3731e6551c7f5a371396f6038b4b554881cfb3eb8cc3b4c32e72aaeb6f1f1cffda861df90417612933c1b6a1fb8072d83b8cf64d6ab61395805392ba4111cdbee60b591dab0a0fe637af4017fe005331c861343c9b03ea749e15bcab7a79d2fd800a73726fca4d0042020bd35e01b097199b0810f8ea5ef28fc7c15a6b6daf521e612bb8f52963dc240ac60f94cfcfa269d887e2dfae7b1697b2e594b18a6cc31c8ef75f3f3361fb2eaee8e2df6af8fb0e67fcf4c867e51a47d72d6043c38dbd28f9ab520986d93fa8a16af7cb548309c55e237195f1221cfbb70229c87534112d98f060651b09b5c70bab5dbc1621a1cf49904590081715412cac94363db6a0e19c0d67269ee5c3580fb7f0016548d2c5ef7309cf28e7b3fdcd7a54cf448d0070a4010f590815c846c50b02a07e006fa40fed32d680ef295b40898d899f4534a9afc1889a87cd94a59ec6c48defef385eda0cd850a6398c13d8a8a3cff1d4f414a5afaa31efdaafd27072f9f456f9f1102830932522f5f992c8925cf4763ad00a042905a377a21fe01503f30a87aad66c55a542eeac275eb23a355acadab3ee02b022ec0937a3d062854a26b57fd65b6b9a7935d4910865b7e09c9a81b729614555a1f97d8fe82981646563a39e6f9f479a898938411bd861bc5ccdd19a60ec8c6859235da5528e5361c20818446a3c4f2176c219669721641c22db50a0f6883b5d82d33f0ef4c73d96d7562e6e022af81a15aea512fb442c5140903b7e24864c8f6554bcca8df495b04accce70cdbe3dc6b025e2152071f03795876d1032040aa3a9da202f92f18af9ecd7986f0bac82027ada1f36483a9dc6fbb52dc2e4c4334af01d247e28b136bbf2b07e19ad315ca9d7eec4c07443335b8b10a60ff58b70f8de156e8464eef41244fabc00bd296a8f8f0745df93a7f3370bb407c1a9bae4d2e32b3c75e1afb5be2ab6d3e7c6c3a0dba0ec0dbe1f7706139d308563c641eac5b7db902692292def26fe1380c63e566322fbf65ae22906223a0c390b03518043e943c46e209b836dcb7edd50292a398251976b96414f731c584624043d116a7f8075c9e4791df164266e9b6492e58564803b215b207e7f3a374c103fd867ec654941ab4136f886672906a0c61fdf7f576858d20303dd7e014ad60d576565c41c7ac9b7335bb42c2e3d4fbcbcaf349c053ddae0367570625e4839fba2174095b0c968b6ffa1e84f32f0fdac4d788115fc2e1b228ffb4fc676521f09d2a810d8366e97aeb04c02779e94c0bb920849a3d9bb06d47615885084eade4677bf7e033e4f0be5f5c14e3d3c28d236071cc3ce8d343826c38305432455eb2129d98b3e6044678d3b5c3f40a5e0a03c52ff2b206b3365dc9b8196b18660c71936c606cb99efa3da9363027867ba5738f7afedea2ec4b0c9f060102bc965a0fbd31efe643954629ed127d9c2f56e014718b9599e718fead709a3f458723064797d16ae9442f4706d83b7ca4443690b2ab2b9fcf6b047812e32803db2b157b4fb6f6106483b8229689d48624d179df3890aa1d5c8d6483d3ffae285e4358e03c8d70160f2f0b4aa255ae68f2b82c1c61746c6fb83af6a1b5e440e2e8588c89e3fd54b5e245339256b5d4f35623319491cb386e9d16993c062cc221898c890fc8b6d454805fff18ecb26eb17c0d0bf0ead937da0867b125781a299ec8ccb258027b38f72bf8ceeb678c3e99040642c28e9658e77bf668a06de491c2a4319281d881bbd944faf1ecca7fd9591a18ee05c4a1b86d2e0dba8da107387bee066275a64bc15ebd20b4db1aa9710cd4cd108c318a71ff313527594f26638f9c6b631ab5eedfa9da00d267f34040c1e1d8b26599f26235d7d126c73316d0107beea2313041bf4b3f588ccbf91bf8fadb67cfd6e6f3017c8bed19aa0df40f24613d825d4a481e4e6dbd6f48ba9257bb6f3f215978506115c7213602a89be197a3608c680b8cd9114e0f1eb25433dcd4a1f4daec3cd39a83f62edaee450cbe1284b003172e9e2e1d7ad854be60b6865f4a8335d70381196739ad15105f2106467c1212237a77b49a9805d1b6ddb0f6d2fe9ce621a985085c71df3b4357dba226daa480402d23d4028c0b48581c7c1da45807d205067d3eb100bff2dd26e10fee1b04966411174f0f6e0d8d3be376d27b913228c2b6aec48e7cc210e4b0ae35f9a429de563de2a371fb93a4f36824a437045cafc52532728971ec5bb461c281e87cb8f56dc3e2079129624d69ae525ab9c3953ee7c56422d6645df32a4b562bf1c84da68d61ef3f772c7ac71860af2d15f13b160c39069c0164f403ca58bfaa59200ebb4a070d854c341707bf3a0476b5796faf4c957e46bd2c81b55a7348281486808004fb94e489a36ac8ea073317737060407001e4c75a64ce15b2643d8560ad8cc6d7c1d3ba7d7f8a0917ae6d92ecbdf7de72cb2d654a29f40a7f0ac00a2015f087cb9fb242e5f25beb71387a96cb6f73e566dedc48ef1b8f4aa3262a8d37712228252f5f94bc302d2135947ed251a0f493a6d246ee74e6f2cb27a7345045f87dec71f9facc91bdd049a9f4a6aee7efdaa59c33cba836354d7a6b33a354d36addb8c971d29bd3eab6715cd779dffc3ee9fd719de77d1f08a6ecb4567adb0f4ca5acb5b1b9c1993838d21bc77a6de3f5cd8dd738385ee7e8dc98376e48ef1b3a1e73385656a93cc681c3e30c5673b592deab71de9837bc0e31f880f4ad18781daa3e202aaf431c1f90be3683994106d23b830aae846c2daa2ab812526fbf07ae2f398bdf86e715eee1f73e40131db7cb99a5681256fb80b4284ed66a057d098ab3b8dbbddb5dca39b38ca5797487caba1cd7759e67c1f03eefb35eed3ceff3e814ebff7d3f3611f4c0efe7c60bebe29893ca49a97c80983b3936366de3efd7c591021e9d62476b7fde6cb1ac29964eb1f23b1b9b023fafb399d2c6c6c6c69b6f80e3d055a5ae8ba305eefc6ecf91f2fbc9e8a4d2296dce001c872e3707f2b9675e7fff9204c08cabe41bd25d173fedba3802b95344ba19a7859d53497675d2b0d2c515dfbe610cc1658730a0d8e88188024e62928a5480820a48806b74d658777216eb185df247fe4cc7ece12fe6cfc419faf838ab7538ab878fb35a8a0be01e8eb253c7ad97e7f5d15f87b37a38ab75dcf67156ff95610f7f4d1d3efef267e90be01ef6f1bb2e1ef1ae55715541c73cfccc8c81aeeb666662ffb6b06861e207326882f8828408081f52e045882dd488f55bd5e46f76efb68bec2c74df6ffb55fddc5f3980e38965e9d275d670771108265d762872820c199270c01145325c41c512359c11ebe7da51fe61769977f0ab9f99d9ddfd9bba39e79cd9f7cda6db40d92b52f07084093f7881e202505030e60a28b0b800c40a9888410a1a4660e840868816581441b4469a24317d458e6b8575588e58e7b83edc63a208ca042e4e986082bb9cf054a4a4e54c132c5c49b2e79cb22b7bfd9eb31c88dfeccfb0e3bc4164bdd79cdebce3bc9e4468beea9c72877b5aaef866e3bce211ef0edda1ebc35d82f1d96d7ecd9ffee26ac8cdae0f7f410c931ccad860440ace380ad3c40a5a90c1840f42c4867a70e10ba32d36ac31861633361821260b2753d2f0428cdbf7f6edaeafdfe61dfcce6de3efd91523a0cc602106349e865290429636722843ca1a137e8cb37347040f4bbab8029acf80dbba50782882892f653091c5286a0a1dc6b0c265053e1c81011563c498018a2e3448f1812515144da9c2044669486f2e64b3381ef158e4b9f1c4f6f77c57cd1c99f8b77fea44d9912747f338cba794f31ef1ee5013261d6848c114470c710293c1e54b1334184d41c529d6cf551e75ccd3fc95c753c2fe2500171b7a78e2046c6c299259a28e30a3ccd30aca88f51366cafc40f4a4030b530c4921a20541b46102349cd6b81acaf59c55a3d8e7e7b7ab7e8eb9aa6c15f112a60127317090018c333ea010630e54e1850d297a688309333166772539ce923b3b3b3b3b3b3a5c4546002222e4afd155640420222ebbe30f00867db0b4b37377549d976559662793e1eebc23abf117da31bae38ed1d5806a48c48b52c12c1da151810b2840c164e8072e556ce9a10a26c258112b0265863154ce6001130f212843430c4b616c31521303025e1781b8a705bbc9dbf137ba3f7d977559d52c32021091cd4866f5b6d0318f55a9542a22413749e49092994408f2978a7be45315ef002f1167c917410c52c18e2ea65aad6dd7e59efbcd05cb3df72e56eefa11d7a53dfe3bceca4256b97c0471f964485d96b97c7cf3dbb9c746b297afc9cf5ebecc6ec636d8be3f0427bb1d7f43b22eab95523a660f821ebaf23329ab6ca227662d3d16ace4ff0d57aeebf5f029b0fc81d274fdbb2ed500cb496b7cb9f3adacd2325b9674b9ac7cc25048fce57da595fa4a6ba962f3465625556f55b55ad6ed9c6f177cb99ce48222035c4e5a43cced2b1276e33de77daca3f56f2309bb21ead48a63add18f6fc8f7f5fb7bf99d5c795f7f3524f5fd3d849deeacd55a07571fd8fccd8760f33722cb10703e4764d9dafcedcf1f5d362f5bfd40d265b77d29012b66f6ed77e10afcbedd77a970e589ab0f6edee62b7023da88df5780656b7b96adf1db7bfadf77d2b32a30565e12a6f6d21bebddc223ae3b5f86556cc4bee22d7f9ff5b284d5987fed2bcef2f77f7f96bf557553cabf2d9dff0d971ccfdafefd257db80a8cd5eee8d5a7a111edeb775a3836905e732fc73d10be2f8eda8360e866af65997596ffe0f2e11fc4dfe5ef8345d7b6efc8606ebf07bca36f7f777b27e9eb1961fb880e4e3441a191018d18b13640165b9068a38931a06c89757b0d1df3743e495811fc256d22d04395ed3751255bf23b1ede01def75726e517912df9dcfb11d735a28dae1e96bef6fdfd707533d8be54ca1d67491e062f0ff7c8ef54602ce79f04d8e9665f447eadf2816e75ad2ab0bd2756807b962d1f81ae87e35f1edee12f7f03865d805bf2b71fe075b1480bb684a3a0eeb92fc1fbed4be89efbcd055bc251fd12b6f7be04eebbefb7b923cf95bf12a2bdbf8be3caf2cdf1a2d80c9861060ca71bc2e8c2892464f8d0822acc302d114505c6da957f08db7b1f02f7ddcac50fb6f73e687befab18c47df7fea240d788ffe8f242962d1f59ab943e2a3076de91c79fba1f716de10cab6caf92b04df4a957256135267d9c25a5b07f479f2b1f89fc25f28bc81f413e11756f86b6f62bbff8f592dfed815735856dde015ee76f889db307fb77fceb7f5d129643cc1047c860c50e658089f9fb48581024d4b060ca17596a7012f37f091bc2b266892a68b688716223e663a0c0e4841067ac60068a983ff3380d5b144a98c9628d14b4e00aae05556650a20b169e28c302cf0a1e96ce4c69e2a40b00a0008616393889a1864919287e60618736d04065e113830926a0619283172a8491c60b60b4f8e084082c47b1dec2883164c658628c1250b019629c008523c0c0e26486a1262e1091260a2598a26078ea98877ac837c9103944def92e92903d091cf42b515d1edcd343e49a350c6056cf24728d1a09f41a2992c000eee99720f02b9b279844de4cf4e19efeeccacfaee46c22e0431467f1e59bddd1e7f6b74f8f97301e1386090f6984185383090358c1531037a031c40a44c4faad84752b50b106982d699c9888e150912445114614356d8921258101e572d2175f2e079793be88728bf8cbbf896ef703054120c88eb3da593e3d46289201671571560674ceb047fc357253910c38ac89624448b016c15f3e1256631fd4111647acc0888828d6cc4caa7641c9263ae619dd480b3328db2aeefe406ed36aed544dd9a46a4adcf1d7cdf33f8fcf73009ef983f82b7cfe0efca5c1f303f92be7f983fce53d3f117f89cf2f824753cfcf92b00d9ec3adb590e3d0749d7b7cb9d0af0e1b3d314ad9017c5f85005f85f55d7d2ed4fec32a03f81a5611c07b6115d6876115027c0004f022eb3520806c5107c7125c6dbb1f1bf57ec13df4e31efa3e9cae61854f53bbfce57c614e8e1a04089bbc8fe2ac61cf2cfda080a652a91450d052e63e3d7cb22c48074a3e7d40006983b0493ff90236789eb049d1c73610fb8cb7e8a3f031f6058c3937a5a552a954910c34655e6ade8ecaef27c5cac1aaf4fb49111121ccb66da9d4a73efb7e522e1dae0184ede42d2b4ad169244aa0a8ec543baaec1be1c8d3b681cff3365f37759e4e1335a1e057bf9be695765db64488a3346ddbc4310417ac5ae77dd3e6584debbaf013b51dd0b09c94a6e87217276a6edfae6ab3edf7d9f7f37511edc05abbad829be7d5ba6d75dbb6ad82bf816037659639a87da236e57a11efe0e8a7756f5b1269d39dfa40ae6dba33ef0b37ea7d9e5f1fdef9f06d7b0eb53970b7ccac82fe6e7cb0fdfc364eb6eb9a43d2d24eaf1471b38596b12aba8375d1dbaa46bd4e7ab6724f96514dab35b56dfc1cc762eea647330d1c876ed3ef07070d7664a6cbdf2129996f7370dc603baebc794feed16a6a9333939f4d97618a73eabc0fb4a954aa39701cf27efc56aedab6d12d9b72dbbc3f1014c710dcd651d3fdf4395dfacde6dfd031cfb85db9f5b78cbf0d880fefb8fbf17dabcf3bbee7e781f9196f5577f25625c33df54701f0f02f9803eacf63ebf2955b3f64d89cc2ad9afa8e16a19424a6c915ad89a3d4fd82790d3ee3d669e66e516961a978c2eb32d622b3af3fad4818fd3a97a4d29dd4085b7f36f9ab6ffd398679f0ad3fbdb8b5d61647952afb7eb2ad05ab8942475414da8088231788629a58b52525a218c74e906bd6c48efcc7ba794b3ff569c774c255107d9dd711851680438c8a9ffd28c3dcfa43312d5c05694f9faee48f52e9d6cfc4512e09b509842062232654026f866d0b19b123ee87621eeb187317169eb022265482ae85d891cd97808aa12576c4fd68ef0270888daa5badd8ac89bfbcafa03771abbeaff9b9e5d67162e9e1d69f4ef6d6a9464e2f7532b9c490e19c1f7d8c9b1f1dcdadce0619de64a7f44c1c55ef503829374516e7475f923937a23789dedf0f10cbd60ae7478954add8ecebd730bc83bddcfa338cbfc0af3f99e61486c9a86da630b3f854e3302039c5b88fd9935bc7ccc9ad63d6e4d699e6d67966642f3e9170f6f9e5d6efb250ca2809935f30d4ad3fce14a4995b65148ef881c7a82891b210bc3a64ec28a374c4d85122dd5a5f22f10e2e0c7389a344b2e268a9b855d4e25697225bf5a1b4401c2b2de2bbfd11d17e5a91adfafc85407f4a912d1d30ac3779930a09eb1d2dc28e538a07ddcff79bc7097338abbe874980f06539abbe904ea444d8fe29d6f725d605c51ac559f5eb07c4131d4ad8dd6ef3ba90bb53e91639cb9ddc63c2d6e7cad8d1752bd04d4842d747c23bb6af0f24eb575bbfdbae43c97e936263e5d62b24f494c23dd59bbc55bf566b55f5bf88fee4acfa9de84dceaabfe90cf1e42feebebe3b75d9f7d36d73eb365147089b89cc3dd994c2adfa5ba731d92430acdcfaeee4afefebfb99e6e15f1ffc2f742767d5ff446fe29efa43d8714a19bde9d69f526a6f534ae67455bc438584edad8a46b62b9f6b7042d7bd03bc4c7f7ef77cfa73663658a942c2f6f38f16c7bfad4da06f159dfbfa4f5b497627fd2addb38475227babc76a650d1df3b49c1ef36bcea7f5ce9ffcd2c4d5f6dabbee14579b9824c98fec6affc3ffceecfb99dc4b5693e5629128f197fcea2c96b3b2f9fd645f07435d85cbe12c774e646f217196bf13abe23aa904090d05090d31b7ce1aebaa1ebb010058e7fae7f08eee905bfcad8a80cb0cb8cccc2fdf23c2720008219c86d0410a13acd0019834a288a2018da215c498e77c17bf0ffff3b37829882e8316e4bb7c9ee5ecae3d6114d5c4dd356666ae4aae11fe239ec71f106e0e467aeb53edc7a714c64e40b174f8e8a032bd8f17a525ea5af5ac1a4e6ebf4a8da5baa3699a3656165fb23767f5799666a6892861aef633c7daed7dfbeeb5ef38864292f9e682f51e7c9bf74c1c6b4a053ed28f2c5b6bcada4fa51adf63b1474e3ce232325feba40cb30fbc07bf02de836205beafe2c89f1d715d4dd34495670e44c447902ff4eae59c7631a7b8ea1f8a69efdf50242c13572d0235994f7f28963d0d8b844864cb7f8651d7ffcb78a1a3a0f9f4858e82b29f2f7414543f7b4ddc4421966cf9ab64cb9f8a2c5b33f3af4dcf31edc7fa1cd344a5f923eb7a26aea4b8aa40f6da07cdafa2d051502694fd508c63545cb518c459ee2161dd1f89ff12ff22236421cb9694e23eaf482121e1ac7e6666e690382d482a95bfbe2ada6b91304dacb2c85f7d47f79a02ddc1aef8872e8f59d1a53bd83193f223ece7ac90b95295195dfe01506147151fc98caef4b08af69c16c969a9b7725a9cd59994db3f725a6e7f7625d39249c9aca490948238cb87b3fa419519ac46c85f4df4c174be3f4807404a96f8abfbfe2087a964abe96b7235a4bb5cb81ac23df8a068a43a8b4150ac5e379dc5f6e63cfb6b2524f5e0a7721e14c7e92cfba038e6dc9c21e0a7fe26ec409cf71b9cbf1157436edee681dc88ecac1c1c71041f047d6fbebecd0f797f137a0ffa9096bf1f6b82ede12f10fc54d8371506e90b7e90bfc06ff0035aa2a48320a10f6f0579ab5febc167ae4b8aa38bde15fb77ecd1df50fa9ba88ba4e4b8a606d267affd50acd237c1506c0ec5561fd0d73e88ca17a241f33351688a2b176bcc7a39d85792f28235da4041164659c486887848811921b224c184536cc8042f94d9e2c415252992621c00f103104d5411c415505c4bc79e0a96bb9cf0e58e6da5caed1f9b876b45ca652e97bb9c204667cd3bab7f70565f11db8ab3fa690d76fc3b4295544a747e1f92430f3be0738d70473f20dc774fbf1f557807f7fd3bbcc36f7ff3c03b3a7147ec197672b0da2be11de0f71791b020b2d5db8f1a70bd1dd7171473b88739c8d673f9eb0697ff131f0097991395b670d4e08e7f55771c198d3ad6233eccc57e7b69628b0d39643105991362fd4b244cc88421cad042c309515ec4fa8b8c801446e70da5a388fd445bc212751192251df3f72161d55a95ea5721cc12e44fb1869095303f13637fddfbfd3b96301fb2e5efe33efcc4aa56f5d7592070cc47b6fc93ec7b0c4434f1c50b2cb0c812731aa6f80206335c30e386a898bbbfb4dcd352daa71a1d4be9cf4e04e846894662e425207f7155498c264a65ce5cf948fcc54a66a25cf94a263bc06e5d985cb91bd492ef7b207f7d747bf92ba0d4af40e058ea5721dca4fe266ca2b0e807a809544bfe976139063e41c3d10bb05021228addbccd1f05d94f7d4a143a5a7d90fa9b0f4afd8d2874e42fc4b2c5b2957a1b51a8ca96fc94c8922df95654712cf5634d892b7f8ead5cf4215bd243026a0053c4d0850f4cccc01245d10d5ec4a861c6099517beafa1f7fd5eff11ef722010bd711c5b84ebfefe2c85eef61e8e39eed75bfd8dfb8e535d77e77e86494ad071ffd1efe7cb66c646fa73ef0e866e67f5b9a7b5bb5962842ca8a0200a12555061fa20ea09852f6b9ce1c428261fc9922020ffee95ccf75013a269e2c83f24648aabf9435da7e4b6904fc8f6b79558099b281472966424ce923fafffb6f2f7ae406dc08a35a420838b0b3eb080d872020e2d7469810531f940446818e42c2253ecf338edd005140a498459212687ae88b182c60b0c51b82813938f44fe12f959c88ee4b9cd109d13e4f63395828ff8642209f31ef1b93dbfe8d2e4ccdd71b94b932bb74b31ad3a6b2ca53cd8197a5f6fd4d8f188cfbfcb5f46b497af7d36eafc30f78c39b78a23fdf997fad41c777f2b84fdebf2f7f17f7f5697410b96fa7c2cd68eb32c9bef72183771858888c8849808683c39492335c47021a1a1e58eaca446989bdbfc74fbb34939298d283051502e274531f198e3f69aa14bc51590bef43535567adfe228443dfe79c4aba27d95304d54cd5b256cc6faad4c42c240a043992bac68c38631bc88f50791300f629042862d9ec0d2248d58bfcba789a853376baddcc3c373831d5d5787bfa6e7d3aea3f6c840af4e88a185cc9a32606ce8e1c109263544d5e082342ee8ba145387b9fccee4622ebf0ce34c4e4d33a35a9db54aef4ab5bad58debbce979d2dbab1e33d779df123841507a83489cc7ecf107a6bc30cccce28459fe71de76b08b1f1cb83219f344ca1427f0e1288a98db4d517e58ea98a7bb87b3fc5b87b35c477f8ebff8b67875601c34d8295556ecbc2efb25ef88a12bc2aa7edadd5d4a972e59d765ed6e76253a6686751df30e7b97266a722e27a14165877bb8dfd61d156ce39e137776444a67e634ebec6624c8329a4d1e874180bdb097072c89d0052888c803267353189df4727887f6fc0be01df279a9c76e70017059876139f2bbd58fbefd7e9b7bb297dc0e5ef6913022b2e55fabb5619e8854a9ace5f04e0b5cf91d5e39998a3b0ef3e1c9f1196ac366c05f23af21224291a13a99355b8a53868a050e5d544183491042483124c39a2554f0840937b4b0020d2095adc5d47ab90b13a43baab4321db775b1f6f22bc3e66736f5b2c54fc35a67c65ea6933f76bff7339fa71453612c3f773929cd1177acb77938111b779c38f41593067b73b98b0962f0889793bcb861cdbc3eb38adb2dc597fa6b7fc7d4803bf28c3996a4944ccf239f41c01d394d13a63bb6932ceed84fae7c1e3bca954dc50562d810828d24aefc5703c395cf5233c595ef7ac1942bdfc7c70bceb8f283105df9406d185df9448a0c71e58fd086d2958fc4c995bf24094e5ac3c56806ee4274e6caef6282972b5b7646c5de5c864d2f52b444714516221ad2888db60bd4931bd82033861660625c6401f6acd7f3972482260a13d0f090851461c4180d355d9e9e0ca1431719625b8cd14410f91dcc98f434303ced161df38cb2e9caf9d9d3e729ae3ef0ef0f2113d9c5ecfd331a7648bf9fbe4b5e915724928ee6d12f9f25e5952bdfc3ac5640da83c0314dd4be8a26ba644bca7731cda3af7c69c59596240d5d8b235df9b2c95d22d131cfa819ddfadf3fc7aa84ad28f507477b6f9effdae7d10586f67bf0bf1fb3a8261c1c0a459b6e7d5a84f320700c470ca10a8e289ba893e611c65f4d8940ffe8cb7074a25b6fc297ad6ac5e657f48762b2295c511148b638308e88621c3bea1fffda2f225bf5e5832f7414f4bd7ca1a3a0d47fefa2d05190bfad424741e0bb28e40314a528249412e5d2734cfee88a4971459f63489fb8f278c945f9df87003e159138abfe12d9aa552e3194841971610a1cd074a146cc12b1faf249c24430630c2b6552f0031932b1fa72a93e1876912c025fcaa5a286225bf5bfb0b5855d057defffbdd7fab2298bcaa26891746a1e57fcd5b00634418ad59761780aa318c7388bbaf55b8bbf562070ac49954bb2557fb5024a3d081c0b21f55552a26c92adfa52cc184f886efd46a279f4adef45feb2d257248c63f5003f24858114e3d8d00a64949a94c048f9c2ec49b6ea268e19d4adaf03c68e72897e4c33d28632ef54d0d4df8de0ac2e525f7b0f41f0582bc9563fad61fd3043b1ae863464a495fff6433121a5958bab0fe8d70fa25fdf5d0caabf89422f5bf285e4506c28d6b5f703a4922df956d829dac438266bada1e51e4941d001c37293bbd4b274fd253373376b9ae4dadddd4d3d244a96083591c3367b25473979ab53b0939dba2905dde474c73e13d54efdd44d59946d5f9a5f7e9cd399db61f09991e066ef61f60999d74826afe6cd9a04adbc43b4db0c77293f27f3cc85861db3ed3331c7b91f354df4e7b699b9f3dcd6b215cf9869afcd6ce8be125df59757667172add3fb6f10d99ece92fea31c9aed5a7c136378803082034a6d44e1841429baa8428b3394a0550431c41254c0a892451a7e33c6eeec6c5ed871c73fc7ceabccb8a2b47065cc142640a920893066727628d1e48b35463de420048e2776545de7f1a9b386e561bd2375432e6031c30950a470c59a206a165060bc2ac61817b8b8e00c1064d8b8c08e39b029868e2f551ca1050b261aca600105e566c8072f30a421c411485bb86881f252460a231b8ed81883af28638d4d182bbca026892eca90c1640b194b50d1040e53ba582385d8a83a018c262cd8b22685a32315130268262c278049586078160021c2e2a4cb18589a60c8524316224cd8020d259ec034beb6c2f53baacad3a3ea7a17266c28e07217264a17bcdc85899a2b664e3592c052830bc2a862c4155dc5192b727c8a0c4c3c5101c2c9c9f3b225ec9d97256cc6dcdd0b7771a609c76082a0128399145660030b331568a44030634a0e3d8839a18ca3293bb8f1c48e7f5dcaf937470f155f1d3f19a0c0f2f86c89a53a8b4160e274051565b068028d589120971f44c26a2f5d993486962baf648a820a1a92b8d822871762521c8ff0d395520363a594b04dd454db268e55cca1e2583371b4538c4999e3b08ef94b1f7a8db5b99c244697c9458c21be8881025781ed2e278901450ca3a79aaa1da735ceedb756d8b1be8be7fa38ab7538cbc98516be368c526b5ef09a6801cfc8d059f9ba70813b61c9b38285cd84a62e8aa21a86eb52a4dd17346d5764a0562a191c66c898446964548861de3544344ac6889ab2ea2a038357db366682d1fc7eaa4bd6302f344f133e6d28c916d70a8612acfd3d87714ce5ffbc83e7b2b336916fec8428d9e25b6bbdd561b349d3b890c75bd6abd5aacc68a918aca05e8d25c35cdeeaa7edb76dacb0af3ec27373883c2c2b367539890ba32bb9e082c855d822ea4a186ccee5a42d9caa992bbd853bdeeaf750d54f2cf0e15c31b89cc44515775e4e7a02e38eaa4b44d74d79e305dbcf9476925e33278bb9b3c79c734ee9934d4a754c4ae99c3367ba171e0fd9fde7289f4e7166c059d23bc5cc2ca59472642e32ec5a552a7f76e7e9578a6392ebb46dbe7463872b21248097ff0097c5ee39a7bbbf9340c701dc164958b992ecb2b37c74f755773334020279ab8820fbedf217348988827a0631522c1359b684f4cb1d7a495092adfe6e2b21fd3a42588f84799564b7de1ece926c74b265cb9e73ce29e98f926546bfe953d621a564b94a25db480665d87ecdc7a505f1ed8bed8b0cca701e9da339e06f297586b0fd45feca39e94f0dae115a65d8e3d09552ece19f0bdabf9bd997f22c4ead37268ea1db0160a581a45c9dbcc4630e2a2c97018e1d1c9f4a85c10d15007e6addf799dc68e53c1fdcf277a48c0adbd11b18508a73a3f3fda0322727e7b3d6a66c3490329433a39b56b917e44f77647f64df9afa545a2cb783dabf2af7fd5411fc7e480878decc9e0e913f5ffb869470b3ebd788177e3f529c99a4731ed99f19422048d6c44e91fe643fa668a14caa55ee9983506de3bcef2703e79c93665ea7cdae4baec63c853c6d529effcd4b7ddba45ae53acfc8b79915f9bb6d5cd576542a9af252b4083b81708cb2ce386fdb7e6edfddf6b3cbbe736edbc2203a644c5e823bdf6f673d39676773f3fdd0f74f0b62fad0c17a481b934d0fa57ae8b8dba54baa75b5f33e6ef2ac55e51879729867f5ac8eec328bc8cd26b3ea5637715cc255e8d28fe7fbfebd94f7ac202e7fcdccfb36cdc572ee3b1189e3fcd5fdedbedb6edd3ceffbb84ef43a6e87fb54aacda6080b3eff4e8ee6215f5e7e9ba3e261a5c22021cbd60f900fd9c27181fe54edfba161c6acba8d21b8cdacfadb27645e2e9b24d8bee3b81dac9506f97ea838214073e8676d3307b284fb09c1f170be1ffa3dfc452f7f97437bf8e01e2c76721304421fcef2cfa8582ff31efea2b31d5461de56dd795d6de5d6dba598cced05dc1cae7e1f27b75f0bb8445c04873911111191512c7b244c6017f593feb1a3d8da3c7254133dd97079fc68f5c54ed39bd9d7ec6d992e377b2ee32753cd69a5fc92e3ca9ff22b0df255465c19861a57b2a0cb954e4857ba18e65f19c6316f2b77486bcac4f4d78ae5ffb49793c0d052dd8b62bf9fd56ffb553da5eb523e3dea9ce04ac8bc4da75899bac1923dd9d1cb022ea401c3a94932dd5103eb2ca5b4f9c27a51a8e0e58eecc5f539b51d7fb158ff2a95b539ceda496335202a71c759ae627d2078cc7bfe3ee89827be6cb16047af5aca2a6d7e3f5db78629d5348d366d5fd8e9b02647f351f8187d7ffeb15d16ab11cc0818e7cb21b6c99cc1523ef1c4134f3cf1c419fce59061fbd9fe30439d0184a73a07caf673b7f79037081a4afa4301050e282c7f430d99fe72470af91b8a77709fc1527a0ad08f4c058e282c83b95324a17fdc681c5158fef6261d27c0ce577f544affbecdb429eda694d226774c5dff7ee0607946479f551a779f7d27c1d46ca7dadd9a1c55fba07d143e067ed75e461f0c3966438e75d3ab927a302cfa18f8366c82c2c740b1e86329b1997eda9cfd75afb64ab3399b9bdcc7715fbfede3ef27497d207c6596651909db6ba1f69fec4f9361d6029bae1e45a3bd9452ce4ca3b56d4ef5b23087b392d278b953279be1f37ce735f7292f0b691adff7c335fa1d8ecdb229bb7e43705cca79ed79ddd3fb3cc8424a43ed046b80cb4a667eb8a9d0003130e418e82c172bc70b59f6158089fd6698dd4c74e5e0eef6fdf0cba2c2d66fcdcee9c3c16f088e3ba5f77990515a6f789a9d2f2595947e3f5f0b571fb755a9d1ec33cda314030cbaaefbaac1f7d3519ae58eaca6f4334d6a9a465fa35a6b54f47c356674f13361f9fb01bfd8ea3efe1a59294817547a090b304bd55c5645332d6b4dd3fa332b612ed9f2afd5950939cbbf899ce5fd852e5756e50bc1639fb884be8088848da05bfe4caeafd031503444b084978082323198eed83ad4ad3bd71943c43bda033fcfab5de2a5ef477999f200790794e5660afcb8eee66aad75e3c24efd16fed4e742205da83d90842d898ae2a85a6b98b560732f057e32ab9cc769d9e7814d6bc1cd05f7d0bfb1af7afbf61bcc62fbab16de2c91b086c20c258aa144759c4d779ec79f90ae6d3cbb75f5e2789ca6d5dad5aedbbcfaab54fd15587ff5556eebbaaeebb6ee6fd8685da55dcd40fbd1bada69936f7c38f81b82430332c6497f3fbc86da65316a2eeb721217686e77c3637fd9fcd675dbd66de1ca03bf6ddb58ec529b4d286f7f40988dab697ebb54f7a9c709fb723a9ff339fed211c7a1aba393921f10e622f3329cefa6970abbeff8fbc16c5c4db2ac5d6bede6496daf13e6b09094e7ac1eaadf0ff79cbaf1fafbd11ae7d3c46c0cdbdfe9785c57bfa31e4ec8979de56915075b5f7bef93f44dbd114dcc7156d32bac36cdc0e1d5ef47c3c0ebba59b31982e3500a343a6937a5dd1b80f55321dfda81d65f5e576d1a9acdf707e40753976e0a23c60b63e3cd9cecc67b2f057e36bfbdcd8d4d2a95e36d9d8e8d77b7e79efb9efbbcd4d71008ff68df75e1f640927ea87d7fdd6bdd8971561266d1effa2269285209efd89eaab4f416cd01fd2f1473e96fa197db5a700f942f2e7dda6ba8ceb8b4cd5cfa9a18867ba8133b72d4a5f49708f18ebea90ffc6cbc8ee98ebd46121dc065753800dcf88f195ce696ffe84ee4718f142b7ff4dc55a5f3974f7f72dd48b7da9413574232ca3b9ac7f623275df6323297cb0cb88c80cbadf0320e1bd676f93b1b41b694ea92e5302c1f17278e2e16eb5f25c58c3579b497e6c07ff49e734e8d6f966559bf16fe643f8284b5926cf92bf5936c79691e4ea539f0f771b958ac7f95ca5a2aed85fe0c333456f693844923b7aa7f16cbe5734769247b60982fc996bf8a8bddc24b58aebf349230bf225b2a2a8e2aeb578c240ccc817f0da50cccf2772acdc3bfb4520df7cb1d9dca751984aa75b8ae54e53a9411f7aca0fd288d74a4cda959289f38cbbfdbb4ac7a35d39ee739eec77fe01eff4e7e3f788c67d9abdf4fa67296bbf61dcdda3da09a26ba1567f9c7d02dff1bc2ec70bd5ba3f4e1a967b8fe7ec506ffd23c640ccd813f16a51eaefb142d4faeffe8535034240cdce3d2887bfc3515ece856acf80f15fc7e54bc4313c721693d9a8406893b4a30d75fda78a669e21882db371ef38e6e1e7cbd73b18d4ee7049667ac73ceee7d782b49988c49b9f39d15c53bba5b83612ec591646bfe7418ee7415ee7413c23471d39beeec445a245b921ac9967ca8f19f9a3acccd6022b9144991aea4e22c69c5593293c23d92165df97fb39925ea8e540bd207325689c699e5ca1fa9166ff1e7cc32b52051af644d1df3705bddb6add65a2b277f634deca6336738aa9dfaa99bce5cf9479028f1a56c4b6b5bfd5e5e137f75d93b94b3e4f793b3e4df8862475ff225287f752faf338511e34edfcbf733fef25e6a1cc7711cd1173a53f53677f70f5cebb88ee3388ee37e96f86b1ca263d8b19ffac95f7dc65f2ce42cb9840ba7b3b89f889c497ed7dfb5d65aab8f477234d1975660a574314f4c1ec697b235ecc869ae7c254b849aba7e28acb52858b9dcc5045fae3fec7217133ca962c79d162c7769a2a6dd7d1ed045852c97bb14295d035cee5214e5ee3c8fbf3458fdece507c29fa0011411111151ac8afffdec781f594d5489e34e8e673761d8692f4f86d4b54a635e55cbac852bafdbaaa6f1a77d2b21b46a5f438ddf56ed059df46a7deb2feeeb6bdbb6852bfb791de536f0eb835fbf72a9edeb6f627d6da3692c5ff6c24474557eadb3b20782c387cf6f080ef0f9f3a03ef8d5c14d1c876e15333496ab6a76bf859c27aabc5cdeb6a7f539cadadc9e7b0e579fd76d9459ab5cfdae32b76962a59968e4c11e7af28e9ea2463465e73527c65dced93de777f79c995579e14b551361b56f7154b5ecf9c939e79c73ced933f3e4f7237b6a1e75df3409d4cf83ec7dcc714d9c9ec33a0666b3cbb6aa6593e33a8edb3c3ba7cca494cd6d5593946619cd6696655fe5dc001c439089ae1ebed236ac44c1ebf7f7d3a13901d7efce19d142ff24507f7bf69e6af5db5e0bb7ef5a506bd7811ee86d42b410fcae867efbfb799e1350ff7e34277280ba5dcbb8dcea87dfefbfe7db71a779ddd3fa7d69ddf8f3a0f3eacf075e4e42f3e5d6d0efd7d19417324f6e95e0652f644c70b5af6d63b55abfe3c2cdda54d7759e57bbaec3f16ae8b776b57e97533bbf36e1f83d354dd3b82ce3c4aaad90c5bd5e55e14db1aaa9524a9ed212e1305180c344810c4f7efc90c188597c9959b3890b2873eabb29bd54ea7db8166668bc90e5ac90e7b3e227a521ba5c41eff3b80d044170d37cf8c679a9507bff52afa542ed6dc83765a5067aa067434e85148df7d9efbaf6b68ff5fd58f179a258ed4b794969a05cefb3afd355d0fb3efb5a48d3d8bedc040b3fdceefb290013db5d6e82052957bb9dc8e2be1f7ec194077ef6bbea7d077a1c0ee881decd6f7f8373e379def7799e27de78369ffdedfbd1bafadd5c3b7a37e53de7dd78a94fa552a0077aa9df3ec5e1e0d430f5a954bfeafb49712a2fc502953b72d0ecac811ee8f1e6ce6dae0c5ab82f95b36894cdd171dec1b73fcb5cd3681279352dcbb8ad769644e8ae863c7d3bdf9361953f5639c324f2ae84684f65c6f5f7e5be2118dc16c710745dd7691fe7f9f733b9a7df7329a5a6494dd33419d2efa4d675f742911377d5957674e950a5f074e5ef48cf1675d2c747c9ed9d63e69c33851f5ece3c738ccde16fd35cff1c58c7aa06f8c387c28e5306a31ea8744cb7b4454d2ac5684400000000c314002028140e080543a1603cd8d5b50714000e8d9e4466489b8903254a611832c6104280218018202000303244c206941b9faa46376312ff1a63a5c71b4fbacf63e9a9071df7b380c69b34fdd16da3b7909ede06979d6c0d6281ae85a7314e100c70c7edd1c427ae349df306b1cf8aca32ce9dba0849656973dcb9cc03e8ce604e1ba7dc5df41049232033b25b32d714dd718af9d4c2ac61a45fd4b2d23f2244047c96285ad220c6ccff90adb41309107061c5f317243a37115906b460931e54e710c50e60880b357865f246da1a901344a80ad7d0b0dab501432a9978ddc6d9162e52e09365f1810dbfd4e7704d333401fc7b7a94c2099353226ec48024d3881ceb4eeb8ad073be49281aafa0ce44f9929dc5f316fc73eb775edba78c19dbb2db8f2fc49611e316426fb0001700f684422949cf306f190c84bbc8566142827af98ea999c8c1f479a23d4f45b0147929290d4b4df0aa65fe94a531b941d0dce236274c6628e3e6d4151b9b1c1a2b1b86b020eb338df9a0be7289ce5de752fb840416b836549e33de0a036c8e58725ff7bed54d7ee02a37d7bc807f5fe36637cafab576efb40d46e6d7f9e9a7844b58edf948b0d173838bfef5d7b4b2b556fa034e70afe251bf16c5d444913aaf59f81b9ad064fccc99f70ac213294fdbd8847bde78c19a0c9d0142d53d041022a0374ad9160534cc753b33e029c14ab0499041317b0dc0c8d8990649a588ffebe3cf3011b4099ba42f04a5264958f2f082fb1ffaab260586d0d88c9e359b3d43bd2ba4cd1d142f85c757354e23e128282ccbed6cb1f47451f30b02aadef5b35a24798615558b99cdccd61534db2d00f714237ba294eb80f90677006c8741ff8a3e96f80056e61432c11877f15b0819482d145a98003fedc2acf134ad9962a5fbb77d899f845c48f8710a2b6035152370abfc199ff3c43e6bc581f5f14d8499b7de720b84056b7e6a6fca9e7ab01532ebfb033895353f3c692a263ca2e6f24baf10e6a68090654c5a044b9e4149ceeb7b436e79bb04630fc408767efea4225facd7624e0850ea779b279b52efbc0722c5fa347ad3f8dba07fb862af8612a39b7f94b34f96cd42f8beef4d6edde5a9ae163865fc81edf7bf7151530f96470f1960b6d58b02c13df9fa81cc1233050161b8896c51e2aae7c8e2bae7a8e72f1ca6ef89e8cfa8169a9b7414d5f40ea8784ef2606e4c16cbca59ab6929741e5b96b27e70f06701ab66ba40abf38fd8e47045a5fafebc80dbb461b805627274869d20c2c378428c8acacb9fb57ae4dda63c985aeeda0cd8a7da4a5b7c3e0670bbbf11c73e16d6a2631650d407edcc3547a9d6da4185631d26c937735a7532a0d9415286cf904e2c3963d25dccdc12a13a461b451a7931ab8c2ece1e4f5a5bd3258607854b969eefa3d4cd638a9aa380d3773890ca1861c419c103840d3e057cd9cb9ddf0c644ce90e170c7c3516dc73609c21290d30704ec75caf3d238a9b4f200b757274e111914d6ffbdd9e5045050baec1de7f06f5d8877e0c561ba7cd5ea379e1a04db2368b6e123977cefcc9bbcaafce1783b3c230f8131899c535ea63644cc37ffe734874d6849ff8fccacb30e5dab7c186078ef88277e0190c7e1bb0fd7c71420a3027c2f51257d14a4ee2f112b2cf61ce057fe35319557e40844d9741b9deb38bebe9c0949419581620f65eb05e2d026b6b51f8a12d01099febd25afb38b15d2c4a5792f729db721ed5a755e106671a03cadc0a2f4cc23e5686948fc58bfb44ef8720bdf95c8e3ceba67b1853f750d4db7dac1e3393daa3d51fe9659a2406ec8c3bdae3861b9dfd3c28acd484230093e2cf7eacd3ed6920e69609d11341e9eb9fb445344f114da2e70d77a40797743e96e6db611e2093bf8f7adf8b677c41d441d255ac01377146e6c52e3935b468a9ef3596154c9fc2e781164a87170010450a68a0851f3b953c49f8fa62ddcf9fb2ddeac306c7b6f3978382ef3f83b61fef9494b6befea32660e113d5d838899c51959ec64d79355aa0e1f6464d2d960536c081a58460c858e8394e258033d898cb2b9ede158e651e46ed5bf001289e80dba61149ed4408491223039689623a40dcfc16a1eccaf6b6deb207446fd3db741b7f6bb0b0786f09e4941b284544ff13f86687d48e72c51b54d544f18f89a2000a7be04d8ec2126b25b66749f709d5732809412b31231c4370d6a80dab984e5837f548ce08af858e8ba3961625ccbb990c6785498a9f7a90779ac7c43d79bafd705e9d97b944d32628c44dc0b346c1010e94daf7019e6781d60a0c87102eb56af399bb7d1c0c277b6d7a7aa6c027c99afbabf8c11b12ad567c8a47989bc54880591c1d46a18e3ccc0399a1cce64c809bf5d4de8e5b6bb3e246a052c9af69c8cd473e27f012a118dd7744ae4d915b4b23a78d149877e428977f029ce7026fad3e169243ed0bbb655f5faa911b67eb618805942e1109a5207d50718250a3db8b3f4de5e6a9e5a1da7877e552af891ac957af801cbd6d5f678baa757cda210cc8bdbb368e35266b27967c71ceae0f1c19752a3ee156b530206fdecb3e51302b03a930420e59edf84265e926db99c09880c7ee24e5f98509700d63e921e48cf5b9096d93d3c6e68d6d4db7c77cf5ed5cd8352f590b63ab37d7f820bbc4261d6856c02ced5e1c76cdedd4203e80e32ce2cea446e2d05ee8b9a72fa5d0d4e4a3cca4723fcac7c13256f4285a9600a005921a479bffb06acc5b8bd9239bcb24933a474c0ef2ec07ddb074508cc35006d1a6f924363ddafb888df5d77cb1d8e0042f40476a38281da9de802485ae729eb0c7eb989d3ecccb306e425bc3882b85590737da37ca20614bec5f669a33cc1276f9b8ee5cee5f14c0207893240e2137e441378200d6e3a0dc36a82b40e4e350ec9c56671c3d6bfa58eda974603512a0b75051b0c2f57175527613a9e8c0359d9daaa1b7c0b19490c4aa01b5a6f8c6337128f459629dfcd75c1eead2f99a33cb61e85a005e43aa6c00f507230b6441d6c2f767db5875bb5c793f9aa1971e6577ac6d06fe606a70601ee379c0c0ea26fac9c202b5f5d56a90c2aabf1bd4b1b429bf9e6a13a67116493b45ed850437c31c08687240e4065f105b8bb193d3093498c1d62d90ced9fa520775e15e37f5f3d955ba6c5fe3daf6415acb1f7080112b391b73216b69001e0bd3cae32b6f4aa6be7116a1177d79f10c1ffcbd4afe2655e29dcbe5de13044f85f28ac2d7384040ff808b8bbde05b15e6eca734fb3127093bf50612e3564f709287788f2557758d09691e21fe5bd1043a7f8c7072aaffbc74f107d2a4952fac14ac5abd2a4b20219eeae6d26fc58f63d83bbdb67340f336060339f711d59fddb810bcdd9f025686ddcccb037dcaa814ab5d86fa12487511af9ead81b5f84ce1300180503774750da2d8e4b3e8562f28e4483b17cae025207a7f737f833a6f58e4c8e0ceb27b66c5db252c04f23b59417e5b7e18db8997075cf64ce7f5c51432cde664419cd61b5840e829783e2ba5a6a34a918a7a0f77080bfe22e0622dae0bcf7ba7bcd83bcc3106a48038cd6788c68461a97f0a56ba764bcc316af112e29c364bee2a70be2ac25e6d3ac326ce143700e9b2499a1cd4a51505d219630e567c1337b21673eb062ff626d018f259c21f2b5a69948278b159175632d1f32f79d845dc4abb7886e1a08ee3b88040072105a9926181e818ca2daf203ff70a6562888fa3f5a9e6ef4c38eeb65dd3a6ca8fbcb9fb549ed56df2335029a0614b9100052b509b230ad9a1cb8fb5932e4ae32ca8fc9c23c8067ecb0be654ecddbc4e382d998db6794000276abbf098d6a790a11dc838ecf4d000715c357ffe50aa38e4666f2e0a8ab14aca5ad9482220a88cd2cbc4d3db53f4388617aaa4309e569dfb303a9f67223ef35a3a06c58fddd0b41a23fa72bc75c92e87081fb8d20967be2418c02ac28de8fb340058582835b2b06349d2175dcba9e8e98d72112dfc8bd8190c2635ddcbd5e5f45d2d0867ba4d578f560008720399e36f0c0016db4b11c1f8ded6d469eefe43f7cb5ebb0423e77c1f7c7d4c9c8f0f6faca36136481fa58de5f16c1d85ec49f3afbd631064294977b6ea46bd406a8a8513776c4bf8e8dc5fa51a956fb4fae503cd196334fad5e7577dd2d8d80cf42b28a8a9b6c231e0853bffa0c8d7632a42e6f5e0d572a28cd112a61809ec7184bfbac717ea5b701a5c82398882526f697f14e9cd92e74680c96538cf10c830af5c9c025fc37957ee3bee8dd302378725451ead038bc19d15b6dcc13541acd269e56aa7e7fbbcc0dbb374a64591a049196cdac06c5072528cd936faee2543aed76e6976c988b123d3d0af907b0935ba63643aac5c609405494026470576c7ac97814c6319427f0980aed844e1ab575dda6cc9ce85b09ff34a0c7a0462e44f1d12975fa53a8f808b09176eec814e47decb90bd98eb7195897c92024e032f12b2619421f09d0cc3b049c959c260df7d0beff951ee4a7d0c09191775b9f93a56e489153646128d0488f242a1e2d2d13eddd57c9f1014c6fccb2d672ecfab197e314b5d9c538605cdf0450106c84026eb8355c51ee643ed6e139c7dec81c50363132b5a43b0e03292f604213c9a74dd5cf7812c6b1c3c7345d1c09fe022637c523651f45738aca92e21a5469a6136083db2093eb3d65b71d3c09c9edd66b3ac95507cb7b01b3bfb476cb7991f5519969f6e9675024464c4765d0d284d2b4a2bde2b92ae7c2b568f3b87f0d7687bb1eabcda4d6baff5d1bd1159911fbc7c1c8d7ccc82a244e24bebfcf622afab7a5cd71885ef59519d1026bbeab7bff29bbbe050936551d85a072d65e48bf63b4b59e80eac3e6cf47f1beb9ecc9d2729ffb9275e9fe493f33579b7362750a3b2f9e1a593ed3461dacb9043a7b54e7a6d695f56d9a72b233a8e88b78478bfeae62384cd2912a7fde98bb540b6b8667f54afc06d05b2f5c7377c2386d2fcaff74596173fb145269c229bc0b89e257ab08c7d2fa67ce08d66f582f74650732ed310888c0cdd439e4071daa714827ce15dbcb604c0f90e71e499549ee8aee15c1a6883c97a7a44150832f8630458db9654581be7cc316be3e439ec88a2e6ea934a3be3a858d19143565159314905920d88e35adedcd38574b87c115ef9c1887eaa9399a13a59011c2886013089823510ed006a4a0095560835ad29abc02f4155509b995acd9171d75667d1f82cc339e693d73aa6f36d9462f86ce2241a62c1c8209562aecfb69e29e660881fdc7f182c064da0e39426dd50c23e2f49d2f3392f0a5c0b81b966a6b150c79819bb3f8453d4d681e8e299c05a29173f0c4e836fc8c2477ff32178ae62c20f0dc3c4cb209e32636fef02463282071fcfdd1fa70d7e6748465fab71550dbebba3e1046475d687551c061db1c2c42cccda9beb7266272bf8a2857c6be3491f8239ebd3fdf49cb24abbf2e5132f52d63f533482a3786d8406ddbb1126288c31ba849d1fcdb6bac5e76a5e08556c034b6fb7701cd311c70be1106643d16811f45cd0427e12f73d6495ee02a0492cab80a387d9f1331f65a3d3252b9fa081bb16f79c56589e1b1dbbf0b97f8cfc84459dd9c3e5310a15e3f213cfee6bd404a1d201599fe45b05592378f1e5e2312788f13a4f282eec1cc0c514c8494028886ef223cc53ef158e0028f0b1428b9cb3b8b7b7034b78eedf8db48b33b8d9bcbda2acedf96a156c8a210390aec5d981f04541dc9b64b3679ebb39d278dcfb81dd29c531b9b756091a41c4d6c8de9d938bde653ae2b58fed99c16137fe798a9789fe25056e692721016ac4d4edc1b2409bbcea2799eb42e3687542252f9d861954526ea86e276df5ffaece88b8c02d6db0ac42b8dd338199b42ec5ae68f8f9024f1529534262e8da8a98de45539437618f83b384110b5ff1e08e2d73eb5f892f36405239b6415072f319a111cc6bf7ebac70e21848cf600555afb8d722dca12fffb9b28b7e805407d7bc8b682d088b273d2b713be2037a74eb8ce6cdc39ad3278dec6d06629b2f728eb09b15d1499b76a4f825db3ff98a85673eba75da82baddfcc1171dc707c87da6b0372044caa61cff99b1d6692ae52bd1f164a35d5157df1d232a64855710ea0c909d15aaade278c27954df94ebd23a55f6f00e6ccad916aafe71e6e1a323fd6cce072248c1b791252e87ef3a2c3b8266dccf0a7bdcd80f84bc21157f16b607326a740f2e90e75f469d8f960e08070c58ef2462e27777b12c9697e205d35449f488084900b982bbaac32ce2dc9a5790a243af71fd7d1cdf286aad156f32c0e34e9bfca28a6420c1c5985cc296459651c12cf6677c73c28e1f045c71d81208c27b0f8ee61a517678845ef5ff5fbe77f8211db21f0ff57e086bc49dde893c0743ad69b1dba439ca958d8632a3d6f12fa6638a41aec4051fe05f319ce2303a37aa9c87f599d6755116d77709f29c8889d00e4c22d169e08d8e4c2cb41f85c006bf7386f4668b27a105f2d7b7b2d75b62dd0696deb899a007cdd27077665bd7a89c00b46494138ca7353c802c74297071304ef009f920559af44f5afb4ea65e8e287587a471f735a7cf83df28fa5885847367a94900f25ea740d1e67cddcd70efa11e3e1fc3994c0647ea1e776db12ace6cce67d8ebd4d3115e582a662307379997cb02d3a237cf65849d1cfd2fe544126c1021f511b19f1033f7bdf8ef48408fb5d0bff22a5b26d9564b0610a65ef6e802667ac0be668acc3a40b79afb95a8127a511015e8a5ed52415b488517486afda890f7f49c0ad3fb32809cf34dc3fc79941381abb0052f03b1bad25ef69632078497ab16ce8a4cb90ae7cf148b9722a30c163f8d1353ca9bff602fad3405b27e6ed016b3c09356c36a769391dd8d64df0df777857f4d201faa95677f1ed5a8fbdef26ae6c9510f0f7302414842f5fc508d26df0c0168e1aaf3e473f93276da48ddab60ad93dc4c1dd3fb5e302cae528556934446700f379f16d6b21a31b5de3c1bc2958259590fcb5aaeb6ba26b3618512a50ff63617d8722b918371dfe78fed85c7c29f86c1590e0378dba80553692b1e8ec6b14bee89883ee4da719ccfb02e0c9c9363860dc5a38d0858fee1f118bead6be7a910cc1fbbee0ffb28011d919d42b03d6d89dd4a6b719ea2bee8adc2761774a15470879fdfb3c15c64c36c86d63233de76ab76f5b09c58e1e5ebef3b856036d8009142e0018ac39b7d85db7b22f3b80b59dd85df3663b3c73741490de5b4acbeb38ae599d4ef6db3e77e27fcf690d25be9114f76b81e2e44a268617ccd6286a4d2462c4765a3940091a882444f0414244ae4b814a910c8da668da567b891fdb9ac55d83b330b24bf507f8f2bba97914d1dd8a4051e9b4222d34bff2947b2b692fe7345b896aa53e03068e3b0c2d7fa127a4eebbd603d2b184c144ba19afc62095e3d1d33e02f91c16bd4c04b1f51b5e269a3522cdc35f69edd3a6d72e8d25a35cd35401420200f4874bda31bccc8f700da681b472191498b526556a83638ee08eda421b7e878fd8e223fd5eea9e33bac5a62cfc67afd316ece7c1144972233609e39e1b49aa3efaf79d5e2e6e7db78e3e516f02397ef2131abb6c389a7dc2ae692426868da65239696d2605d51ae554603c56704d7459f52aaf55d6be46ff01c217b896cb7c46677e199a912a350fb8ad0675f022984fa57448ee4e2b0d0e4b612e65d54736b8a58ba4c94a52c312d63137496b23b47ac4a654ab314e4e54fd27859cac94ba5e5cafdea1b26008822985b7011c3766a037f10fe6a001e053d69665c1116e09b04208a7885cdc2162885e7ab5fd4a2f49c1dceb6b3f33298a61e74386deb6a6a8f2539625168019037718ab3aa2216abc181886301fff10e4559666fb47fd9698a94fb04fac21fd718b5623fd870869b2b57b1493ab21f48f5b2612f5c180854e273a119ef36d307561d55f0efe5e2d7c8a0d44ad558845a5229c6c47e19f67e9e4665466b0434e87dc7f8a657f98c2f1740f94605b78eb416da9038f14b0051e722710ce7bd910c7052dbc412199af79196f0947e70fc970f40cbd1389c7c4ddcb5194c001a186687bfcefd88d72a9c08d01f23dc24d46ab19b30a16b3891d6347804efe8bde52b63018fcc29279ed6db69486399460074988502539f5f191688141da9dabdb0292fc0a619d0964ec0e8472cbfe4360a39bb91cc1ae75535683a1d7383288d36a9b32e4c26a18844b47c260fb9516ede85faf39bccd938a0f7fa00501c36245d07a87d89cd7cf3719c861024eb4284a522504a24da7b20d073508e0ba22094a407f1b7f7b4f58e47cb96af09a5dbd3beb46928a27adb9936fdb868d3c882938e38cbbe3f6d42a63661b5896044912c2ca482ac0b654e6bd5341db5908f9a4e0cacd6a659e392fc02136b6ad312ca0f69be9d66cb939f409b876dba9bde4d4f87938bf9b5deacb1a62f8438293bdb2281abf83885d778ea8046687bf649d024331eabd270baccb0774e50030476a5f8f8f7a7972eb4981b8ac998b22e9cfae0a8bfe9ef948a03fe9d5660d6e737b08330db4baeb6e9d06391171f73552560f665b6e90d095040938e3565dc6e72013e2c9bdd8433b7efbad575468d5e3c436d70d24635094c4b7c52c0e90668db8a7d5f3189a9ecd427c26ef0b8e84cebe5a44c094c048ee002f0724df1cd792a81fad07831ed916700a6e86604ca5c5101b8904e902cdc618411e2b1ed3a3ebf281843a504494092169685c13d717f2e29a8ca28e3ca83b4e63b90c90c46864a2841bb66a3b34a1fa7fbc54e2adc4843cbed95184271544ad1c9fe634730e6331f6627d6f93003998dde2c91dd3412af0695a7c59780eab8377b4aacdd06cb1a22634f88fdf604a828b06ed27248304a8d34a592636b60a309d6a0ebbbd1a10a894e2fb3628fb3da68cd5add5e6fc4a86fb9fd54db1edccf2f3e8e8f88e5e4b84e68cf3a1b738f2a846a0bcaba93a2cf2ebfb8e90f0c7e171be84cb5b949bad6b19e036bac8febb387fc8f773d7533cc0701fac2fa77e0d645ff2cfb493edc87e8fbf2503a961c1fda97b9d403c5a61ebd13ef218b0af0a0f36482cc4f506fbcc47daf07a6c525bf1f3dc08aa77d3c90ad5fc3d1f1620280648c19a33b191c8259fa1ae18a4bc8f4d44d0f203571820a1662680b5ff14151d33633bf1743c2c33463e8a0408762a149f55f06d0c4d2f69e2c25b6f689639a94ba484dab52fd6e1931835e517a0f475efdc235f50609a6cd99e2817b2d9e1afd4bfde743b53e54a75ba9c9c6c17960b317e1f48f31b07fb1dd878e8407f57c187c02d36e4a68644f601a1837c07ec6453f9a6b99db00b714ee93680e86ad204fc506b8903aba06f8681d4970999a250dc5c85f1dbdd2c4316ed115019a9e6cde7da92bf0c24da9159873379fba2a926fb4332dfdd6ff418cdf024ec9eeeab2668ad1a3c1d3e2b1de09b6ed19d610f6723abf25d1c8f0a852f2ba977d66ad80bcc90bfea283202a8baf8e7e80761196dec82277ad5c86a4f0b8e0cc41c947983fbadeaaa711192e7e161b61b594baa5f39701575237d681b79f6578cdbc74d526b2a0534071d78aa9cf49f6d818403e4d82a3cd1e4077dcd281ec2b0980dd46af6f461a18cba7aa4392618c56b415ddb936ab4cd961e999b3b0a5843914d6538f0a15fb0aec51ac2932c6d67b824f2172f1ece4166c82b91799f8eb5c40bfc3d4481d263e75bbc93dd6f22d8922633e96aa3dcc16e730332bf96f28512ec07367cbc8634b846ce63c825879ee29ea440fd6a31daa6b751e71a7b9b11aa264871387fb8892f06914b0a0ad5d72cd8d81f2372d873548e587a4df0747459ea1e2e38b2e8d6de5bde3572aa30da6601b1b39d9ea887e2ccf88382430831aa4fa1e04667fdb580308235259d827520807d3bcc5a3ef169d46a2b8eed530aeb63ba26590bcab0325bb8cce3518f90914418c2146587270d13a2ad53852a90ff6072f7a3089ad9254dba052bb00f0761e063ed9b21afa1258710ff433986a577b0e4b63875d8f3414dacd41438a8b196b35a790d0713fc90ed194852ec02ae10dd6a1019d61ff630a9bccd061c49f68057ab80b0bb1d7e9baab67778d4fa88b3d451937f73061b4951840d3e799182e6a1cfc80332f1400457234390e330bc35b72db5f9d8a33cf2a6c258d07b0d35923cfbd8a098a5abf8c5647553d071bc42b48329ba099b943272b135053d63951399062c1a7688f632417f312e84499c8c3c1aaa45c1154c69ae145e6fd21329fc8146184ef290367b1d90fd49d176d1b714a4d1974175801e2059ca0060488493d96726b6636525d3e20d08aeeb2a187a81bbb9b59429ffa928c4a0c2979b3a4aa2acd2d1cef448c884c8a465eec91a5293bf6a6a8772cf1aee24750a28651f881615882a36801cae01b57275551a37ddc9d86413b67b9040bc4cc871ec4faee1930b42c3c62c03cddb3eb04ff444a22110c00258251740890a001332d22e22dadffe1fb40c19bf99eee7fe203bd6bd4c8dd002d2a753b29e6832467dd7c7a6715f1922a1dbb0c3b02914b7ee16d208449fc55b04ee6931bd9fa6ca82c2889c28d0549641693eaef3c90081ab74efa6900b90a58950fd2ed454a911b8ac5c8b4e0f562b882f312a7be03eb963840baff3fca946c51b34c72f9bca90ff910c839852391bc45610870ff926e07e6ef5f9031b0ac9c28af888dfd466b486b1d404320380bd8dbed291c06d36bfe8a312e13596ac43030358b554c5a01231661cffb8cfe4b2111228010a9288ca4117966192e0719126eabb97ce01bfe46b852df3f65c26b73bb3b28251a4bb3964dc36aae8ef1e63b444a701dd6c4e31b3c55244de87e098648142683385ca3870a9908f9faa83ba8140a4212ba004b4f9a73060a25e98715231c0846c4b6fb4a9bc26d05daf58bfcec5ee4f970c2befd6dd9ea146f73b9578fe952ad1482892bc69f0c4bb88d2a6bb0fa14d4cbf2170ffa656186f4856a619fe9cb0ef3ee200e0508460df30a2328c85b30192feafbab1007b58ce8ed512c7608576182203f418a3d82245e2f229730a9fc436fd3aa19b6104ea96fb964751e5089d71381cd183a4370faded01970bf11d3cf9af4bfb3a34c3cecb315bb03ae806ac0992e65ed45e1f745c129d7428520c0b8832389a1195824f170a199c5fcb7ae6ea0f3f20c1e666d7d0dcbee659f790400fd3060aebb652e9debc51c8341b46280c7fe8016b8b3612aca83323ae61c60e6a490c40c9e60edb8823d48acc8a706b75fe8fb9c2946245bfb7f9b60351bcb8151ea0bedaca95de18e36bd740851d5121b4ea49b70dca3d4315906f6f2d5149b1b2ea45f0659852bd57fb3cec108646d8bccbe01ef077da0576a8a16914b680119c8c5348d25dae6a867eb3d94f89c0578908cade537351c0b5200268a888d00d62f74572d92105038b1a169e128b20e45ca24d7ad1d54344e0efd25120e1021b97b4df20c2bf90fe90f5e12688045fcb33425ef76dd865e3bb087161aaa82434bbc8c8655dbbb0d81acbb41a1efd6f5e11221046b4c9729e9fed43dac66d640e8a915290d5946d9f60a1dfa42f61233e5d75321c471958a032ed840a5d24763fb248c7cf7b692bb1f46e75c840bc88a6d0f50b0382468104009121ce7930f321686b124f24e043a7f1a10e13eee1b69dcb12da8be02786fe01adb0657890f5d44b07c43a2951926b45ba41181cba1b18b1e24001b7d03f40fd94cac9d6ebc2e23553692775a1002f55835ad6b9fc1d12c0884382f6b17cea47c732b8ce48d2461b16196dc7d12af00ea50af2293fa154e59933ab531ed7cd8ed52dcbf2658ea9041ffd1e0c3661cefb954bd405994a440b3a62c1d0e1438cdf0604dd45a8e5f4e28162bdf174a0b7d0b876dcd280c5c923222a9dc084bf281e7fc8972fbfc171c2ee5f03b5b9154bd130fb4f3c902ac21a0cf2f17e02aa6be69a11766615fa7b23ada6fc2e013ff8bc7134ba8f8ff078d2f83b5521bb24b78b484f921957fc1f1685b6233246e1eac421c893ac1b8c5d8ee88d8db691e9ba88d340dffe6338211eaeb7e9fdb10021f578b1e4e1faf1943e2da422a6aa2cbfcd86e7f738a6cf5cffdfc9d31940e982c02f8314bb691c9aa1049b286b813fa6c2ea7be9174b850ba7822855ac157ad755da38a63b64d61f80ddeea7d79ff18f1f039be81d8e0a1a1c5d3cbb1a2253926079a6cc6c6dfbca8c7bea601243802079bd29c75b9a50cecc85a31cea1200cf933abb95e26a59dbdb2645a4fbce1d11b73412e477c2390769cc6baac9ea232b4542a85f91d79bfd27943a7d9b747c526aeb846ae84d31acda5da4bf414ad612727e7c1c959475cd095136d8eaf0dd07766404a9d8a1211e1f5782de066bf09de42ac59c4f866297506face4df046694cf94814d3971abba3af18ef5582b8243f783fb477e99960789f5729e207be748bb1f6ec964b8feee88e2278bff93c7d162c9fe794d8f34c81d568f1000bf3424cb033336b9ea7917aeccc41b40577d385e9cfc17144053c9e648c22172964d3c471381c3cb2a877c3fcd562f29ef1de1accd196dac5772f92d400b94c27d8e4f1f6e5a92ca6c00128aaea92319deed46b58aba56579af5d3c7781f172c805103577af831042276b78c803cdf799b309ea184fe5d44371663cfcf5b5b4fb495203a3c658da743c48f3dc78e962a9699591027a0062515d46fe5d3741064ce0538af91a747fd1d0c49fbd7ccf81c552f46fcd75b99941675377cfe0865b3b63cefbf8313514c0b2f9a037fc542cb3a46d6cd29d4ed114796751ba168f0f34ccf8a8a4ecbe0ed52d69b649a7e875f00e32967f6dcdb44d822abf6c142e42580d27bbd25baa61a341131dd0a6a68a224bf055a4637d3bd6bf7f7fbe5b509081812522545d3d7f049d7ea93b9ca22527248f5094e4538e9c042980d89ec4342834e846f3c7c7d01b47913b1330cfff2bf9d07be0f43cbbd1453f4e42316954cea6e4f19683cdaefac2b75e1fa07a6c3172fe4009206bdb586759951d7fcb2a17a9b245af9330666b52ae46b63f82d48dadb62533744e7db1b67e0149ba30b62cd1446bcadaae12f94d99210f2700d173d230354a6c68213818b08de796044d8ca901ec043f570e546786870a366c923bee48e284b78b8a82246e1b2ae61d666baf90f96d8b6f6b6995b43329ac904bc701c207e82bef26f4bed9865966bfefc4731cce3f4fa40f687f0709b1e9fd99e7e06b1d0763b4d2ae99284e90b96cac6b44dbbdb9d6a059afc29010590fb29d071c20f4e1e41b7f178091b71f3f9098e653a6e294686b5c616fd6090a839b86d9ea88f911c12953f525719d4a41f177e893f9a7ac8acc5fe2bd09e57acf7122584b2b55cc30490b51d89ad6dc7de20158809e89aac40d6b673965784c0ead222d564e8f5b33cb51843bd737cbcbf53a90546dc84868e11ab170800f2a4da4f82904b75c862909a6a23b421146ef8d1714f59b4db966da56e4088872d88bd290c96de677dba023c80d553a1539aa10d80d3e5a7a50f5f6be924adb5746112803c4460a1be945472bc1b477e654de7760e6a565e0f27fdea477efb88efb9a77e00158db10ff1d1fa8b7c2d336d7f641cd1153a3af01ad90d6d51c7dd58eaeb75b369de120f79976221c495b0fd2175e7067c1dabff0c1082aba9569e5814812cd96694d076f017c345ba8a583955c7457d2ea812a5a9495d5d20edcac64b42daad5c18b543457d0c283b4f940a08c6aa1d60e2249d169252d3cf84445555c772f22dee0368a2d7ca841796af1907ab5941c6dbb2f8d0eb73013e9d5917ea07431f34af9d626e645b067519dd00f935eb09beea30973542024834b8b18e0eaf6444b171b753444f08fc5a272ec437969c0a489952fcdb2a09f37bb5ab4552eec9639d15bf6b9709df45a42bdb0beca85bfc01b6238f7cb450f601fa859a0dc689fc92617d0c314648463276b1e1df6040760e9f85302de4abc0e93279da28548ad036bbd806a4e1a4b5aab48db7e6a08412dfc76c97098cfbd630e029fd7f1be60b858e9f37fbe506bad4e29806a58b4b3d21fe5091b33a7bcb86b632ff3a09f7a2733452781434e4ac6211eb3d86ad151d3260815efba86ac87937cfed81d30087b7720fea14574284ee6b7d59c2c9578051c5747c1df0a928b5eabcd802b610eb936882bdf113a6cf6023d9e1e311e39bbd12ef00a0a8dcd0cb54d1ce43b0d7eafb7f2cb872db981145b02bb1a41bcc7817dfe4f8a945f5e55605f421fa879ae340e769dc1559d500be2fb690761c6f05a7fec8888556fd5ed02beb4438c2fbfb56786b8ed50569b414f2dd4746344dc8511809494669429151b1616d77cb78b8fc5d99a768a3f1356b40d9198ce7f6ea8c8058ad254948016bde7b0ab9fcf54c26d96e170a150b7fed4168d26550f623ea26802ed1785623832a68a2473fb27a45a91c15907e4605027178a58e032340ddd63be8a63b967735d545cb53b8d2307a536602fc45c39aa5903a9a0a7c1ea67bfe9d82cc52c88c6cca68fa53df8e2bcb85954c70f1dd86ce30b02df353c6931131d7f80a6b3c707cb5b8b061ed08f559c93f5a811f467420868c75270c366ea1d76b0b8e9488253aa5d0f9b9009fdf624a917a9cb2ffe033fa8f287473ec6f2f47ded260dcbe6622a00b0cb0e218ae15f0d1b8eef3aba6c80d97f783337379fef70a8aad1b1df0eff3188589c15e4d7666f41b96a58df1664d9962557ae6b3f9084a1de3aadb9407ed5013778713c372221482ac2eee465a9b47c4afadba01629a603cfa31f39b9ff4896eacf00c39260675e6908f8915a5737d28e55cbe8634b0aca89c94710d17a9af877de4b32f1fff5520b1aaaeb09a2067658654101882405fab32754d2856ed7f455094351eccd5da678a33209e63f09b50ec549f1f08c0e74621a5942394e78e8809b069f8c2fc47cbe5a6b602369c164c2c249337b0a9818daf96776763b2c00a980ae6479946a523deb515266f559dbb6c13f17013bfc80b21736987c1569ef23a09282ba7442bf0d510fe201fc486b771c9f03a66a57a3f0bf8b9b28803fc531b3813b067e80cef25f575cfd22449f58a8eebc50b5e39d599977cafd8ab58e53aec19770ee54c0437ee9ed03646ef46dd158886687d7224636d72db32d85b41a5e5db4ededa21170c4ecbac6b7fe8e61eb8f4cbffeb673bc11a0d5809d0e2cebeba0ec6fa1b74448d9fbf3652bcb9a7b3091747662be3aa9c9b60551f80e18189df2dac0d04abe11b198c8209055226fb705ac9cbdf0a6dc474aabca96d86f4415745222ee79b884b0ef0eaeb4ae613d5521ed690ba62249cb85f6acec030a49c68785cf941de45ba8e9f8242cb24c09007c9e311a6b8ccb90dda25b363a59fb8693aaf7adf2d7e934d4f5f537e658306b7a3bc9f0a74b4e0949a7a91119f36518a2400241da868602f06a18000202c13524cd3f39398442a850cd99002ab8beeffb1699cdd0719fda001a4a303e116f38a7741b91239480c9861a5019e990d300323aef2a029e8fa8e5c0ce754b34342486aee483b89f9117a7b05b755c332ef0f2292d99970d367df1ac5c4aa82ef7b0fb4b08483aa7c1b11a9b19d75213f7b7162d589aff9ddcf5e3ca01b3c1589e7cc7f086a3b8676b01511e998605f672073c2f37db956c5daf701ecfb6d8a1a0e2629b509c426bbe8509a52d510a32fb8d5870b31d92b8909ce9e33feaedcf48328435e16f1a88ffda92165310badd6931208ddbd000c60ffb0af303a96d6d9d4d7d000359e4777100b1f19f0062f5626d0a57723698b01caacafcf42401b4123ba383fb1cca6c4c602394259ffbb05d7e6d2efd93012aa37dc56374713786fa56b0b5ba582559255f0936ec2277d612492f358a43bab6eced8ee3ebbd85e09e5b47ff87217ae843bfc2639654238abdb5981235baca9e81432ed4a538a135e9b1c412a0766d4052fa2dfb5d5f46720cc4ce066240ae5b230ba78875a95d09c028da1e6829cc99941a6ee74591570fceab1e734856d2488ef8c28b5fd088356d65e281cb6b55957a1a3e65993e2f62a79ec7cdc66fc9614c7b5202d36be0d464e592b2056e9ff41b380b1b0633d57c8ed464d4a003d8cdc8c18c46d3a3e7fe4369c712a7839ebbc0ef759e71dbe3be456cb0d7be33add08a56e2a5d4fc7f56d35193193b3b8999bc468c77bd449d14120c40ef6064d7ee7de73305b99216ec9628c80d8d62c80b89348f50adc7462b036b40404163d7bd04592b24c780e471851b87a09a33ae381c828ce4eca60b06f62f75293fc011772bc6bedee7b6469a3a1614dcf48e578cb5b79981f1a1c8b13664c29858b5a53844a3629cb691f517d28bd7c624c36ff78e8b4327019113f1cfafc8a6f7ea9734f269873b8428ae843c09265553e22ea495a6bc7d2e57bd50db41018753cb5332b8831ca5cae08b3d1b84b834483a5959b1b11149ffe32d7557455dc1232206bdd3c091a8e3aac9dfed8c8114ed15541368c95da3888ede0981cdbbf8ac423e9dc3af6738db74bdb79370b8965392ed8161c719cc44db4ccb073e5ee4a3f3008b671f608298d0e1fb6cebbc934e2fac7bef21934a931a00d3244d8710b6e116ab56f91bad0b82f8bf64ad19b548a38f2b0150ac026ba223f0078a504d9550f301702b5dfbbe893e4752d702910d479dd9da820e718bd2edf4340049680f8f5c4609d6a9110fb878b918400158b390d2400b216ab03b095af4d8f767795332be4da14273a49420e3ab3e65b7f4cc6e0a66ecdffa2af7ea7e459fdc0501d74b73ca6bad19eb60a1c629ddd50f9471e2f6286419e94ba7ac0c8195de30e67f4996683a67b15607e3544e1ffcfd4a28e101433510b47bac0025669d3cb149fae2025f16fa487be1405ebc68b268e44109dcb5306733bc8fbdb36d6decb049aa1c370bedfa2a3143cefb5ca4e1f58720cc00d58b42cda2c0a623fe2936b18ffae79e5bf52a13382f81d3de2fb53a5f45cff5057d6414b509b142643984ac32323cdb5c6e0e167e06955830e17b809c7a82dae9157e38a1a5b1935b30d22c38a52a74b8c95717c17339af4fb8fa4cb884472375ee10ba9d7b4285eabfb063100cfdebb88ea49d05443e1dfaa2f2be6e9aaf404a3719ffc8fbaab90fb6bc67f2e5b3866c36d3591012904029bd3b7e20139e4e51d56a0cc480a93e5a0feef701d43941a60d5d1d1c1fa0d9a167de2bbf7a4006eb69447d51bdfb8554f997bbefca0dc5b610c572db8cf6d15abbb7bafe577de45cc762db760fdd33a47d0fbaee4bb24f4ce94bdf56df9d09680d8f6a12f9c91788070ed8f933916ddf7c78f8222abc4f18d7d8cb27c1222b5bab5e6ceb8c70f66e44c2e76aabe4241cb2612bded706d3ac4fac7b50649227d3c3fe96a4a63e9baab349707ccd0aa4389f599bae6784ff25927d9d41927be22ec2dcc092afdd36d4228f2d6da2227d2bf0a5e918d1e9cc9d8f0e4f7d6c9fc795ba63964d15bef708902651c82fb30a9c7ac98c069872544f380050c2c079d59b753bb66a586d1d03caa9804be3c5edb3bb99062bf208007539368016f34c2692b687ab9b6e732e7ee74275bfaf3f01b12d980e9ffdfcbd9e774b92dcd11a55084f8816646324996aa9271f9c88b780ceb55f64258293a6aabe339189bcbe4273433b99518e4dfbdabb6f96c5cfbd69e3bd03b742028e894c56a49fdb36d6f4c7e155ffa21f4bda1d32dcc1dac85a78351ecb0f4167137ab10ce18fe3d1814fa33fe07f3f175f86357d1bae2b57882b5e372d76c1f162e4016e1c92e1d37801a27e94f85b82ee9cf10d1f99cdf2ff94f58f765d1d537ed0a85273942362785c3281ef7a6bf0cdf5f26e5e28da123fcdeb55013704bd34996596e6ff99aa226552d53972d73ca00396dab7fc8cbab52d1f0ae6bf2d3d8419f96d1d3641a5728ed18aa29b2022478188a19c54d9dcc5eb0db4104434ff7427eb72088b82d4d59c16981e5623545951fe0b6f701a2e3e4f6a7a1f73054ea651efe529985fbf19406e2f8ce23c6dc7ec0adc7b75850392b66dce061866942ad575c276f23971c63f9a798f2b671cf0dd2f3beada409866563c827dfee7a8933041a0d1df2fb9e080553fe7902b78d6db5c7842b5787f05bdd8d84532a98a58a3ead10b92b54a6d129d7516be17def96c5b636150845fe0bfe31a06b3580f9e0a5a9d0db4f012e5aa71940bb6b7f25b3786ac05e109cb11efc913d033688f5acdc957721d701c3430651c97f873f56aed2efc9fa467d88b800ec7efd96d6ea03026f5c26be6f4a6d354b0d94e0b20dd62d747e50188994c1499d5c793a687900ae18152e574c57ab14b0b7dcc5ff5905311866838dfc51b06a27fa44d0785c74f76937e55a83824dc773370fc1a6a35cb0e5a54398dd79894eaffd24e1e238b43a9f463ed45a29cc7f5a604053e3eff3fabed153a69bb02247238bd4b29b3047d17daeda575c412be79ad227e31bd6f6df4ffa956ab214cb95a46dc96348284742a2490bb8b8940c23e05e795a2709d514345fd898b3a0b5383157008696ef5fe0ce34c13b995492f8c79554a23fe036c8ea82b93cccbf175a5e3782ed92f43cc5c843d2c143b0c56d74fd95304179afd7c0dfa8dcb2a185a7a6e5b2b8e442e45f3f66fc17946983a9e9a99fdea1e8fa946c82ac7556c04415c1712c70b03ded7d7b6123f6e0510bf5835c4407cc9675b5b4717675a552820ee58bad4045926cf35ebbf96f89ff48a38697d15ec7b24c1a1297a16fd1a911940c82745db01889c39548c184d92e33cfcc3617731e2531401ee7590a4a4a92a931d99cd93491fda89380f02ec8e1ba46d0c04365f372dda7b04a70937074862688f628e05cb387ad52a3980b00b612ff76667f0c20467631c0a2acd9d7c02e8a3a1b28edf9d0344e1f12af08602eb713a0476a6a7a50fde937000188eeaa04253f44ea8e956f3286ca4743b2d9d83dd558ae86e6ca8684949117b37fd6d3b8a8909fdefd0397760c94f34a099110680f631ffbd46e48124984464e4aab83067ce478b4479ee9130bacc787d0f1eb97340cefa95db97b925989fccbfb86e6430749af7e1b56d0e6b058324a4128c1243f5e6ad038fa91e903e76dc0161bb565939895063e1d258fe36f9230300dec879898d81fc98a7d99655a4bfdb6ff9d1d320b463a4a9c01117c96114a1d53964543e7afee0cac406e4aa069e3c6c7125196f3bf9f17a209b23cb480cecd5434fe08627c4357aad90f3194eab7aff140e18c3f0329781449bcf42448f3ee96c8390c638a0c2334ad749431cb121ae0b2618e39a92c6db842eb0b5b1f60dc7cb34b67e7164a83b199b02580e1fafb3e26fb44b15f4e36d44b6eae9c3256faa84fec364a8a4f83ea0493bf54ce926c423f71b138d0a4dd5cd1d76c079c0d73c97996e8545df6bfc85f177b713798481c681e8b90f4f5e0a9bde574cb62f35441c56e87dee76e4b09e90e24f2bff7a89b8e340ce8af0cbb7ef94bad9df15dab7d104f9a4f62324a420ea89277d6da438dedf3cb2e8a657c29df9318e13d042859f79643b3a265e720349719ec743e69a152061cc6b3b3da45c0f50e1ad0b888875dd23f5413ceb1660891eda8c4294a8a43580a7a0406248df2183249b19d8a1d179bdeeabb1ddf3ce6bda84894be20160209374f69fec29938fe217aa02f6b979d9e93e26f49ed5ae8c69609506f7bdbb21cb9cc6521d2e9c738224d20dc7a3bc4b8ee7bf696f5dbb9c3352aee739639aabd800a56591841dab86b49de22301d3de698b03c2eb1b310f84ca42e9bb6ab3b22c4b6c832e91e9b2ba7844efa8ef4e2be829401610824e33b440d704bb861e23b39345db6e394931311c6b6f5c2680911f0c83a0cb0b6df7674f691edc841f4da30dc6237e43e882e993586c44ded6422b2a9dc52548d52ec919dcf806472f48804a11ac7c511423f20f7ae6d9c65c8694d1e94d1e970bd3933396204622c33bdabc8050b35eca2c1d8e4436e429e4e83eac9aafb47a5b0f3654393023a643e4a4d61a2de5e945772601240bcc1a7f30076c120ced526559aff5d977274be355836c4c15819f25502116cbef787a69a8e48d06e99cf41ccaee086e5bcc1d3e55677236bf35c7540df936d6af2f831280ceaa735128f10b25bbdf6435fd6dba974042165a801a4a5f4db7254e0327db6afae2db40c02ab0ea6d068005ec66b24bba300e87adad2969831316af6eeb4a7cd8bfb602a72c334f1fe1b6c914479d22bb0e41f4694bc74b8c4668eb356a348b6bea2ab2449b17318777cffc491d084e44486e92c6331827422dde812a639441d2b68605096184ffb664e49a3bb05bdced10a816898807f5eacdae9513f41233b607966ada606f86df50b3031504e7f4cea8f2a6e6862ebdb92c9d643d89ac1f15317bd604c3e29fc462a1ba7ddf2ed1f4992694ac97f93c0bb6b2d612d6d7a217a1ac206cc64d855889e2e1309f472ec13435ae5f315beb6bb46259bf07b5ff90b09d8b437f800b7a46a7ffeddc11da0647a5b03157404a989dd7a44e80f96a5719c9787065265fbd58744ab273769fb22553700f0c00a5562b7cf3437631326a0e2906651926365742d8d00009845e1a294374ac42f43ffe7e399e5a63b7de715b22ce75f9158402c5d1c997f9df945b7c553e847b0467b46707a8302f19f87701031563159f50f28573cb8bc162aced081219757b3a955230c26f065a04e8bfb10aeae420c7e02f402c4205a4dd65c14deb4c340007f45abe6d81d8eeaca8eb2e33179f58f7ad880d31d1e10d568c2139f84c95436c72fbe0f54ba83a76d3b47d70f489058ff13f77df26ddf1e8ccc0d41591b5c20ea52a4e83fcd77014b94879aba9ece901873ba630dfa3ec60d9a3ba7927c20bd0b22de23456b93b4e2fbb1792ca303e6d37b02175838d89008c57fd3b1fda5862778491b0e73718f9084606ea8ecbf17a70eda435f7ec877c04d232c08d65d917386f6d6e2750ecc2ed597c6a8bf3685f10c43f0ab2a02b59febac7e7b0c7a80913e6abed3917f4764a432c5095569551268575788b22d98f87e26c0ce76a66c46d420483ff0e0e299a55eeff0c33ca41c70d2b27bd47c8de75dac1712ddb5c58559eff05874f90d8846c6ca7ffcbe95185c233282d3ab10a2fe83bda4906f4e4bde3d33471ffe0e1284ff586956c7690fa85c1435d73b0901772d413a502cac518437e5fffaeb5d2980a32d5a9654a8775a3772a3b7120f7bbbcf8727ce6bf1008db85571e4f9d4afe0b6d608de548c6d9801fa2d86e540d2236c052f9885f4218fe9988e672a04443fd8b9caeffcb50aaf9e86eefa66a30c8da2ec69e4ff58ddc6e64f8ad8b743a164fbf3d934b1856b0848a5dea6de4882ae3b5e47c5b7f9f8953d89d94359b7f5a34a50e74bb75e91b688ddecdd746ef6fe445426145fb1300c8451a4ed5da56df74970264c0967786cda6c81f9f387f4e02a97bb8199f16850786154ae57ea38b4df9d530bf9a85749341f831f2dc5853315a65f5683c31c6a40a67109c00d39878ae1bb9784009956d231217b2b9368a0499b9d153281739c7a80dee4172cdc9d2921b275ebcef1c8d2ffadac0a67234ae44d0f972a165413973b507435655eaf434716bd18ff321d0adb18d5bc03050fd6031ba318af09517e1d38d3e1030315f2085609880cb7dbc1da5e78353a1819ffbc9c79b3e73792bd84ed4e7fd344faeb1491377cd67895a940cf80b3ed33865f4c87bcae9d6d3fe8fed7d391006fc08141478ac988360b81716e7e49a4641c90264463d9a5d42163539bd69bd3065f138eab08d7ebcaa43521fca540423e19777f4d09e162f85948ec4d1422d0e1c3db79d650da8b3b18d8825a407282f67b0886a26948fee500d6512ac1cf16ac0cd0d96999b050f83873117f296a30bf83768a56a0e911645532627729a44a9ad3b8bad531b7cf321276234897c265081fd3f967815c200043434483bfa4fe124b986361ba2fb004b1cd4ed70c383443762923fcfbc0d00dc1c9aef1aa5ffd4358d58054caa5a916e36616425ee677745b24ebbc82fba500513eeeada1516c793941c6c8f7654b20fcceedb089c1b4f0abaf12caa2e045102c3e9487c52b1aac59f8a19ee576705d4b092a9ebfd7188914d71ca0e34d837467fee159e03e685bb305646da44087f147066830c7bd07ba3c170cc9453617f1f9fb07da5b65948322a733d897698624d5d2420dfd0e064dab1fe31414021cf968b5b06e41c7d80d7977b38364abffc68ef858a93f43b716a276ea40c914ccb22ae5f859b5ade943aa16a833acd47f4d95d8bace19d8854a06f8b108a40130ba04566b3b3125f7d95bfa0f3229a9afdf4cab3e8e051c945239bce60008ae7eb4cd6cd4798c1211aad38cccaa0bc7a7e098ad952b6f754e3efa56217729f915ccf7084ac22805d1b272629aa3751fe7624a801929c676a71190a9b0647182a88a40ebfad781dabc035a89d86117e9543ed478cce67426b7013bd9ed5f8949ce1bd27e0f35446a4c4e1a041aa9b3eb454a091964ca52d9b42180247e7b6a072cef508881f37a57ad482e68f3e1ffc0660adad216eff2d97ce26f47835661bcdf94f729b4a74e995d6bea74869ba7d13776a90568294f5446c0ae0785e174355bd0e926359e15c1a35a3f96845315170332e31c97c26a092d49e975ca02f0be2baabfb67979f7304722003ebd9761f804ffc4c64ed900051160174d91c280f6764aa573e19cb1872786ed871b1db3b72e319b6d789635b2abb017451aeed391f5f0bd87af0610fd5d957611f8c375e2890bfd60b9b37a911d9166fab96a5d3d017315fee08a809360d1d026d40057be88a165d8bc5f40ddd912400443f1f37653176bbcafc24aa47873cc0ad55312160f677c051cc5da4e549df151569db359e813289cc8cde7365a81a69639d8eb4f40b3c3cd4cbc31d0ccc9f8297e92513e5cdf79bad09fd1ece6c1e5d72b167e216c0073f8d060e19faefb8d3cdbaa1a3ef7c30f9647646620b7a592d8e771eefbca2e9baea48702d83d1be0b31afb82d91d89208640880acbb6545b16bd9940e011611c65de1263e4dfd2508359935aeda1b384803253b5430e85eb49d1e63017e5ad9f8689c206427d72c75bdf46026ec507b17a1089ec45e9814218c6c0ab6a130a3cdb7da94e43cd27b3245c73cae5e8adb5f38526dda695770711e44a9f270cb12392bf30519867ffbd95b84b324388f4e73921e15c782080e2dfc2f66e58571ce146eb1b27cd8020f30b47c0173b5cec10eb8f60e76f5e3120b77855c3cc9c00edaaf2b3fc7c10e79a437b8d16a09df434118a26715fd66e3ae5000263f0788dfdf2e30ef190c5392dd4b65f757cd55b848d2ab091d0586afeecaae5266d94873353f70bbaec7b94dedb99794c202282f5e179702a31b2032de44090878b9f27687c8de65458e28f2c506ad73d4a80ae56f38a157b6e4e641a60e9b4614ba5b9fec4f4d722a8b6f405221322e28f1fc81f746067963cdcee6316d614819dc92d103d8704954f4dd41843657caa2e650ef524359cba7ac4153cb8b408056f67c5188caa20165bc9d763bcd100cda0c695bada00376b2bd63a62b6cbebfc35c5bd01fe6b73b6618137c53eaa3ae9c7cdf610408da17628c744b92e0e89563b0e1a0d3a903f55ec03b93e0e3a8f6b44e3169e3cecc77ecfa60bf0ceba610c27e4994b02db437b7b30f20484304109e4a208b4825e003acb9f5232039df566a353fbc4c9b20507a3fb956eca5640912a1443a61197bb4935490f7b42e10754b4846d81fb4723da2438577671c44a53128c59dd015daca4dc48601c700f7bd5407a0fbb8a80240b73003a8a478e2e426af5d90d5bb15a2708631a3588e37c2bba452920d76181dc054d969c46854b9ac4526ade2a6d841ac012a75e68f1d369d6581d9c26a10c9933a2b8972c1dc2c5ef1812f5d772abbe053d179a753f57f04bbb3c959d90a8c451b760afbeb7af669233291fd4fda0d45ee99985ee0c7aac9e4ba3d5ad1d8707385aad019d1b826942464287a930fd5b45fbd0ca644356e87d4223d0636d2ada83b7228d2b424a0a0bba31a246ba452118039d58ab8117591a0e40105da2dda462646b234fb526c3de6c882cf24469b8b505ddda3dbc0e8f589c05703b0b420421702d239cc7052660dae3461ef9d61284faa3e021a69d77e427f609fb8a1dfb1313b0226ddc1664dce104dad78d6c21965ba5ef6260d99bfdba750179e9c6e6fe48dbe9134a2d318b51ce05316d652151254e88b19e97fe2040e98558602f055562b246dda17a8fc6bfd241c0daa007a4e232c24654fbbea54d8a9c693ba86eb989f12eed1566a7aa8b795019728566b5159659c8dd2049f2f38dd819152319554c508b15d85d69ac0ae9e5d551e84919aed41e195d4a78d4c61aacde19f8acb6956ae09e32c3ee206178795082549784158040ec2da298aa8ee88d37e8fd71f825e8be2b0830802867d5bb42fe8613a5c0169249078e967b17065b51803ff08683d59256135522152a422709ab722d65f6cbe553814f86bb80756bc3c3946c9f7abce3ea7d70c804ef531b17ade2f4f21f3d2adae2f1716ff43e940c080e7a4434e93062fbb6eedc752065f417757f79b0d0943da1f4cbc53dd07ac61338a6340e50f9ec2c4ba6db4d285e37e87765ee3ac1dda13d512a46ac473d6907f244ef2400745a200ec1c863e5416c549d4be7dcfdae03315bd6231a8c7e138534607fb45d245f4cbcf2582e74d3c6de6150c7f72203b9be5783e574df67826f3eee9439b3992d1ea1e19e40865a7178a54ee4612db2b765066d3435f2d498bed4172480bd20e1df6d32e1a689909f464a8213d916c53eb6cb73fe7675fc131813d213491206e286f06be8d8bb225028bcbd1224c3422c54f1cf62a0d6df43be502f16cd8174bd61b5dd802c65ec3bbb4c0af8f13d5bc3dbddf89613075094eb337f6a02a347cd369277aef60672510447024675939c78c68b5ec78415beaf2a9246ed29eb642c92d8129a0401363e8523e6195762b3e88dcb2a1b97954658bfb8c186fd98c63b452fe81f5d2119eb40fbd9a4018a3dacb6e6790e3930d9369a5c08023394b48278900221371955ccb3ca06802bb0232fe7241e27e42ef6c5add4ff062f043efb41fd0c51def906c0e4b230d19620677522564157626538a6be4148267142ef97663ce0dac12e10bbed816504f06b8ed0684d967177377ea0cb4909331150d42b92efce5abfefbc7c9420403c58aca25da72706ff24cdd940bb4c78ed21f56d7e512ad461feec04831fda26af999e367c9a692306dfa2c3e87e088c1990ca7baa75b615fbc1251202fe9f108674da82b51328db082e6ba298367a4af744b74b2de8df079e7f50474ea8be5638a639d601a3add439179448135f79e6bb6ca9c4e6879c90cb67bfda58b30f97ab2f6d22d5d731ad03bc28534a9d56820637dd50e60150e1ff10126399930686bedc55d77838fe6ff601f052256fe4f463e832fe0ab07af6ec8dfeda5cb9a982858650977ede035186cd37a8fcd6c3017002c0a2761b37c88b324bc2789409655a0e9563cc77ee91c3a515c8379e8a1bb4da68e4b28edd4a844d6d8886b2aee2356bb1744e8dfcab256d55d10f10551049ebf13ec0ba09c69e6a4a06c174b130963e6b360b9892a4816423a6b5a050ea494dd8dda773af7f0ed510e5e67c82d78d657118a9c23c1433ea0c333e5b743771444c63edc84c02a84a7e449be2eee2534add49cf3f100f50ae2d02c7ae237a326fefb043e4dab5464c81f6f2d933a0280b1edd4b3e38a4ec2a712d18d45f91e43fcc15255eebd714a4e117e8dff15a5136a0437d8135c78f7cb87f06ce18e87784715ee8489d41eec49033c99e214fbd8f1d7f0000efffd508b4479be63649592ca64eea62b382738c45fbae1945f24feacd1213a3fe8ac8b03a1b383cbf93811dd13bbe195644c30c000bd2215d98195a50441cc94f8f44d897d6611da083a7482f69dc1f158ef791a04282f8c24a8aa859bb3fdeab332e565e282a8b11a5c8abb93bcde8f58cf68117d7800ecbf84c16c8e458f06fdf6730a551d49f03d1032a53d12778895b8285b10139e35b68d96c11fb6e6b3f06d13cdb85c98543c9e2885d64862a71e19de692bb7b960c1a2fcc98072bd4ea3dedb22f5fc3a1bbde2f7fb753aaa287b3a0fe909a3d18b1da2180c84b662de646230fd8188356c6fab1ba2fb429c1d454c814228abb35cb0fa9662a5146443f1ff01508b1970876a083be050f0823baa6aadf38ec707ad40b98f2e711a29bf88ca2c32a331a4ac5975762e45e953b863e826312853eaa631a29982206f46b0e9a44d0c5b1952928d464c33a385d527ffde0dff2d3ac3d8a94c5b8ffe37acaac134b5d19c5d5223a91ffb30729ca6364d14bbe68b428de845951b317a33dcad4c586088f84660155502829fd9aed878df1ff9e2d57eb9a79221b9afa5afece26bfbedd5901e57c90a618d8e2544d0ca28ee5cbd22af637185acbe63d71c399e312815bdffad0c79a8e920bcb5895043c7d508d96ea3225ed199ddd57004d92bfd01bf4c3e368ea60c61868b9f22fcb5a37476fd724c8a70d6cd3907f3c2b60c9585f7f2cdbac0d1b49fe30578b1fa7f090945aec926be4cffa170db61a5780795ba41164b1934b71ea9e11339ed29652d42a6e74681a8641b6d4c9e0863543c6005e93c94f8d9bd291284aa0e6e183671ca15191893b22494db0a32193d2135fa6977dcc935fc6cc72dabff358c5476dbaae49439ef3873c116249292bd5140481b139d684a00844be00158a5e1f16c0ce3cd75a78d319c4496f2ba62e4d6388974c744da645ff2f5ab452b3ccf478609016876fee5208adcfb40afbbf015d42ec88f56644c5b5caf687789493edff44f68e85acc048597aaf6f48d6963afbb425f7d4f4a7852b5a8ab76caddf6a36b21e0fa4bc69e6538e7088cc4b87c77a758e60a63f1e9220aa1374bb9a86c1099104312f0301a65b2614994a44e7c7125736c4e6e180dc4f38b580561866e74dc2919604a2405de2141a3bf68817df40d3c8b43daabd131f53807dc8c49fe289069b72186cdc3c9d2842902d06162d7a64b21c3ee7daf0c608c2592c58c60229d1c0868cb2791cfe019df4d509d26f199794528aa712b1d135f229f994cdbb373f807412e2911637682d228236c5e6aa8f92114315b2ca2af9a4d3d5ed47a293dbf2fcea8b00d812e13dd7ab04de8ac78d0da7010d962ed0b5c64803ef5c7b2c262b4935264fea53d2fb2e19b549c8c69d45635c909bbdc9293f6eadc0de64c13d0b5939a6d30ece9484308347219106c5c646d0acfca7207668b038bd50180b61b8056c84f0a1e9f02cb3c95aa6e208bad3202ff39cdb933c64304f252f3070862921ea6171369faf4e1a57dd6b431722cc2ca517460269450d329dcaf0e0632005302351ac362018aa42e622f9166378a1621686f8db050e9fda066668a90c109941966eeeb5a982d73a8310a9c0d88aa6a09799a2b0bfc4489b90549677847f252e22af290979ced1755ced6fd97b6498957c1120d190368b9f01e6559c1e8ec3663b529c2c3e09ad053612e663a5628a2ea6fc046b418bf231d5d7c57365a9e28968b04330f9b2647f67834c96800dc9e19b92e694dd7968a4d12359d1e96cbd7fc934610eb172029c0f36955962754715a89550884fb77a2911e21c87838d1bf987a6f84754e4914f67bf269e984281e56a32b2e251a9e1af07312233ab9c3843c2f58c4e05db31c8dff88fb5e088704327473a200bda42e1fa43fa672823c488b31dd90e21896515c6654dfd7ac361474398be6e9cacd45c2c7b3b8dded4c60fe083ccce9d9e22418b89fd868913affb144a840ffb5e9ca97281cede2c1661ebbb144a17c7f0bace85fdee8dbfb45657b5dd8f21eea3c3bda57a32b7f1f0c5dc1d7a03909447e1a54796883c3582f9ed17a00a1fea0aad9a7ee391d88f85578a908de1885391048af06f425ea0c3eca24df0b501152a220f69e43c099ecaaa55c249bdefd30c84f0fbb23ea3b14d1db6206caa1483591aabf5919cd0779014204ba25d538e33f526d6e065cb00d3a947e1076142f15390f047b264e23183bbfe9bb917a4d317460aa2a5974d124058a2274eb2132374dbc079b61484081a91de0476d2f0801817119817edbb294af05a7645705365702b0334a46a607e7bc4c69ebb976980131a1cb0889757ac12fad530929d250feffdf23b927d00e022389d44a62df3f593cc1d9f4f17eba850e67a0770e8ff08fb6ed650e6f5e3789d2c31e50781e2b23135529b8a448bd1dc397cd0da31148fd17f04d88b01619fa3a437ad9bf0969bb27821a89704ce3e40c7c38b66ac38cfe13685f9fade91845f3d6b4ece6733cc16fd86e8e1bcabe5b6bf459e60fd4c5a757c07a53e2ec6efc5e6237f9d6c549f91414ebe6cb12d88cdc6a178115505a34b2a94524588c928f37db40f6bf7b37f4076ec3d1d09b2883f111ff71a1439d793c77cfb9f0c33ed45c1ceaddb20ae9a0432bcabd85ccd45ff0d6c04dc813a24e8681c893dfaf3e010d000f9df9025aadec73c008e3845f4fb0a1cb5dfb1ca9d1ff04721b6a46dc6aeef312b3055cbf9e1c6d289daf2ca40fdbf0ca1ad39c5a0dc480e27316f5edb6a8341aaa93e531357e8157e5b64b3c297d68632d83a064a4eb2049a3356dbdd662a115b2e1dba80f1221b3bf91e30c4c9617084d725b39c8da73be9d060bd488182cdec350f4c62de6fee4321624ab54de56ea9564923f51792ce7d955858792007717ab9e5a884ccf0deb87ff9de7f4b9a31480b44442b88304eb8853824fec1c85f3dc23ec7e30a4730b746d48a46788935211f60d7b0153db1c25f137b0cf33cd3d6c19bfb3ab0895ee3d8779797c46b3f1f595fb0f7c1dd3242a4afdbb1d8b06ce3443adaa36a8a8d15ccd8b338cf5d48c6c4d82a16d3efc30013efc7a3d7523a21251043205ad226d383b0a8b5b68a7e44cd337faab371a0dfc8cf6d538d4f5c082b36bc4e79e20ab01042f395808b76f19de364850f6a58a8937cf0f60fb01236ef16004d9c4f7724295e9912f591e21f0258176c830dd8a5054226dd17a950613a5850ae2dbf3c1a847ca85259268b184865732dbb0ff2ee9060b81a52fb81e4976c92f14cc42045ca049cbb9314db1bed0796396fe2c93ddb03a5689bee9d78a2526bf70fe98d50759962d13d93307521855e2052ee2428637aea59652be6a08bf701ae89bd03aebc697f936021e9900ff69c5fbdd1741c96c77e03aee3ee3304715d8942b1d697f97fcbac49ee8ffb620fe99aad2e6b7c1c85a5441a8d237dc069cc32917f8e84d91b092953a00309a36b6eabfa5e8f3d1fa9f32170700582463b551be6498094c1aa3c1350a1c24c889c8cd4ab5f043e0962f9038da8ea145f4d880c35e078588338b6bd29103ac3150e52c1dc8cede213c6609d04e51b37a468318860c7a43c9770b52b56d559581e1eacbb5c0152b131b9b181da220204076b1ae32ca3e28372fd8c9f4372698c84289b2be12408a17e8aa20c8a72288cc8961a3564e1cbff801a204f02555e1538d568b5a6a9810fe02acb427a7c5859a2bfcc0e08bba5d2a033ea234a92f119a228eb6f8bfc0b993b07eb2f68a475040e01bee8d7f6c792637ab252ec08be6b4b68662754d66d37cf6d05fa37e8858634550b51da33e330dc9bc67b7278e5185997ee8ea315934a081f8728bc10000fc36d6e3ca970e2f16a3d90fccd686ee421eb93677318e2600f2bd185e99946283dea0a002662f49e50b1682ede3096d6879e3114236a1b6d8482c082ee018ec3a7a28486b0e6ba5b14b12d36f522d38ff9f25611b8862ef9489c8b3038981b927b604bdd03b208d73bd723548bc2519591338eef661c1cba8ca2f4d3160bdeaa21dfaf440a4a742aa4eabbc8cc4c6938b991c5402afc7d9fa7a56c661b63170e42177325896c3fa2fba0865cb7306faca8bdc56b00c5feade2f30ffbe08e72bc11b260fc12b1ffae87a0a2a26fad64f7316ff578dd4dbdd9a99bdc60001828366a5916418f72ecc6b7a7adc8bbb5dad9bc1035f98552d7fa657c29e612d898cf07806815dec55c781460002edb6ac3a3060b310a663dd780600b0019363510114a203ead45a945920cec24a075f1fd00b3ac985d1d841d9c327d207938fe123091a38ef60b70ef480fe2ca167b287411a0e76d48b0ba3437772ead78059dfeb697eb128eb5b317df555b08bf1f4e7be234c0b43e210642364efbde5de5bca2453f008ef089c09417cfbd02aa8e30d9d8c63eca1715380dbd7b3bbb3bd0c4bbb596b619ee7d96de338ee529f39414aa90541109c370a285e7caec9662f537a003cd79644b5f3122848fa043c2f8182d983ec81db33a698acaa2643979f3550d949981aa8e4aa9f0d54d2191da281a6d34fef21d4d0d9f4230debe252a5f649979ebd1b10a69069187f59b419f119eb211a369ba961d37bec59c3e6f44d8cdb491a8847292c5461e75ab83e1fd88681091850f1ccb072d6151191273291b0cff418a29c48ddf07396f3c84c152a227e3a12b1239ca75bf6ac670d54cea379c4434668d03c3aaa324283e63c0ae2fa4f6f5a43f58c06f99021c6982d4c68aa70226d1ca520055d5c0942cb1139a99e9dd23a24c039320290de3ed3db3793f4f61baaeaad14f7e45cb9b334b261dd90cc3ce761776b654a7aceafc8f154a566a09ea35ec29cd5aae7bcecd853e7c41aab5031da51b58aa59e73b11fec90b825557acaf992a57aea5409ecba50fbad882a4b799655bd6e2273a5dd29409d2e5d74baf817b0323dbb35f3ec1d4d7a768e7dabecb4759ab8dddd6dd9ebac683a67cfc902167013974cd66db21e97b91432d8989f2a5a0c8762a8f71c4527e54a696d1aa452ba825ae90aea9561565a5d94c995759bacc7656e858c4e590e4685d7fd487d61155f14ac94d2d5ce14b7faacb369ddb8cef3587564c6315dc63ad3bec5ea6cf302d01b52d17bf0c66eb11b1e425b4c280a9b2d86d35d1d3f75f64386556733b8299b1bb029d80b5653ad8dca583d4e65bfda99bad4a98c466d1a8fab69aea41cfa20d54fdaba546fbfbcdcd170a7d7e85568a7d2dbe974a33a4c5cba8db48e3d40255748c55775efe7ef74707439ea69f5704534eeb81cf5763af123e7e7d88371f417a5e3b1a8f7b215930e8b3be7f8a58ed73e76e4f6e97e385803f5d390b5e496e1d307d53c15b70767d8c5833d297765dd308e762e5860f154f41847bb47e572547301724f39a99e7242406c1ed4498083bad1b60f8d22f4e175b77763f51e1893a54155c641bdc372cb290b52a4a8562345a2a6d32a3aa0619406b92a0f1e3a74dcb8d1336525cf97533699aa081567055c3171673371a7f3bc94524a5f1f3c1dcb2150dd954be99c8ed374a7f7a0ae86d12bc42defd34befbcc2715cadb5729b57eee15008634f7d3a7d51ca4dc562b11578603a68377efe08fda4dc500d54f6ec27ad81d8a7f7d39cc197e1ec190dda7c7a33d1a0eab3fa6a846db4d487a90fd8623999a6a55bc09286f33527a8445dc883200d549f3a0931a8a7524fa766ed1bedf4699a2fc35a43606e0c6febe9eb83fa327aea2114ea40a8ff0805c162b1d80a54b118498bc5be4a01720f08e3a1061947f5b23dce519d7790c03e8c96ae6723921dc643f3f6d8cd6e7b8faed4bba74e2be03ce24ea76bd3813be70c774e27f706f8e472cd8633ef59f15cb341eab96603d4b373cf351b687f9f6b4cbafcf4f00260ffe6a3d21228072a5dae067af1d7327ef310caddfc039b071932fdc57141226d08078a1c71fa6ddb98cc6fdb5635635c4f17193468882bf69b874e839884fd08fdb605a13e9b47fde63f9b0b6dee9bc3b607e18728905bba7325c217019bc2e79e6f7e69500ad63bdf1cfc2cf5713bf2277aa315bba9d4dd9d015128b23f9d7b16198a5252c368947f499374a48c39c194269c44b144b653291ac45d3ced5003963449c07042b6531a85ead6a8893dfbd8cd2e7b8ffe009d5edaeae5f5d2d9cc6f54dd58dee8f9e6c69267a36ad9397a23539f52c7f778e34b1d9386520ad074ad35a0ac86b0158149d0e7ea842153df0eab1ab249519f76d94bc8e7a187e311ee1b372aead3ab1146601ae4734fb4d467e4d0ad11b9f9c6297d4fe7943a68062d168bad304403d1b82e0dc435264f75aae4a87c77b49a9231bee498943c7dc93d7d7350df5cd4971c142df5eddb6490b2ffa8cf9b87a3a54242f7c6235bc9c94a2e566e54418ec0f187fa4cff4617f599541bd3469b7a33df6ea7f8f957569911e9892f219e7ffe8d472a12f559fa722651d1207ab72ad7f58d8a527d6f543488c9ad2a86546e545c12a5959cd28c63e294b8a599449f1ad64a502d3347d33a2b94c651718de385761bd7b5a777cd6d550632618ba72643ec3978ae29f1f2e0734d890d4cb3008df1de5cb4208303196986a891a5909dee1ac2c3922e62e0e1cb17723a1494174f5b66954f174d889145e1456a0b72ce90a50c2d9e62483489424e77715902fae93cdd569fd6d9ef0d0fb188001b487a84a1bc68410b52854e062148db08986eb540a911825481abad213b2e55635441aad065e142762337028cc89710eb9e7bd688ecc6d7089d7bf63582eddc1badabd02d51a5fb5a0b722ba25aeb484388bb8d08285d3f2f15addbf1881d8fb01266645842aa40ab203b3722c1863502e804b915519d3484b8948ea1963150dcb04992be3decad372d40ee2979012e2fc3cf59d5e0d960b058be123df0a007e825d3687036b4e17c90eb07bb8e72dd360bae56e1d7adba70ae8878300e0e6ceeb6400f74906e22088e421d4fca92a2e33a630feac33910d101edc3b90b8ae79c9bc1a579d093357637f0e0217ecead180694e7b7dd06822048043a282507b444d78acc0a5961af2010da88c181017237cf5ad060f12db7a28de7884755aad0b80183575272bc47afec8d387f85c3eac6412fcc1127d912a5e4b8cbe5355c23539f198ee464ce0884fa741c8b8608a461f63aaf6cc61e0de33cb8c1bb5dfed2b03f3a6e42fabcc50a99af37cf5bacace03b0767684123e6ebeac77c29393e57487ce7abe72d5666df39b8b14a113c38026918b7f2d18fe39f07bb01a301b65a2c160dfbd9d0412f07bc216b884cdae4884ce274a115f9bbaeeb4050a881a673365fd7e44b9e82a76e8f65cad478887ae18995f2c44aa95501254203aac8c23efccc3ef685aeb360d7cd1342cff3bc957fe2e7a068d97eee81111275ee75a3732c16ab7a5d188621cb57be0a576eb72f6eb882b1fad1d330aeb2c29015b25621087a6118dad0ce300c4317cfb7fabe958fd0ca6fab706421dd9e950fabf3a08337e14a9c1f861e86ddad42d079f0b3d65af6a6e7cdf9857415b244feb08c32aba732a4ec0803c170fee877bbb259fdd05985a0f316fc6e3e9c30fcf83dcfbdfbe5e4e8f084b6894e0ea0652deefc39c5fdabc5652a05141cdf0150909d450c9a685295c4130f3e90ed6cb7d9260674f63b77f0b398a81245b6084a41728d8913c8c8231c194b55a40aac841654948e742f205568fd40d2911b01ac050c104c243bf185136648ee8202309648900ba938936861a18a6cf1670812ec2cab34faa81ca450225be4718154a1e73c02e41ebb7d2e1eefd968d84c134bafb8397bde39524b79c44beb18b264b71d066b1f1a0456f168c6aa7cde5c58ed832293dd4763e266583c9ca6cb75dc8c27b7b4944a01375a41af41e1b86e04c7db3ed4614f7fc5825bf2930740b6e755152b47e056bf41ab6f0f8ab57da6c7511606178936b0a101cf6b47c7552387466b06cecdb561ad42f0f36cc76d95b614ea3dbae9c8ddddfd82eacd4ee7a4026edc302e6f58b6762ca7334ffff6f9d35d6a387a17e75c12d473dee2409bcedbbb9bd61d2814d53a92c0fe2c4261fa8bbfe7c401e3965787875e1fb47b8e82732b9fce390f0fad4cf8bc9d73ce5d1af010cbcb9ee75827cceffba0b801ebdb59acf7074506d8f8761b361e6cda9a197b509c59fe135f1f2ce0db17b08007d98ef35fddd85f7fdb0232df8030a50ced58b8257709ba43ba4ff0ed30333f1fc8dc61bd1d041b64ff793ffff4528399037ac90a0abde49e7df210e7b6f3b27e38c228acc697b01a4fdf470f19d70173c7e6edf3631c9c83f34ea49c63631f8ee7392f19c0ad9e1bcb1fe61c9f33e728ad77ee8086f57fa31091856b4714fc39ff41803f81d7a8f9ed4739be88c20973cde745fd2730e0bf71328ee99cd80fd6d08af547e0fa22d76fceb4fe6e24c16d90200d6be187909c4ffb5dc98d42e6e880cdb91dd04193dc605e7766b63c9179def2d4e54ba12d4f4a5fc3d353efefc9d178e3aa73c3b69f15db152ed76a80e24b1d3c5a4807f5929d739045b1d95b3cab07cca7a3c8c265e75caed1355ee7780805a63d09502081fdcd8bb8efe7a8cfe74406fc369ec06bd67c1d4158823273f0f7686f8721893aaacce496d6bb851a0d49b086f10b7a0ca5944ea73da659b0f926b56d4e29a5fd85524abbc7504a698bf969a91e4a296d312d4a29ed31422e293ae7a494523a9b8ae17183f4b4030f30b32494c8b2cd6655956971a14a6a6a4a0a4dad810618269a4dc6258ba39f5e448b284b814f1891c452b5c110691da0862e59106931c1cda652940fdc4c8ae36e2c16f352b4cdcd63b19897a2cec662b1a20ecc44ea6a54783191c22f53befc4024e469a0a2cab43a3589f0d407df972960befcb83af832058c8b084ca70b1643c03e1a535143c1476b019db5952198d0d90f8d295f7ef829eaa20e43574494d6392b0c733a097c6edbf43e014f3314004bf09b79f43193d01a466b994d35f2d33ead540d33fa49e9c8d30b48559b61ea07f05c73f205a8695b70aa073da1b3b445109c39aa53af28b4d36a1b16d229bcd5eaadeb7123dd38f690fd368c568e993ff03afe172005c11e4d60eff2f38ec5b29e8d377e2211d1047e960df368423b7be753e438aef5f36dfc441338e7f7c2d0ae44b65ea7b3fc7b502245df26f07fe32b84af23110a72e0e6d337e7baaf8e26b0f3b66dee7d2347bd0ffaa7ddc4522c3d08b05f6e55fdaec63b32378cab62f9b54fb1fd134b2245cf44c26f13d8b9f2ce741eab09ecfce0bfeaf841bbbe646f6757fad0801f8d899b61c169ba74b6999a0d69c4163898d93c432d65a1b5721c0d55744add045fbdd6f905b86d63abb6d3af7d2d57b7572d4f68dfed8d3f0d136a981d75346c7ae7610ccabb228b6e16849eecc7821092932a5bdc90c64c17275e3a043f1c71e4c41549c83085ebb9e6e4889ff15c73b21405b496991b6d68ad738fdda132cf4bc76dab3c5ef661afb56edbb6b9a0769e1fe1d95fdbdedab39d1228a880dcb37d9f77b57eeec15c97b7104cc09fcbd57305812f040cf43c6742577b1582099ef3aeb9160d775615800c63d8d679c90314c29e6f3cfb0d214f7b604358a2f790f094c74b78ea3c1c78da727de0a9b7883c0da90d8b9e7246ab5963f0bc157c37edd130201e58303a485d4ccf43a29ad1828e1d4fa0194384145d8cb04296ae77e1087461c90a4659ae54f9612a298a1cc2f86c8f07401cd0f32d04fbfe6902c6e561684db27c8de75a9325af0770a7aa75435b693f51426ff4f28ecdcb5abbba18ce1cb384d6b66d61156bd1f6b45ede38e78bb6a74230acc7e7c653b75a6b756d636bab1c477d736e9bd41b5647cb2da9d54614b74b5adee9a59de36d585b1710b889d7f257cb9cdbdb47159c23ec7f7e0ab5e89c1da5455c6f95735a044f1ec39b65165525d88c77468a106c460a046d5a7776df1f6125aa1c73dc6c3a7ba9a9cc96e8104cb3234fc7568ae242764b7bf62f9ceca1d2b3a3a9aa4a83264d159aaa46a282eaf2d4250aea090a0aa928da9c497369f2192a3455542580938710d14f77a59b525a6bddb68dabdbc6715cd775d65acfebacf53ceffb3e1004c30f04c3305cad562c16cbc666c562d9d8d8dc7b6f6e6e70eecd0d0e0ece8c19335aad160d5b5aa290c856a26e11e9541d1dda5387cba9f12db21b2009fad1beb72daa0822b020f1829a30a45195355e40b1c044124a8ec8f2e7b7cd81d0a0cdb9c67e73a10662f29b7bd040347e8ba10cd3115496c898e10ba32866a0a4d1420a1830c103b9d55abf59c3fa1b625bfaf6ae05b7f46f4f8a71e070cbeb49f190f88de4a9370ff5d3ff5c2062c6f1cc38ba2b9783222edb9f5a7ccf17b0d495f22121093f44ea963ff6e7c7fed81fefebc05a6bd9d9b2540586b017ac055311db6000d93798cccc4c99a03acfd61b4b0c88de4df6b1cd4e9a827a9e75ac6ae967678c3b9da3ba609269d006a35efe5467d1753b11e69c28f4e38d1187509f20d46792de983b7df6aceaa91ff0307951a6c79b5eac9d4bea1836ac15ded695262e7cd8b526488eea770facbb256a7bdc56e914e201c4248de73dd7988011031b52ddb2d54044845cb488fe0738519860f2d3ab4844e8c17cd6760f9ecb13bd615e377a56406b6d0e1c0f7387dfb12422049be3b7e3d678ae3121d369b999138b98ef99ce8dd2cd689c4e0d6b1b64a7b74f280d54d430a50e32d23eed3b4ee01cededd3061e1201e368b71f7645732c227581d0206f9f766bef8502451e9a3ecea09ee4041a276cbaefece0fcf412e74b9fdf4f2fbf2f9d839f5e72f0a5bff760af7ed077c6d13369d6c03cb4cd56692acdd9649a4a5b969f1e3f34d0bf929b3dc7b4d166505f6e491bd226db903624a4b925fd44da6661a484ce2fa463643806a13ed33b4072992c81faccc96548263f2f796a84d0bff115e473d099e45590707c3555c3a6d44f276ed952964984d2404a34c832f592ebbacbcedc4e4d251ac4e4d28c89f63da33ed3b5cc6c285244f5848740d0b4f165e40857d5c043d4414084cb7ca58ac5be8eaf193be232df2297f95813a424482d933529a7ec35931a06a561d3a79dfd744bb35076c604d2bc762cecdcd8d33eeddbc8ed039633be79689637cf227bb70bb53b6c8e2ee5aeae5cdbb05603720ff768e7573b1bad30050b14588144062b2041e22cad71618a0b25514690ecd705c436793210fb9c45da8d94d0403cbe6a08e5b6dba5bb25f15002be690638e806fbb4d3a4f2c65ce1fb698cdb801c1ce4ecc3eee59905df6510034868587b0d9a5fc43e50b4a1f99e341f98f0d4168392f42557f970f4e53c9a48e594550a543299573bf9ee2637bea78ab41b692fe1034d4203b11f6047adc9cd79e8073f0a2ec651fd868b02427dc8b265b46938c1d4529f29be84ccea9cf812c2d5b2883e75119a742adade1310b4997af6263345a6cf9869a986b177dd78709df5babbbb44758122aacb2423c5e18a65d34d65466588191527a6341d4bd347693a095255541728a2ba9091829a4a506694096552994b7d060d159f4953d53c9e6a17289b7b736f7066f8f0de1bc6de59cffb3e100c3f300c572b16cb66c5b2b1b9f7e606e7dee0e0cc98d16ad198d1343aa76bb872dad53abdb313f68ba7f2f0d09e3caf904516cbcb3678830d4276b18a22ed29ee842d96618b3c618b1aac7ecc6fd1c6ea878db0c50d5cb4a74f6a831b461d64d774d769edd8bc2ccfe559f9f05e83296cdc0d36709f6eb9dcd2ba36e099d3e55c6ec9e5b283ee95babb4f9fb6c31bb2f768cb73ac4c8c837d4719e739997d12597b03820f68e6fb97d7fefc940c3e3fe980829f6379b9e725a4c0f8f2e7d981f46007c2fe23a485d9a5c36d671d2e7d5e116d1d38c4fa4001473c79cebb0316ee7d5ee24bec5f60af7edcf75cbc1f276ee22a0c7329141043a158bc0775203abac51f51a861b434f2ea2203461d088f3d80d019cfb52525de05757f7c78ea3a7e9ca903a1fe23c489e1218a07a81e98bef4d9c3ece7df207a05e117428254aca2e83a49419e3a8be58fd22dfd87d020f6c9791ccc1be645d5791504f4e9451a367dabe1d2757d33b8ec3b600e9743174ffd6c0f55703767be3ea9b3ff70a57fc94ee4e737e7b1a371f6c8912a424207c723a087e3915005aad24829480fdaf126726512149924f2f3f36bbb04d8a2d96ef68e9d63df2a6dd1babbbb45bbd367d91da1810d271ea797d38e938e93cba986538e938d0d9c729c6838b59c6638e138dd386de04e374ed7c9c689e5b47272d169e5143a814e9f93383a7d4e9e93751a01e0649d3a2700f438754e3d01700a0007d4298d235a37aeb3de07862b96cdbdc199d1a29153c3a5b3f3e2d1c046134fd3ab69a749a7c9d554a329a7c9c6064d394d349a5a4d339a709a6e9a36f0a69ba6db64d3c46a5a35b9d8b46a0a9bc0a6af491c9bbe26afc9368d0068b24d5d13007a9abaa69e00340580038f860bdf6c5a37aeb3de07862b96cdbdc199d1a29153c3a5b3f3b2b1c19452acce6c68c0f3dad1897dbbcb450da84c698b9a72d5c8a1d1daa2be7d8638f228cdc0b9b93623005e328eaa6936ac5508809e1d5947a30ac1ae27003ad3d977a68f53e993058003d74fbd375bf9c8796fc638da5b46dce9a5c7418defe0e832e330e20a20e7d9415858673c64c3f358cc38c255085f8cc69528503c2ff15c8b92c486f53c64e365f85ddacb9fa7f988d74bf0f97b16e2d140349cbd4703bd5c359c7d88071d04a33e3c34c3d93d1ec271e66ce586959c4fefaab35375764cb05e8bfcac5567e43167865b725490128a8839428690c0810f3490cbdb8934500d77bd5ade725ef9b0ee79cbf37cf3dbf25abe596fbef597688275eabdfac08a9b839eb76639ff045e53549fbeba4328d7759c7942461392138ca1831112621c552dcd20862cba90ed4186581a6487739dced9d220170d428360f435ee8cdce88d1b882e714269989238a3d010674b9c47a210c6d18e234e8071b4df80f9f61be6db6d44661c2c31dfbe1af3ed6114647eca6034c802ecd39c864b185cf015dfde5f6e69e4375aa69ba0e7f90f7ecf8d36b10c7feb80855b8b72f46087dda4de15d1e7c659c5ccc15275ee4c969ad78bc79708d5371fa153a822847a6f9e8233599d49beb4677710b5fa7daee910e599812aac89275442d4ae9421250164b4c0420a7a8832c60559ce30194208272c2081b951907941131e920843071734f1c41349b848a9610c2334d470c593253f342929e0881a827c8099b50ffb00c3d4307e9fd3612d7bb7e8f0c41214539cbcb085acb76148eee4a2fcf42b91d389da5eb965c8ddf3470dc83d411e039e7a79f35c3807fb50970eb763314fbd5c3dfb94089b0f9bc46d1dae2d6d6c78e8e7725001d887ba8e0e0ecef7658ea7aca7391807e592c33a39798a62cd122eb0f20027846821ca89345864d093322d2de578ae4919e3719e6b2a2863d21a0804ae37613ed36c4929e9e7943552c3a611d717a114898e93991cc0189106881590db886473b24295cc8b16b8f00559c712c492a7be4737932f6129f89269327cc952b22f996ac9971349c9c7f0e56432c241aa872b5cd4c678c9822c9d0a1596a82a5ebac8aac8526887091da65e903d216506241da7142e9aa2f030554613d963c96612b003172b9021031aa460218d8eb43439a24c0c5564b090e54c02e24c11656267b620b1429673f63389110f5b7688010cc0c0018d12520c630b1800110412515c207d3c9111ab2931431213c9cdd0454d0c35637c7042b2c64802c60c3d6ce14316d2c8698ac674250c2cbec4d041196334e9b0c5122ac0819c3390806189082b98c4408a9c3e976890d10b6366556081a489531a72d2b8305aeeb2cfce2068185e3c410f4948c46c917d21a70d65ca8c4942892921b2e002fbe93cdd4e774d25b6d35d5c74e8a891f19ef2a3a25d56ba4bff9df9e9df9906ea59c3e610b4cbceab0945aa8a31bcb0400662cc5ee028283f5489d142104014e183fe9ce4c6d4c1d1dd64349be302292f489ee0e287353b57b03c3902ca0b4730b950685bd426b5d136a88faa2a81dc536eb36f7779b9d4594c3c34f4eddb96ebdfb37d93f1d0dcc00f5496402246460e515640e54c15573ca1427202755d56c40b6aca08824a88a617a6b024f1650b244a3421d1848620d2df3ebc22db9496da37d9963421505b0269498dcb0e3203f4d0430f538c5822c6096ab8de0310024a2c0a115cc8e00b18c040891b76a0a1d6248aed06842947950331c87285942e8eb60422531fefab0e564b7d2a110e94612a2c60c7f39ade737c89103af51156744ca18a90f6d5e7de370671ead3b9a42e1d53a8b2790ad443ef3185f695afc617e79e33b98d42da57e38b1b837cfee27ce5fd122174eae1dcd13d530f5d48574b7d3a1be672d648c33ad1457d3af73a0f97ee91b1a8619d7f33b83346053801237273ee29b728bba4e3d36b140fedf829fafcf4497e6e83389f1e049c0c75b439f3346cbaced06eb0873669380d457b9a1b6d5ba241d596f72be35872bfd76b84cfa98b522b5cc883a89f4b5d7488e1cb9e1ccc7899fa92c7cf9269b4b7a5069a3e7da3d240ad43775371517ad439e7b99500939bf7e88d8a088a9c5827694526ebdc66db926b5bfae9bc7946b4c31840ee2939d98f00fa37a630a65045c8f44ea1992df8a3e346247521d03f67ffc623557814f2b9ce78a4867f9f1f713908ba1159c488e8f2f086192b62844cd4b314cfb528c048a1460a5584d4f0e929b85e22b8bc7d488d309749707c75fe12327778bc4783acb3a374d98bd0a0975337425b4e7d2ed1a01ca75ece5851c3a891220d7be2aa489d0367c82356890093473baee32f91c91dff441d9f2ba297084624e8113022c1f1881179a48a101d077dc7851cd9a1c9991548703c726487266762a48ebfc4498247335edd8e8ef7689eb9b20e76d85175453aa887368f1a88675539a3d0d9144ef6a464cb97134949d24c9a4a5d650e898b35103f29913d75ee88aa808bcd498265324d020522c3ab5bbaec83f5451807753086cb5e3295f51f5c5d45dcfa2854145edd18644727c8d1110d9fe1472dc7717047e4711df1e52ed1edd2652ec243dd53df967868c753374283a65bd94da214741e17023acf78a44ae747da41ef46219fbfc6237347045d47fc1ce444fe1c91491a22932d91c919229338af203dbdde889ee788d7698836de1259a15731024c1e1dd9f8ca8f58af20a0478049d047a064b660b286fc9c57db18e4e808c9f5a3231b3f5a39e8472c3f0abd1aa13e345cb16cee125d894cf668a19f8651dfb15f2ed732e27a460b40eea9529d29dde8e64c72be89f68a94524a23c0e451fbf41ac0f971bcf158d65a2bdd2847ab4781986bad955e4eb41c57d65a79db782c3ff07cb3c59d4e89b888d899d68edaa6d43650093275f6c99672fd3c0bde2b46d3aee321f43db86d1b277a3f450c88dc175aa933a515c817243623be7c795b9dabf75bd87515cd9755183a55ef033d6ff2101202bf073fef6b0834040c88be6279de9c348461b15644b47605ef5c3d80f3cec3550826a8e3a534c01a16c6e67ade9c2808736f3c6fce4d863037389e37270d61706678de9c348499d175cc5aad4228c17fed5a734ecf9b9386303442d68a0504c83d254ff0d9c12ff989879010fd9e524a69ad9ef7936b2dc137ceeaf8f9099d9be774ea80864d0f6e3cc13acb6d2edc1e77eb6db5eaf2b85cde575bf70b6dbf655ac7fa6018320f79c03f3888e34621ecc4b7b32512d2cee0e7852153855e744c4b90886bdb5d8526460ced092beeeab9467ba24feef75ca319f153a35981816db54a66545c9be71a4da981b8f7b94693d190baeee3d8dea92f5d31eb2aedd7441218a29c647824a9599217a054b0040625254cde0525fbe3c50d6daf0d6f12a9245349cc5c57982731d06280bab51f96c4da87ff5f406a1ffed25e297b5ddb732d0b93b5c142975cd773ed8630d3bbc9f52268977bd3031d7383932ac30d2f6071433594ea8260cc2e6e0d3253cdb5cf352850541a2eebb90665891840a135204bb3bdade79ad2125f9f6b4964f1a5cb07b09c3ffdc10e27f5e982e1cc17b5c7745607b9ea53ac953e58697757313620f310bbc838d83dcb4dcee5ba979bfa74b9eead75a4b4e956e79cb39b99997396dcd969b9dc7d3dd0596fc6d2a86706356983ea96364f7d48439dbb01618a53c3489f3bbc67777d60638b3be3994b1a2e4cb0be6147b1cf341de49add51b1580c095936d4b7b753cbbe3bf42e1bcab7779214df6527f911bae19b87d2971c55836692324566f0ddddadb4d4c47739a9e4f0136a76692f2714dfce53627c370e5fce2adb59dc10440e4da8ac210309982b3a8a2fa8b058c1821223c874abf9420caa2a29a7318220998a2bb82079411661602003b29c4c4f684e54506112660af2883440e040451926a688822c2715104d645e284181171c66a40e2ecc344171c28a305bbe167cd9fa8fe779beecf96f06fff9f7f995f29fbb9008e3bf249480ede8e043f7564461f3f47c79d06b03890fba5d1181b0ef69d8e471b18ec0d060503f514252ede303cc54fb38c1f09e7bdead88bca01c61b4c448300c2a1a696f77c4d277be79ec6b074680f11bf753197e698db8a2e3a9cf2eb7743dfd741a4e447d47ad7600419e4debc6750be68c12b29379e42f57205951f3c21251c630da610aa4265c68a2a6852886c2c511373b5078a88d40f2d479ac06ba903561841451bc28c15b8c61c31449b4600a599ab6812a0a16bfc6262b9efa8eed42e69a3a3895363d41c65377dd2bbe084363d6f0c224e5a9e7d020d50316301011234389d10d6b909072f002c30a54e4132fd4e0f0108df9a508173cf5169d92544412468ca9a10819499f7690e254a5c915332491b483a6522c167b81a494d6f094524a29a5142700fd4402b8e4f1cd4f7fe5866f26ae6031040c434c41c4ecdb7f88d0a28625cbd78a4801518361e59b40b776f1d46d5644d4b2e09661d9e2f9e6f2c633400becdbed0c06dfee0a2288efa820aef81662f62dc475999d8047734f5660c0f3eda11461701308ac5aac2c3dbdb06ab9f6a443142b202e6f8ae9ad889aa84e314fbd52efbe007185836fe75644dd45ed6e1c65c553af4c45bcc005287ce0628d1259de0428295972a44353194e644f20d4a793bce001ecb25cd24e257e7ceab4d56ab731b0e1bb0a19528213770888004fab30794ac52d43174f8d8a98a1efa9f187da0f4ccfb51f96be04b3fcf41bcfb52969405a27f44951bbb9abe7b6acd465de68d467ba9db1cf74fea4e82775bffca47e7663df803085caeca99642724b0cacf9ea2c7c3d2a5b0aa7eabe3a073bda549369a9a8eb521b8d06d920d8677a1394123f9d09a99f341ac42454d44bd919e320a27e551d34a1006349c0a4068ad2405c9bd2a4d401ca7ef67cf955814a3f4b7029244b70966449366bc25403ed09940d51503accd4d19685aa81ca0ecdac6aa0d25a792b3c14a581da49f84338f574d1f1e5542253e60c0d5f4e343f67f872a6f97bfdba57f5c5ec4ceaa777681aa8abb2b1aeaaabeaaaba2a1bb34856d65505a109a8442d1755410b15aa190900000a5315003020100e0904028138301e8a9bf914000b80963c6e469f8aa34912e3280a8220638c310a10420c41c698192a1a07002f751e92c83a4bdaeb979eedb5c708fb48d241c945e54ff49a1368c555fe9f742d849a58b1a11da42ee0ab66e675d3e580d841d6e342d7e06f0ac052d05c128f187ee149acf001e939d7c0db5a7cf9c22e8cae595a53820d13c1d4ce347149e9b9ab68093d4f168743d8dd9fcd3f15d603def3096f4ea0336509f3234030b4c376fff5f16710549d6390c509dd7e5d589399562fdcf90f9acfab43dc511132a36a6168667b17fe565a9039888236966a7969c13c8208d21f24475183ef9496a1c112fb516b2439ae8afa7f4d8a6c68ab35c4f1d9f68e29e055b6be3027017cf18a1f36228f4fa9025b71919e82ea4c029ace74046dbd4921836633a2db83685c538024216c48eaa957f6a79768d966090df8d5ba1e60d2feb63f5b6052904069108e5d6bdda57810a1fbeabd8e532805ecabd8fb32e20468845f083f7fd9ec92b0b47b64e3772b6da6767fb8fbcb18f7460bc2bf80335b1c99722a04bf300ab1086a1752dbfb8aaeac7151deec0a33f02f331ee5877d3f3e830427fd88f01b7bb779bf5b0e193d1c32d173c58dca1c034ab7742c9d2723105c3409d88606224c12878706d1abcdfafac729d918dd39568fca1c61a247170d6d7b9157749d0d53d220a7d28895304d0622ba35fe5a6fc37576dc8f9c8294a3b94e95383d7fa980c3c9161af4765dda74d17fb8d6b346f1d4c5325d7c75987b656bd57a341b36c92d9966e23a5850c3aa83e5f7a985e8aca18fefc812fe15428f93ae569933902a44ce9ed909c6263a65beee84243a7298791958088501470b7e590ade2a9f25380a6a1b4281d45620557137b181695f582d42db0ef0ceba76bd72ffcf81018a64df9b1481421d79d561b4d7f4bf79c0cf3df50261cc1d2a0bee00705382351809414e8d44bd1f2567673a601cd7b2898206b642b0e113a996fb10596ec0ce7c4f9054e8059fb4ecba2151915ddceea8df6c25923e1d518642c09aea47f4ed5ec6311cce2e94037c16ed15078025b2ca351481b1965cc6b20b79a8870d65d310c6e6721bdd780a73a5d5ae6beba6f4f8d3123a52c39139d46d9d22d2a38444c1e08c428bde7b011c37c3459afd1d6774c9ff5107200f3a55ef5b6da55020decebab204c702ab964c0c4fa674bbf278e25d65f07b9f31fe8b65546461db0469a9743352447165b2d847deecfb74926530a6e434bb3575e4daf00d7e5d886fefbc4ca678483c4565e38096abd6d565dc1af2afac32646538b06b4bfe02c0cfa8059dae901bf1952ac666a9c05428b7c550ca2ccefd484516ab69771965a1459445033b37cfe1320d49376722e6b340c3c3ed81cc4c38fdfb7da2e873a351ecc144de01703523d4cf420a0d218d4e0141b5bd2b99e1ddf4dc9b4539165fc927204b1ff38af237a8d8b7ea2d743c498fb01c2711bf3adef2c7c48d0f4a82ab86538a273828884dddb63afed1e02bf8f774a752258f99616293844d9de934009d50747990182676fa1e9d85ee4243531ca5ae6dbd5d4298372b4a7483930715f81da5491e1624fb4901098045f290e07594216bbb807cef25f26fdf1ea5e91ed32938af08222401bd3fcec42122052503a372e11e91eadb9f5502d3f50138c6000a847b02cdd358ebb6bb0c0b0f5ab736226635c25f060c8b42c5ba9492fe26b551cb677550410921a29801b482e1c21642068b6b6a76807657224b247db3edf84499ca04f4e045a70fcf22e1f38ce3f34e67776c77f969ba20ca286b59539cc61257705483f413ae9e80ac709a9ac5d844bc979900231088e68ce3f60fa90af847318b40f717c7a44f29c93697cdfc98cf96c9856def347ca83b1b2838888258004218f76066c24106358460ec284d320785ca03d5d80f05c90c38f8541d0f1121d648f27aea44210d55e7d9a8724c54d19e4c43d2edad0a4663ed076eef05ae0aae06acfa354cc8a03ce5cf74f1d8e3223b43ed05e52e1005f11231b9473f5b966747545962d2b1b10929134bb1cea8a6952236101348602d7af94512ab33db9d297e1428cd3c39b866a7d6e693e3daa068e41218cf7af0d4a09006be841e60cee060ef943e4a35a5da3b06c0bb2286a861a8649b281405dde4fed8e09521bf3dc52953ec3cee2cae148ecbcfdf6391f935af0cc53ab78f1dd50692e4dcc1edc51db10ee1364ac822a772f39a1a237625b3a1e426117274e595832351357ac507a3acb1931b6b8c7eaa4b0e427236aecbd555d548ae1168bbb7a3e28e8a6b6442096e0dd543d2a8cfcf557acdbcecf6b26816059543feeb2e6c11832cbd20a1fe77d409f681c1a9317e25a52083e52a1111887da780bdbf0279b532288c109b1e91418e4effa995e2f4414580a2627eefbb883bfb8fd6789d027f5d1d48c4e6bf46bc758e1d1c7c0082dc6ae534e1717708910ad9f7583cf0393174ff4186548955ae0590d7f7541566008a814450120ca1cc6743416b45e6fa5aba009e4babd733c3ed612293d4155d1fa41cd86bf7d4da53caa46067a259f20aaf17d60efcd1e5b246d783696f5741ba4e5c30c398bc90657abe5445e91b2ec2b540b9b557f302c1b9d6b5dd483d6d98f80b39d83346f6a73f7b764e4b555bcd7a629faea5d40e85a8a0040529842a1b578fac2fbfe9b01de7803c1e0d4eaff3c0efff295f88682655abdc0e6408ac274a68d9fede0fd447bcbc68bdb8fca78ca1ced5e4f058e0c2ed71cff05b092189a3b464830a0f30d6692cf54962cfd0cd6310fa4bd5665fb1660e906fd142f12e3282c977b37938388e17c68e7b4fe97629e9f9750efd65cdcb149308065fae520153ecc548a1e3c505134ef47252d3dceec0d890a01ef3bb0b68590c5018c8b81032ea8220d00001d2fe29ee451cd1b1548be5007f0ceb3a5b589fe850d40884d13d01631ce5cc5ef21cc80f8996c283c6d5e676ea936fc4160f42a84202dcb382be863ec1aebe9a8ce68d356f0cfd45027ccd370f4334cd07cab5e26b614109ec257748b2de36620adbc0ab40c57dc516a8532e1527a8fc63c177770b0382ba8464665b8acfcbff22874b873cd241f82fde52c9d19aada7c3b9ec488b1512efe0b572edc0b9a7dc4c692f3459fd5450b8299bcc30054d5d20557d294054121d715f8e655e09716740ec6720f7117c46b8c5e64aebfa06eab62df1a55865c20a09ffda1c23a31bc37d7dd733d1506d6da4325e3c8c2bacd6d26f33ef3604dcf47dc3d219dc305e1fcc8d447529b66cc15fd06b7e195ce69834ec9f942e6ef6fefa66ca245f8006a192d23b6483a73957b7f1427d9b7ce20c79a7dd4d90ec692999d8326ad0b7bea4061341ebacc0e3b82662dddb8077fc34d3b4f3a9d13279e064d26d5ad922b0fc22ae10ba73ba167280769722fe99ed6d6ec34536621fbbf4dd2a3c667b8311f3c66810d128206c952efab12ee5112540dcf36d932c16b8813fbf2c76deb661d26a387ca21ea251981c532ace274402b50b57d181ed1204f3aaa45f778b122081722c88d746235b4603544f0e2fd273ad38291f49e8343c37b66abf5f49ac3f649dc18a5417f7f07256729c7b8c3d1e6869c6f2aa034216c061c7a4d55fae0f93eeea96d0bb379f9855d95f082ee75b8e050b26f673612451e6781a36ee238e81c1396ef6594f18077d16fa18f77165b7a5f73b8cbb9d4f2942ac992b8afa5bcfbca9c55efb0f7e90d5a64fb6af9f1a8c10c61e3182f2c9a56dda598bc4b3961ece06a93ddfd0f54904d0dd7e2c8ff6b5fa2dc806937cda5bb413b825cadac9ae47991acead6798bd872869c98c54a96ad29d788f9c6b7a2d642911bd3ea38e44c286bcb5c40b25ce4facea0749c2b9c3a2e5bae98a20f3263731e2234e96081ff51193a8571715f5dbe3cfc5fcca0a6e35bf9af36582a4ddcdc36691f88a9272a41bb222905435be7aea2e085d9ee1a2d9d3147ac5b5419c8abcee8f760a8f1a8534fe9dd0e24479f08bbf5d270b72a5a3ea8fe6842182cdd46fe3b0100430f100deea5dcaf197923114b6b60ca47fb9b78d148d540587317ce22624dffef3d1d3185c004bab92c54cc574a7be1c3ca2d7461bc2eabc1735e0c394af641776a1d5c5900ff16aa73bc4751818bbcbae21fab19ebe20a1226278ff4abd83d98a91066d34c989177cb47ec613fa93a4ae258a08398ae32d7570b7e5b0b2684ab050f3727ee7f737661e3f96a521d7ecfabb6c639fb2ce85da3aee32136878227f140e767e71cac7e5de07bde32d03f18df727c0fcfdd655bd466c73c7a9ff7c85411885191786bdecfd10dd783af82fdf8db0a6ba5efe842b0d556509e5e90e7bb9632358f16c5924a92351d9d9011d4a7b90a10b2e3e7382f05e237c6c14f7650426b3e79a4c224f4719bcb09727c9da23076886287f93563f1bc799ff05b78c82563bea62d66f6933ec8310246c7a53d191a39a549f33c5763851dd944108616acae6c23f5f5c6132331b86d74042e06afbb49e72a0bdca601ae6a519f469a1045266c2993173c1807d638cb92b11f4fae0600e731825cec06120e0b805270a24ae970526d8d5be1da73d49bd6f68cab6ff9474ab15eb0518893624d3b04c9a4c6fb0200b24dfc1f66e7e08fa95c1967808805c51a627004d9518cd57b0888508d9058a8ae7d2e0ed93566320d26aaf5264d3ac114c2ce802c3211be2ab9f7f1b6003216854a6061987b37bab82a48b68467059de3c0e2d5c4b04dceda998193176d1c6f338e3d481fbb009b97976b50eec2b26fecd7ec6d760427fc5379c5ab14003d8152340c5caa45b92ac71d1d9ed62a24d490ed8dd4e14ddbddb450a1a0546096a1e46321bed216e7cbae70a5d2d61662c486812100909d7f8e41db1aa3684c64b7efd66f34b95fec04cc2688343721bc29963ddf260d93b06c3dc9511acbadc18c734d39b8568f2db327db2b0d04d3bf828217d156634957362b28b61754cfb2675084af8a0a7283d35724b373f378a8439d522e231d322292324def12b777f51969f1a0484ead82025b2c128fead96bac337ed7c4bfd12a2bac9b0bd99fad605977b2d8661d4a88848db87e2305a2b7a1d93c66204ce4b626256866cc938a255f109fa1dcde9a82eac1567f3ac83a1732060e8f19669c1ff1013761050ff9d49c4d4672c5aaf857680785c48a078390dafb07b0895650a0dbe0b40aa56f827861e5b2669e5db0c2158f144f383dd10a1254c3f3a70ef206dcf06f876b845c1cd6b6c3c12a12eed5a4067325a70d76ee02ec8821b76bf54a37dca496f2bc21e0117afd6cf2de6bbbe57c3f29464409c8a9fab622e0cb9bd0d287150f29e028dae40fb0ce96be529cd331d0770ffed5630042d631cd81941e02c8016bab89575032e2a81415a4e58db75b57ef52ab981c38d10412ade7687bc14720e843e462c6db92042342900290d60d2c363986ee644bab407f38b0181a9cd308d8d33476494ec8c04bc6fb38bc8ec300ec05be418eee20964cd4090cf1f406fc7cde7551168a419b97d9a7d5aef3c1297e96b3a8189141168af7a9a2ba01eb11530537f7b10b90271b95c45bfe1caecc58480ec1d20b868dba5815a992134700e0f0d1ed51ea44fc1c51b2efdab16cbae5b64be156765e033988eec5697706b8dde1e87aa7500829df9b0f264573fd8f5500d609986307329a7a8e565aafc8748f5168cb729ebd80c7ba0a12d5fcd35cda6b6d0e35bbfe7f95bc86ee8853f84a6f395562ddb5edcfe57a8438b8ca2b69fc7fc73693f2256d678cf0147f9ebc891555f6b80a86f9c2acf8cfbf586ec395e5b820952fc9404a1e12cf3e166b0160677059bb10d278926fe0fc47e4e9a15b8b3996d17c966c5a785d9a70a51a562fa611c7150ef10102099e2528d89eaeb2ca8710c92059109bc4c409e6f65301045d7d5e80faceb0432ef4d200084459b1a88d3514804aba52875db457128b23cd8716707f3247788c55e0c710e0f1142ececf59650d6418ef9610a4f0c168b4bf39b3b51dfa611bc0cf51a507eb824b0499fc36d1f856bef2372cee5866e0f6819dc776c7d49e6da2889f4e83942db9d346b687e5d0cb4aa9b113f7aac1651ad6f98ea1a6965064c36360a4fcb88a3ab6ab82d2aa0843c626dded97ae744c75f9f33113072206c3517b982a7cb6d04ea12c051fe6d2026c654a5de7f162b361f18a2816d5b61b572e232fad55943ae3909a81bac56f8f7e09ab9322e66a8c29e8340706bb6ce4c188d86a7b03c6271ddf9bb6cd912ff429ffdfd28ddd2b032bb90d3d6382bafe633126763a621384a1468b3bf90174e7e1f02c9184a96a883a0b4f8fb56722f6c743515574937bb29c146ce50ee94e704ca34f4eb411148f17d2c81ca352c6248064786bb5f420d0b1e845ce8b9102fd16fc9c7210a39a0a42dcb10557f901604c5107c75149f43fde852657ad7179646001235d79685150ec052265f14171a4612e15c24e9ef9c5d140ca414a1786cd24c2a8350e64f0565646819b5e48a84818ad425d8890c443df7e4a5afd2740679f9871ec08394509091e9cb952f5f78c16810d5c2d670f8c71539348e2abb38d3e135f22b483084d3060a84c1c4b1aa6c3c2ffed6c660060f172c3d70c76d5574614d6134a174ff450a8930c8142a1447fea7923a08b13b02045c07c30c893c0204e010f88d3b501600aa9ba554c69d8a5a215859e503220d8887000de8e6032403bc1dc55b4cb5dc34073c7a40dca4d089e2e452684278d43bc2c45149b033a6dc79a6fb2210fe68079984d85b1ce5a5eb1bbcc679ee305ce4ca60390dd3e3aa05b2cfc68ccecd9610382b43fa790216e91b6660de5ce442b4ef760831a0f95ecf403df2603de5607069dc2fa81cbaa4bd4b5096b0a83d3f16faa4e1edea5d9ae6fe468ba7755021b53ee61896c844bbb789a758ec5a07bbab24a82067c5359dd90ac2357df31dec24b11367f0c05cf76663f5137c4f279ff71d3122ef7e640f958d3575b51a843dc8650b93969d95bbe1a627a9de8ff421984588f1348a5252a1b057a5e360abeec445535fc855a381d4b84732e208f3aa3e141e20c48193f9b814fe2941d10792a2d00b2c4a539b721df1bce830d098c03301a45b76734b658d4082bd3216fe16375d459a0e7f8e9b50645833658f956513b3b5a38025e3bd84b302a4d1db916b991c075e8bf32befdbf53f34294833159f301abc58498d4ca1a20caee5872a71193c3c37878429bef1063b8772f9ea84a10558474b1cd5227d448889694dfdc19c42e0769e89551e85e0ec277ea2ad4066127a7fb68d7e467298dba828e24c0548a1e26691f8edc5b46082109dafd49c6c6ad1a18d531549eed449c8a42bc31f7df13ad91ce8ef5fe3ca821e0e744a78fc14ef9a9b9042608fa3a4b2fdc4b2bc84f0bc755841b97d1874be20965b23418ef4acf8ac60599717dd25bb1cea870f621ee8f76c701a6b876b4a02548b21e718b779a969b281846563bce0b00cc63c0960db770c50ef2fbd255877f4baa514b4876c52105d27ad42822aa972b007790f048d72b11e0d67b224aac6a14f4521777c348121ac6ba1b5de2b3319f47ddcd65e0b450d63fe79200bacaa597439943fc0f02aa15473504a5d05b537b4199d00d3bac01b58c8948dfe56a499f6c9a99f96b1b160d973d6b1b1b130ce97633539096e0be47c0c5ab2889c5dca63d3a06bb3b9a14927e730b06605a3b765f49b7ac475e35e7c2ec409e52a6b9f5183bd4a0149d05098cbd9b4843324aa0ab770c623968fbcd01e313ea74ee49f29c06db2eafd48f4b9063e3e066a98ef4c9c37e555b465571171103ea3ab8307c911dd45746fbbf805750eaa7719e84916254ddbc20ed3c2ef591dd83f02f09c547f70217211282bbfc707f140d548905c429458e3a34dfd5c24b3f3c772e0fb64221b787fa4dcc812454d263f0708f4a493edbe1ed84083ea079f56413146b0729e190774ca56e9bddba3e762253c365d2ea2055868f1a9727c6ded3d7961a149ab2b20540fa6e658dd639610f46c691593059ae4d8735ecf45ac6d062d6dcd77fe2a8dea7d1ee53f967aa1a83812a9b4707551c49111766695845f9730d5fa314db87e98101a8578716eb0039c48abbbeb3c17cb595db16c114c56decf790f09642291258aeff494015a18fbd778d902c1f088633ad060d5fb870b1610164f0287060d764f23912b9042a4a2800785b604b1c238c05c8896d05592bd005c040441750ec8e0b35bc014e965751d33de00048a6197a4eb1a51d5bc4048ebd05e362a3db4396dd74b9f1f2b68cd32c91af253babf14f8053884451fed0e548cca5bf8f5242b1966df0ade697090239f8aef8c03173c27fdac08213e6bec0b0f7e9fe02c8886cab825768ca49fe1429973e56b666c07b238e5ab43e02a02dd431ed21e0f99e2ff68b7097748c0be289ee4a95a2c878ef4897a1bf3e75065c814ce71997db8e67ce7c3830b41bace56cca1600f41a26c2b04984ba5928bd08c62e63923481246218eb22fa568319a40c28790f623488affdadbd3314a2528fcd4107b2126ac3a714b02b0bdd952a4c1173cd92f6ebe25429040fd6a297d5357d42cdc0722483e41276146fbb8a00f994fac622cec974420de3744921f261de9ff38181a75461863127a5f4e4706de1ac6abc60d119a829e354ebf9ab980aca1dd88e58851651a0155b473d0d9b071a06c20b2e3e552ded146f0834e3aa951c776331b5a9811d0e8f8e7e16d83eef0479144aba9cfa058e38c6e6babb928174eaac3e28bb8551734d9e5ab04666c160cbca1cea14f232450138e94c69c8b2937a444497d326c1f6efc7b7a168a480f481387f293cad277540825056fd874eab1859ef33e4a147d6b106b5e061fe226b4ea9f705181aaa064e1830d0917fa953f4fad25158abdc165552e852686e624fd16886520137a7dfe53518405ae60b1815df12cc59cb82d6ee8f817ba392236a68a4cd1183bc6cb89964be4738e7ba539c389f1541df907b3c42290be8a149f5e01a18ec892b2efcdab83699d9f017d2aa1f92ac10e6177daa2aadca2ee61f136a654f00be921617beb2d476bd2473d57c834dfa9088de77deb99925e378db6569ff84827ba890eadb4641a5c4265e6aad86c652a22d516c66c4ab8722a5eed5333340c63d6eeec2baa92b1b9a296ff44ef8af80b8e93250b44f923912d266981343123bd50712e611cf490a150581e0ad8e225cc6aad068f9fa81cb0ec2efc00d787ee49cbdeae0c3e34ea2a147246a293ee10399c50e8f204133b9afbce1fd9c30127535ea31e8a5f91fea91fa87152e22d60384c24d6146da870fa90938ab46c88a8e53bce5ff99b3cb166c734ab956c2b95c5a11660deda83846bac956cf0543f7c275db01ad7baf799148f95d4f525949b868a5153e360de22364067e1a19e9ea12725f54d508b652210c8ba3980999af77e08ec0262c3833474c2406e36b44b5a12c0e9d3d8d504ca294638b2e77f4cb0b860ee5b87ee5b49874d1b5223711d0634180c5fe41ae1835dbc072f600ab25e684c5b140786429770d2413f7fef12a25e97d07d451afa2412f7fccd668b0e3027765608757d079b172cf896c02f1ed646d8b7497a8d2b822c0d09a23bc1d60119c1446a1690859d931577a866d600b5e8c2714108d4f090c376ea23fb69b5d1cfde2535d6a8b4c8dad9410e29580afc256c3540fdcc94367b62c2c75a818ebe0c208ed99e07fbc246d5b819147acc6201091b1058506f5156c5b74a535f4db47484da00de45ed81422b7cb3c342934973350ebc9eee78bb1e9ed5df5f8eb20b6a3a89ca0f9b41b547eb247aab8ebc7125195ed5a2b75be65edfe0ec719d1c4f557137ae25edac6ef4d0ae37410f4bc9b9558adf7035696751229be5965bd57837d726eb2ccab20f6e71551779db72f2f60d74a35f958893ad6eade4174a659b4c2a545e8a20cd0acd67a174580f72a1a3d22d9bbcf556511d99832060afbba9eead92a94b8d9240723c0523a1aaeae932a97e15116e2d4aee5635e2adabd9f31b0ebd5924cfaf52f4d6b5c95dab47b898ea25ea7d29496f55a3375d27e15009c9b66fe65631f6d6d5a41d3582eca3db4e55636fbb4ebddf00dc7155157f32d64fabbaa5b26c10328148d5af07d5426e541f3b5e84ab196417be436a8a1b179a0662a0bfb3b1b6ada509169aba380d4684be8fc94ee551d459c5e591fdfc010d9bb93bdc3a719d7a90d39832d75d00c2ad8e8eb5cdc488bdaeeba4cea06e6cebdaac1fcb746293e562e0bdba0cf0a629ed89b96eeafaeae6a46ec3ea2b9e5e69ef0645bf8eb94b91e4262ab2d81ffde5896f9dafee60d441a3d70ffaa5ecb417b5e7bea2142d1b74679a335de2e63a49d6a973879edfc8bf267b4c6f85a5d1b37fe411283ae1e61739824d1d3cdbcacfb0ea4c7da98e28195568c2fd8e552cfd75275f5cd5814527db4dd38976384b122bbce9975b9ee96d2b0b1af49e1ca4e1e58a9f912b0ec6e4779c4a11ed7711e441a5e23b6c9b732e0b3efab219d2c33765caa7a4e734b05059ca36a4a2f9904e5c77bf8204aa22f75e014810d109a515694bb07115c0a53c78ba0d459ccf359d0a46a7f5082133ca13bc41f9191094af5553378b05a55d2519d02cf41398171b501ee21466028b5243e7c5f103f0406eae48bab233ccdbcbca5d8d345612091b0243d962f6995fe6e389dd45cdca3984b45baed03ca8f7aefeb51c7ab553847d6cb14189f012a7eade9375b3c4743f9d67b89f764409f1b58140b36aec80dd3255fad705170766c42344d311aae17da3dd23601700990a04af0cbd394f5a440ddd5712bd2914d26797f38b5d5d498d8beb64f92d2d5aa00b01463611fde39a013b27d8e72b84631031c01f9390693cab5145895c6081072929e321243d9bd668f99c1fdc23ee294c3b03b13f548b9b56853f8bce5eee2927febe0d7690e93d348a2f0fb1fc107437fe45dcc23c82969487783c914427deb5275d3043b54ffac0572babe7c26c154fb1f2c9157af25db93df941f977e88ebeb5f69468df9a9680138d3f3bea0b931b9a709226247060e1137b19cccab433027c6b0a4b06b78934c7b5da1dbac9216a4392fe6e88a16ecd2adb4bf85ba553a52efdddf7d17f25c5e39a20ccf5e15b41d205da9952c4c069f85f4f235d2675ad5b20439b29e23aaaa93f60f2f2474581f6c712da4f5951e7aa25721ab6022074ad302a2e2569166684ea2812c9e035d14ccdd48be527daffa34d6b8c655f6ce2aea03882bc5868a9101b43aee9d2ff3516d0dcb5e8da246eb4bf83667f167bc505e105ff3da91e2666c434f661d97542a37506b3bd78eb3d0eb99e5aca0de2a1f7515616329d3f0b5aafb2f235ceca88eb8c69605de24279d814d0e1bf1f65631181d99b5115894d5574d6e1aba71aedb1ac42f63e9573dbcaca48f3f6958166c4b84bce5e0de99e6079f1e6444dc8aa0f60ebe8cd4432009aac9a548f16c86647605e8a584e41ed8cf5f035779b9b2e7d418260672a9931ea8c8680d99c51bad6f53c86eebf204b8692c5b7c2b703a5d690baa41b247f76a25e4f3dbea1cf1c39758def4cb43a3bdccfd9271b2f7202381ca7744328ab80ad34abe03aabaeffb2ded3646402130a9eb564cc60d28084b779ae10173c8d4432714828846c121e1571bb92a372209294804213db70f4517f06838a869c9cf424c410aadbada5fd46c880340ed3e260486540d5522b26ab5e590958ee47c9c748c949c173a917adfa75d6c45291175c08d711d270498bdbe334877a96e61fe18d5519e5ecad4cfe28dc7719c46c14fd44ed9621aefa5b880e4dc3da8c2c824f6e826ff6443c4730e270d86133067d70499e7e0ddb56b01a40a8b8125b023046c22402db527c007abc9f99b49e28b3f8cd530676bb7cd6dd2e8f96a4670ad7f6ace161cb161663a29e94f747fa1f75b3c9c3618c5dffdc8a56d6382c21493708e4e5707efbc37a3bd468353b051af851f45f26aff16368abb5ae0c5227e1657e8ba18df8a04bd7cc84863d58191f307ae39e74d59e36db04e6d70b6481fc1c98fdcc25a32aa9fb698246a4b1db809b2016b0c37c29674fdce70ac241d8127d9917ea45ddebc5b46d1a1f1414ff46151b4a29e2fe7ffa2c96c36f9c4419372353b71b2e85bbfd9bc26e2aaa72b7c255d10d261f3046c78dc0c0a85a8048b36adeb874edd864cd843b92bd6dd0d9a808f785da9c174693843dc435d0041b693f7ccc9aacf24b6f88d6d20cc997647fefb305dfd8acbccf86f89df9b2879b4c0c2aec8bae18ecfb516443d01ccc1e8e5137464b2c7c4d0511afb580d8ce026dd0fb479a372cfb5042a21e29193c30afe4767c37b7c587fb382c3f318fff581067c17f7dd75528541fc3b4241b6eb380c90cc1c605eaeea3bfc2ab4e316a9e14b6ba07b9ee997decea12dbb80f4dfb1b1736b003c568c068801fe48b51476af0d1982e3d4b5f2c7fbcafa79774096104acb0e36ea2e4a530f8bddad31547a40780e30183ecf57bcf7a7e5e43a35423298061d328a585a94dd0d93b320a9e3443a99aed49aa660447d5ec5c849e82e113e3c5db1569216008594285e21e55c9dbf9fb6f5c8c879768806deb1744b7788a96ba1d1f4862845b9c2f75356f2897585784b87074c0dc95714e96383a5095c69be0f641f3026d7a2ffc82fd5c9f847b020e3b6708c1fe643cc9949a46a0b258370d481a0febd691de253e90e42171cae5a29f0e0748ca96fd4c8e1bb3035f2974664305ceb87053aef186d48a6fb0719b5afd5565dd44d58b1a6aac03831e375845e612f2435ac773f5591631036c47af440e8e4c65cfdcdfe338a140fc03173ce2f46e25e8cd8cec1ee071cac9d40ca99593306d2938b970c0e08af05b3f85e76c85c240723162bbad7de80640ce17257409164d9346cd9ac2e470136b1a0af6b7bea4e36d11409635e95f6409d9a45715933139d9f6a886c1f3eebfd91406f47bcfbf96845eb5307c441bda3965ef88ad94ac9c526934ffd8dcdfe56cc2603ee8dbd6b5f8c989bbf62ba8e6c2af3d5986624d2e1ca1fe3f048a38b7704c26d9780edabae20e649d2f90169eb04b68539c318e86eb9e0621c5e279f99f412151edbdb564eccf1b1c22af8d30d80d6d447c0238cc404c23c624d112da46acdc8b8216caec5a4f501d92c61f542e8ad291fe9a01f960aad20c91b263051407ca607c4e244dd40645e459a709169e0d97fbb29ace1dabe5901f5a5c777728014f44372b0dc7992ce3bcfc597555e9d71eeeb5880b207390b00a4b93d6af5e55edc905d8b4ef730b53153de33d4390a60d019065d5db4cd0356b47997a60b4d01b955ce82c84e5b86d45672a131819711b94085c50b1c58fd2c84fc5cc0f350e674a7a8d18f126f874f84036631c6906a4ded7d5ef24fb8336b5725c9bfd68557048539b5b2c40bd79344a97af82184d9705006d96c7e98d1b0314e5eac9db783c96c9d700f5bcf09f29ff2601cf84720271398f49005c8d3436098dec9c0e28ab2a9b5f85dc97c3b540339bcb502b833203114241ca65201845859f943870314c5dba169c148edda79b2e8f84d461083ef63a28ca04af78db9903da2c25b69449ec7d1e9eaa687714380f498cc4077fc4b6987bfd19248b8df16ee9004013f3a49c5d8caf426cea5c8ef7849040bf4cc856d04d38b99766a3182dc05fa90b4eecb24378ce6bad33e6426a403ec34d99b1b2461e11c10bd8b49413d9a309ff756d6ac44a53ec2fa2821305e2f056c0875d6e7bae95018e438e95182a46352724316499a661cf96cf67398934013e5c61af89eb3ffd1837ce8ed594ddf12e4e99c100339aa676f76e18280b79361d3bf062741d2542cb08b563e2ae70d9ab61c66604717f23b8c8fa47b9845d77980e5dcb30d34626f8ef0055f1a24e4e6415c75e5e90b4b80be492df81ece3c385865537e9afa33c2ff7103af3e87471b18272b438c2f3bbebdcb91170ea572993ed1a3c2caa65a2a07c4a9b8dfa37e79a347e08940d3d7b1f253cb6ab7b398ecb4647025617861ac53c05f20ab553ffd9a66229114aa584a78a94ff3bdc73559ea4f02d0ad2584f5d60798c9e3bfff1009492c3824ca4b20ef753cd9e787cba9c087059508428fba152ea813a92c5fe2c7901f011ce7139349c97c88866fb4b2246c6a0ad9208811b5f38a35959264204096357487485df481d7a91d9e3e889260bed7a241a1e2a2c08df44177e7edf67aeff1627afb95dab6f5740012d3584ec7a8f5ec6090920bdb06b6acd1cf82cc577892ce845f3488a2791bb7233510ac681d13a277beef896d966282f82d8a40d3988186390405ad2c55568bf36db9eb5458537d3f4705ff7f7ddc2f24770c50e29e760fd2ecb9ff474e1a056185db0737411cf1230d7e2deab4cd5ad826b38e76039a750e1061d4f00cc281405786556a73903025315c2071a73403592e2475d3461710c9ff725c7e4b206f5a3ceb329bacf4f198432737c223e298d41e96b46ba54cee99398eab4d400e31c783728400111f0e97c24994aea0fbaa147bf443052e0974a804f18f78e00e7c8343fc05cd2751878f4b8e54f40f9115008763d389cc46fada37425f39324e6224edf161218d514eacf14d3824ec0d684eb21c1ac1c0560b1e422b9e7cb90beadfdfd43bcf2cc77f3c9461395301df9a0007d569878711c6f1de0f1766b7bc12e7a44c5d5bb1b3069ec64ed3154253ad3de9be288d6e130ae13bfe62c8346098e7f90ae053dc0d5cc08a2edfe61855b82e32368f740d92cba04762f9be936f0e2469463534cb9ada90d45472bc30985473d385ac8930918b4884c6e48acf01b41d0d2754047bbee9664dbc07a18498616a0c22796ef62a8b4351a2eab7b5b3ef9fdce028e7d1db5a1b085cc76d5a6a43729a8ccf2d6d901b085f6b41a2874225b19f535fb45a41b96dd72e31d916af339a611995c4e0bfaa9015c7ae0ea8498a1e4437d7937c1d61b498cf598217a72ed0bc5698149f5e0c5a53526bdd8332901decf8f038536729bb16c895f8bac87a58b732e059027f3320a200f23330ff1d56d7031dce7851e98e345b99afd043394c59ea18a03868ccb45f25cf0e9eca4d14000a7c58ea9c3ac5eb84b1963a22dbe916a3f13b1b3fc48123db596746395528f1ac9a6dbb82293c42d4b56be57edefb95652501efdb725c3ec0549eba3483f5a28828f634c91e8cd56b6d506569de1c7d0f47ca8b5135656717a73d621299c993fdedc60f02a15d68a2a8aa6583c3946a54e55a9852328078f357d43d0cfe1ec9009b959b6604f4cf8303cec9e423e45e21c820ffa65eb0fa0f7b576cb77be938f52a16e3cb63993affe71123bd0ad5621d28265fb02898bdc5d61b975ba9cb64d5df2c9dd1198f3a25efd706359f265b7d209a6830366225eb3e347821eb26a608750023d756bb47752eb855d89c25e85b21a425f5fea14957d773d0c4a274bdf9efc20d6285d2699bd9cadac758ebe8bc4564717db22ddf7343b31eefeb57843ae8f624313de8ffcb76db365aa63035aa0ad341d340376cc6c5df3c7f14c36eba91728d5b45e84bf618bcb2cf9cf2b29bd995c96b270e2afb2df524f2feec6a4854b1cd6f9b929f5ce54e6ab4676fa598285001d5d34054fc511eb5c28dc69fa660682d5b5d6416d2f4b92c2d77772939a9bad90044a26fe1d765c03f2149a1869ba5260072ec13617172a170d9175dc6aed5d71929639540ec3024571fa5ed4d1a348b1b571e58594e5f4dba8ba4610abe164578fc120c98225b80be736dc57f0ae2e893db3a0bfdb111a4c03f141e2930cf6f5ce8840be17bdd45e0224c0b5c7af8dcdfefa5db1cad12748a26c1fe6c69fe6989a3af82681a0de31305b02990b01bdaabce676f21ac8630c11fb466a47802a28156cd3d38b3e490e5a6c20be86387afc0b961b0d117828788482c47e971260ef60dea09d35300a0336295767b8d8619d48908f20a73532497d13297c1bf0b9ab6950868428a9aa9e5a40cc672f384302777e3f226bae0b39be0db81ff4c3b99b63bebd4b990db853210301a83ed956827b9b5db3a9986dc79fec3860585b619b7f2107d713ca82aa1dd071a0f08e805c05168ee75c88646219d0c75a541bf0cfd4808e2c7d7c62b50bbd2ee6e171e53402c464badd70041a7640caed574b86dbb0e752306cb07fb9ef55b473fa72b903469194cd46047888009533282e8afc7a36ff48247139a9639f8269670b96ef758cab3410b8f2dd423e2bd91f28d47a80bac66d9c571e9be5b0d27519d080dfcb690eae41fde17a48a94cb121aff95b79343a7000a460b9bc0ad96a5c9380a5b6e8daa181c77a92328820611ec0937124c550b6aed9d558d9148f497abc5633c4529e1352809d2ac9510a680eb8da8d07c4c11b654aa25569b51dd0e2366eeb9009e92d20bd8e66be8d702c1e075c46f4935727e32be955bfb7f0482fb33ff083ea99e026ea6611945fb55ffb9ffa18c3cfc6290bc13524109957048c2d0d0219ef748fb1417b6a6cb0afda3f3834745a53ca2946e11664db36b3ac381197fcac4b6494300e4887d065de3ae4e5e3f0ab272f30e98abf854141ebed4d4048919364cac315c284e45053e1434f82b5617ff3f5cc445a20539c02b8cbb2d06126b1ab75e071d41f03430578305aa54261704915dce14a30bcffa4eb5a717ae478cb005e452d5045b0e968e5eaddb56ef77f01e61a00527ed70165cfa04e8eff042127de058b54559d3a0502d178ea17eadfe157df8f0994a052314f9eb991196137ef0a2a91821688887ece2201bc1a0cb7c94d097123781f3d7a5bd175dbeffee2f4daa4b0e62f44145cfe9d9fe64f4b540f1fa19713404dfdcd266f1490fcdf0327e64736365930144751737801edf11ccb476c6241345e3e1f160e7beb6aadd19874e99c72a71fd3b4f1aa3125096d3c85c8d9860a2e944602c0582c3475b505cd63d2083c80c5c4b41254b165fc02195e858191c0055a60eb34cf04e95e7ee70a255c82e28093d6b3aaf92d4b62f597c0087da485b1d873b77abab3b2d55d056da414145e57706bacf72cbc465079ae9f3fa05a508eaac305f49fcbe0fb594cb2fd9b275a3c472d5e98d520a45e68ff160689116ffd4ac4a99953b94fdf1c15bc550afa7ae1783d8c4de6b6af2fda85ad4b9c0fa8ee4f313256eee5e9003a803b2787176b6fa6d155cec3ede2dbaa9f13023035b6dbcf46393a8ff5e7536ed7714d10f7a52596eaabe9445aec12e051be07237a937ea9360cd8b1340162f863b52665b29ddc17853f8ad3a8dd6f7083a4f4621abbcb1a04d8fd7e1d8e11f07c8ef09362ab45463a80d379c12f240f8d9acfef06e9fe9fa32239b215a58e9606fe496e26a086407fc8494e57099a4b582872f17b57e42574f1959b3e4e48a785f7063313f4e2d43e27081f0205ccce3a56194305c2cfa5a4184b5c96ffcfb886166ee92e25fd169d1f057066bde1649080eb8d0f86bd911a9781f1d6c51a83e217d5dd1bba566185001d6d8073277f2865f7f6b49ea76ab3b65ea887ea476843fdcf95e4722f1da4d25283a039c5ff475ab88b6b44d3836dd3c6d27af160cc12b2971950ab404d1d285edde6f749ecb3408ec12cc1aa5db89d9f20fff98e33572b73c29c435216271195ba075f64dfda888bac5593e9b51430e9ee1ce84c2c4c412d87fbecbb7c0939c9bdd330a4650e2b8adc5391d5a77a6aaba4f7753ebfa282b05c37001c38c33a1432a15ea9739421f7aa39636aba9d5301fce1b8d3fd290ccb829b504b6fc8bcda48f999a56a82a1fa064c7c24e2cfc2fc820aa5e14bf45ae46d267eb6a92c734ae126245cdd7f96a4509c9261a535a35c3038757f0ad10554d20c82bad27f432602035df6e4772709b0f1d0234998ebd6d3021e10ff9e6fba0ca2b852f5d7700393e2710c2854dc482e54c44b627cbd0f83c83253d29c01d768413c8978bbc049dd802946869ad3773d5801729f869f2eeefb551eebee26c6ff31b65473e4a21c6e26a7873711d3170e86f287f92b38d90158d11b0f7461af7f0567ea28ec87bb093eca40b8e26118762f0039e01ee36fe26e147f6376bd7add132e6a694145c2750457b774257a79955dfb00ca672141f140ed9a1ef0e9ee48a74a219d5b1f3c3b46c67287326789ba0f533c22670958f03572da89d39ca0dc44d9096953486421c82edcf3cf365972bc966ef40cf1cfaedea94855803fd4682b4d6c758aaa039240ab39e49cec4a9fd5ff4b64fd0d94f0b38a8f53abac9d6f54e444f33dd191e88a63a01fc1c4e316a8816f81fa94e6751e4dd106c4bb6f72129b9f775fa38a744a7cc61311c2ba59ffba8897c22e1c838d0300c0df80310268883ea6c36e875191507afe71cc14f7d9c1a4d1244e3f0176b3d4af2b6d3c4e1b7332f95615bc7dce29b7aee5acf9fffcf1fa0d364ed5c8401963cc7e2c3d03f8d533e82527cc7dd0c5f92cd46cd9c2dc514010e0129c1be5b8a608800ef8ecd31237292b86179bf6f7931fb21a5a55ad8fef039651dfe21d7e780d363bfcbcb9a365f9d375ffd577713d4380281a7caf1b8939006cda5fa607324bc08f567a199396bf427ca366cae4a281e4dba79b5974a47b9a7122c1484a15a521ac4895bef246f3bf6abe6ba471614780d9071a7886c5396c77b4227510c9e99c68a2a08748e38151990c3c2ccfe7fbf0f3da6777d5aa87aff0446693ce68235f7978cdf9fed07080d894ccf0c968d92f481cb6740e4ae8bb2613aa2d00454f26f5624c11637c6b216e31c54da173135abfc05deae09a4e4732025b687343e289096879f88a030aecba882788c551d25514ea3c1cad46472b9df5faec8560ccf90cd2c7401bcef5756e46ea1fa231b2854f1dccb5aaac8ee027b4640ec10b53f3599494b2a9eb95cc483e0267d59bb5faa5202cf78ff9e3876a3b3d42b78ca0ed7a4c4aecfc0fe6451db9a193bd7be6fa209b0fa27e7afd5497e639c8780bbaa55a4598846602280e4bde6c351dd3871babedc17438d0e8589b204b24987c8bbb3c83c35a09fb9919d45cf0d513c8316677294ccc47f076d0f29186f6527c527e70b249548a563592f2d650a10c06fe78b51637fb2bf549cf486acee4f3b7ed745024c50fd5e6cfa9b5199862d24443daa09b752690cbbd1a1c38d72e48e953c771d3617af0b8066e60ef40961b84af3498cd8f5065b7b2d3bf1f3c5de849f3f56d10266dba8fe34c8f811b3fcd71e51eb5b2e0e9ec376e4c6b7a3995233cfda67f072d69fbf46a24628f6c340950d2a60cb53e3d89665223c9f40cc525ac00f58472a5672f4132ea91445650cb197b4691e5d02c67e5c06c491fcbba4fbdaa2c811052236f57a71d0d4aead06107a382e63d14236345ec4c2b344a9603a6a53c645657d300d14a491ae18ac62cc94a3a30883b621f002b76f4160f894f03882c27f7ea86ef52093d755925a1bb1930e2a44197b527019379c263523a01565f42b38e11445ac9841e9c0b953486dcf0cc89ce023e318db9c6f72cb9cf8034d1cb2590e58a6343a44e1911dd4f190596c581972d31c09abafc46b52e3400593c5cfffe14dc3f3915aeab75ea9ba8a31efb80566cc0af274845aec5d493f17ddd7abeb887aa0b0f8743bdb1c4202ed2ae23e72291cc379e1c4374c17cdb87f2c39360be04a1ce7ee08948207fecde49c076fcf37751489d883666dc604e3e3f772250c13fb4ed2467dd7ff2d949be49869488bf9d36489b726c609b72e0a9dc3dd03a9191dac27a539db0f3edadfa14489a97fae2907482febe6f302490b36c7a1c21de1b974c2b164e40718e4eec9a8de34cc905bd8ab5c3848c2b27b5ac4c4e56ac1eaea47261312a2dcb0a8830ed12c3a5a181a9a97c2016bb4e7a93d5d726071b4b8754a9b980109396d54d44d856c9e9dad2c194541e1c455d77afc9ea34938185a1c2ead44c58885d375193a07e4736d278cbc25c9e911cf38cadfb337da83993e11fbfe49b4de2d7bc9726866cb2aa6f88c226d8109b84f34cd2db5585be26e60b0d89aa64693790185554c811743eebd384edca2cc726a182dd2e2236c626fea0f63fb9eb2a2e6a9a33554f0df26a78191db3461d100f02aaa172e4b8eef358aca13314ff69d5d0c7d3f565d4b3e5c5fef2a0bc5100aa904e7e61097419b1213f197a7f2912b3b5a5d2701df26b49b99aa1a654e4b0982712b9ce7c0c3f3d43dbee407a68fe65b87af9edee5006447415386a2278f15aa16abd3a437ee700e62845b661b0bd4d669233e85a8239b001bbed9a161d687da141b6528757d6fe38ed038b1433358ef82fe13d8e8bb2b22ad4b37cce09f243e9efeda6f0eb0bf3e53ba057bc5c5ea65f8002b0c94a14018f9837cadfa98ea10df2a75609d8a022cad00ae335cfa055421a1119ca5a445abd29ada1dd433a42b941ce6246ef0fb5839961082307048034c6100849cb109bf1fa4e13ef7703a019d2ac8cd66a7f81e9709e9b95140aedbba9528564a6ba52ac7934b242e532f9f5a9a673695ed4e85f3863cb8767173622a7ca03874fc65372ece96212420292a6be4361c753976381078273d75dc41224f380525592820c4a932ba4659fe19d9ea3ebe186d473c33f802f31dbdda35d2f0d22fc2bfa1990cbe97d07a5453b20a55bc0f90e04324645201d574aaf71973fc363c89a4a38b7004bc2faf117d2a24069aa90c9b1dc1b3111a9262abe89c25bfba7adbd161493aa3fb6263c6586b2280bd2816fe4b84e30f8f58e8f784b3f6ab59b36b6df135b12e98be2e030bd160080446231d4c38bba6ed09dc2c0f6e9a4d9c798bccae1498df59689455a64c66d073e6c43f2be80042977c5a2e4395600a5e82fc1047a8da7e7a9bf7c89882ef6e7db70ecf7d56eb2f0020ecbbfab1ed5e217adfdb3847099436b14fb791f07de3b2d1b31984e874d65c2145d1dd842ebf53d1d4bb73af49bafdb32262ecea02bd4424d6453c34a86dbf2fc4a703ea2cd454aa699940857be2f7d14953f3b936982cea5d22073a27a82df7df2c92ef057bd1d5eb0c07f889f5032b49f013ad1da171a83be0539423298b3717cbb8eafd659f842ce94c44efd73f0ecd8718dd4c6388af099891cbe70953949862a81b539c9525699f5f386824e701b920888b651d59031b1c60a13c7bcdd2cc09eb7d304e665474079e48d35b7d9ff9d407de85ed5568624d6bbbd1389358125968606fd235111bd78a87a988bb6e5a6089402ade5e9f1cfc580e021397d981cb0f011e7462c317e222a6fb144f4fe2f5139ee67a64a57de9c2651991c91a98c5e1395fefbe946a367cfcf894a3cd10e106a95bfeca26a0c73772393d501b0e80d6cb0ac9f79f3b4254ccd2543ccf4958fb6554d23c529abe29a6a13ec37085dcf477df231f405a57c591b7e36fdb3274b87e364cefec3cc6e99225777ae302ec994085cb9efd778b3efc692a812c46521dabce3617198e5a5f8476d1672839e5fac8fbc22794956f9d3807d884fcff9980b71a708ace3d61cfaea8e85e17a81291026caad070bad2eec2f589542ed2b4cf9a373dafe6037bccffc1c7c85bcd762c45a5983e079c0825fdeadb541163d28d58f2a81284dfe9471cfefb93cca978b95351c2f072ebece9ff337d4bb9aff2313e39595fa900fdc947540bfcb43746a0978ad23f3c0efc2f49ae6a81c847ed5f089d26421814c84d2b21b03b8fe8014fecc0f624fd51f525b75cb465ef93694a037bc06765882824656adfa58a8e0b836a0ffad35fe65115c6196c894cb8e38f0201ebd82bdb8192064225ed3432d5c4263faeccd68374322f6e525b39672e6174556f6a0a952bf6030594282bbcd09dfc1b45cf8c8f9c1883a43cc88c048ebeaf46b737833605ab5137c3682696705207e04eb0fa77c2f52e87463733ad6c33dc319ad6f69eddc91b09e0d040d2db39ade823a29d18e460d4851307faa27e6c03ab74dcbb317686bb65397902684d347919760c8cc1a982652e6bcd92b373d03dfe06a78f9e55ffd14f20fa52994b09b13a6f62bc872e064d4626f848b69f9a9e8952a9f26efaa0a4a6edad762e4bcbdc6a28b608d70e05b16864e00030b4538cab9632d1fd0b4f44257728c330ad8e84c43eca4f6d4371add9a13ab1fb5589594077b13460a920e2e493d15952b78bca96f66daf9d53dd0681aacd428d51377a25937eef17e7b8d2406ce88c77ef082d1227e1166f2a43c1cec040a2c79bc69649feb390e155cc7e3b16cd9d54d8a635eed63ab4b5b47d1424afa6708faa9bc40cd12558840f12613226cb3438c76162b44557051360a51cfbd01d4f5cf76a79f2d1107d05ada6f57cd11a4a04632870435abffa5664b2fc96667038db777e2e3ed15b4c684d3f662541ad776b92b13f021b287703f0f0ca6729239089723a88b19052771df657db5b5dd44f3931fe0010efb3877526380db9a606ecc8df19f7c224123b81dbc0a2d9cc068198dea4c500e4aad40732edc50c3f338a3fe183d1516081ad0f8894868edd8ea79861cbc1c96501a82926a891227e36a7a54fdd4318337345c7275552f9735a8e22732e2ec8abd688306e6c167060c1bc99d59238a4a57fa459040c37cea4a425705611e58ca246ee1b4124f2f40f240f4bea216708278c5f92bf1e57d9e9b49b01806ca8ce3e59891fea0225e77fb7d349eadab53cedc3be5a9ae6223a28309d76e1d27fce5743b679f8a00ab62d2c4dfb91a8bd184f2756900adfdb2afda195582c2772f32368ffeab16945c3076465900ec82ba7d9e294e1d55fd5ea0abf5175b478ab2b981138d12565f911435807ed223bb35c27d24a8b07e5ffafd92a69c5e8c10083f2d5cd91faf37f48d244cf3b2845136547ae3665bb8ecd3dfb21b033645d4b08395c4fe35be20772ca6d2fb671cdd3cb2b9c7bcb59e825dec2b604b13520fb0c27be4b687de4428e3620672aa27ad96737777d58df347ab90a80207c656a9b139dbfe1d5b7d887b40739baa07fde4a79b0faac11f43ca6331d10295cd135198c13e0f321d7711ad8cc314e5df2a3ad71380974ba298a19eee4bba32705d4bb7b6497615279beb73118458b47f8377332adbd6dc56bef9d6641a19df4aa5126a303d1b5762552e9628978b9be7e0850f688cfe5bcb56e32dd7ff8cb31537f78c4312c5bccbc2964495d8ffc680ca4ffcf4449f0232b26833a2fcfcbff1bd812e67eec3a6af41e9ae928ee7631b35f7a3dabb89826ffab88dd8266c589b96e90aab73340a1ef8cde0ed91687e89ed94a09410c270e37844580d4cb422665e484bae907c9b597082de1da808f61097f9fd6a4d0a8fc1a7fde3cf888098913175d709ac8e747fcb6e70dc1ac1424fcb0b1699a5ac019b017e8a3a8f04407d47b395a1bc9786a50ea4381b7f12261996f61071d59c09557b3405fb0812008db6b99c67fa0167702aa350d2d01335b1c39bd875f1f3595d41fd5a30368730cb035c71ffeff893602ae54d6e1265c7a259bfaf617480d216caf7555ae89a26121da226473d862b44853d0cbdcee12d788679145aa1484889e747fe5d38f6f3af2d0836605389be3d7df41667937cca1e09a720a66459a8b93de29c93971e428741c3cedccf774978a4389eecbac7931eb9ab488899c049f83370a8190569da2405af27b55f83549ec04290d639d224e4a0832d9d393435480b0a05ff27f009ff30c26c27169c5c757edfca4f0cf31dfdc11cd1b853464aac4a0c3cb308a9f33d8369989b2f432b273346840e70ed2181dc219b3ddf6dfd2fc9847b5894809f30497c06ac8caf998d35118ac74b00337a96e8ba0255e2e7e870eeceaf8b5fbf1134bbd85dbaf7d7444b47645747ea2f2323da34bf56263be0fb81b903c6fae48b571ca1084213c57a604040c906d844def77dcc6d283027796a5167f29134ca74dc21eb2dcca56aadf308a6d4b05c955845d8cf2daa92cec1b666ec4ecb1a474fc9a6568c8d43429a7cda22aac68fbe7c64a38784cd2aa88527a5ff209e4e0d445edbfca51cc8901821447150469218e1f7c3f066ed5528909985d5da49cc4bcac4d737f24b328226929bd1f9ebec52142633628b110862c750b639f70cb90167b54614abe0e03585b1b802820793de4d6593083ea57793790bbbd87987223261a73c251e7cc66b25bc9326aa2a1b0bf4fa3e81f567c2dc4a0c6731b4828ae5b5371f9c51310cde20065842b01d2aa577ab775644220796e52436c31bca17b7058c0b4751261adadf7a42705edd731f2607db23e131a48eed93f0d651ea7bc376d61b71b4625d79584e8fdb5af2d43b91ab4871b75df55105d24ef16744bfd78247a81a5890668bb4a8ebed0cde3599489f6944dde0885b521a93669f4deb13a62b0a0720d45cb3b0b96f077383752ce464f8f56a38e0ca25c5029f09d3bdb6bd06d18c6c1b6e88a7e8b843ae794da458fd086e8ab02db6a99427cab0580b6640ee87a2fff6ace44cc59f5d4a9712daf327409f6fff82d13b35b71d917a150edfe60d9b2b3a4270644f6e012394a90454182d92e4a4d7d1a6fa7e8b1da39494cca7970270770d95033c51df4fa87a3bcc96ef0dd5067f447a0a5a74b4c98a90fd8a4b87543509926d4ab704ff87ca8571971b1d7735c0e8c0cbda208f7189a14900a8cd66da1a2326da24218154de3343c874fb9e540c8cf576775966d573c3603e94570c74bb6f9979e855448cfa3eed96ff280cc8af22d8d27590374176cd1a07c234addaa3814913bea05530fe35f733fdfec3760e3c6f965dbe71d7baaa0b018ed5b5b73b91afdba15f7561c40fd5519eddc13559ed0d0dbef35921f68dac1d2bfc5a2e1ac718435b8d9e759980f40552b1a97462b1b2862fc972bded94cf679da3aad7c133142f310c5b69b3a04c0e18851300c181adb3b7de384aa59aa8383a92d51b469117162c85e0ac9b5aaacf248e4c3446378813e34a829cc49923e5f51a60d8df94af45a7f2cc140d8d00a00bf80d89adc9ba5436328df758ed945a73796245def250c38a87575a5402efcf28050b35e23c5752af2965c5095fc38ec074e8962983337cb630e6891307355c5ad6c88d54127dc135748d6f239c085844753da76f0c040f9bdad62a75dbd4c7776d139775b8550518af8a4d8f2b55872f9e66ba16aeebdcdeac9c448805420118d279fdbfe21878f691d561059b210b8158ac02caed2ef55083a5769ea59a24f15bf19bf7aad14403a082eedb7dd07a411401946315d991978547a313e28d177e5a9b5647e6bc1624c35e7bcb71109e8d7921e4ed576881194605581d0296733143d4207ad709a54ee1b0f3cd15830cbe16896998e7da003a87067b7dc6a4e65d270943fcff3487ce5a5d0b126056538a209e42c80645053a185e288e65b07188b364e7434856211268d1a0e9312f7b9030347283b338cc8ae01249e96ff9afcc6386042bcbda30b60cbb2f33ce4624706b88cf4e5fcdbc2f37d3f18834d8a378066382293003239fa7b8d8ed23c5152c3896df1ded8d047e5db851691430ac586ced7dd0f8f537a2f73338b95d6c8ab52be762a12b6e4c43c1908fc110c4ec3092b33edc0b92f343838f5ed63dd16e61e499c42640cf5c64c2699602f5d6871deff01126da61475fc71fdd89eb71fa486db0d25cd043f410edca3a5cd3a7902846c74b12b2c73158408bc411a739a54fa227fb74007e850006e7028a7971c8e27612dfac00b500ca9650475a2f9386953059b33989bf74945875e5a38ecc43daf59e2285b6bdf606b271cd55a6e436078f8c58b8d563b4e82e14d962bc5f3a795cfa169bf84c11f6b189608f360da988cde942d0cb21ed81b82b25d851a8a9620aa62bd74907656263e1293df62d3fef0c85f4c567f4ee5e41f7b00eb567c63886b796259a7ddc820a9bd1a122c41d28295fea3d018705d662f3d036493dc27e19fa04fdf09594ed0bb85b4b997bbf9440e80f8ccbec901722d46603b5f3082841247656cbc0f67e3d519988e60169ed7a9b4d88709ac18803cc706b2327c64f357aac45d01ee0a725a5bbb4e9c30837694dfa4d2dcac6a29afa78377152f5f567178a7ed7b91b1a4779f669d724ffbed9529b26d46be4bc65ff9ff1dccc838bf35cd9c93bddfb49e822e0146804e68a3f5ba8d47cccae6173e95434a331438374be27cac932f3178b31e063c5c91ee7b87b6cb2bac7ac15a172d510ca7ca87cb18fc51793297c3144908b39ce2c8a26eae0bcd3372fcfac88093eadbb26568bee2d36e46a806d52642c626ba458e6907744464756756a656b445d9f4cf9baa6b2ea3b4de677bab2e923a07990958dbe04ee121d0d737449a02386fa8c84bbd490cf55b4715a8446d71b6daf7e88cb7c84755b18e95bef012eb5f6626885805211ebb585ebca63e91a067889138c8e1eea2a80175db5a05e41558270a4acbf8a68f41f90f4ee9b28c4b96fea3451ee3fc699caf9c7fc10401d879ecf0f030ab522c290a4cf34ac4e82106bc6c566e24266584dae50ee613bd65002bf4e8c79163365d534acda828ad64b6a7d8139072b73b7ab71e04f51ec136d128dd0c75f0917f822fcb64249085f2febca9fde8fdd3d3afd3a222f63d97335153807a84d73851f1c598a48968124228a0851a8b24c869565e0622b69d0ab5664e1d2f09f7353cea017729441435a0de93f3c6d07c1763e84a630f4aa1283cd4771fd384485be1a9abd09431088db9dca183800aced7cdde946dd20650f55623cc3d34f3802ffee8711b9039083c768c3ac7715641727953c7be0fa98e91d9a12705a8eca0aceef250c3a8545b32a1b9a731bc8ec7fcba9dea06d922b2d46461be4f4a66d4c8ecfb6378d86977ef298819c35ff8fa6eb602105597444b00a4a492f4a22a669a67f02ccac07b2cd990a9352f0bb15905faef12d541f990c1ea5853da7e4bb223aca5221748093f9d7fe5ae82618a30232c4342e72fda1a93969ebd10836d40990368f08599ab592e3a1db793ae7119ce178785c19413c1a9bc2ac2d1e64d0d70f2fcef39e7cea00d9c2c440ff0051d5aae1617ceac0838f6ea4ba4c31d00e00798752b31b21dca04c5aba0c6e9a3b2e35a0c9bc47616370fb8c81075898282419f40a241a51a8a00d63b4d513af9d9b80d9b4e0e8f9c9ce26d9aed061b6f5e717b8fc0a250c536c1f099bf558d6ae5d30672d2d5180207131987e0d10e8cf48600fd43d3a5cf4afd1eba399feab2d734e3e8a655347996b593424bbfff162d5e9783fc7528f21d591b1c3027b7b40fbc2c804455f4414041ec922b1bd7832726f17021ca6e6655d02fc6202a67533c20f218f20ab061237f20664f58bc3f385dfda2266a013890a96d029e46503c75ba56f662debb4429d89e1e78feee4c3ac31de50003f2d64e72850eeb91b7a61346efc057140392aefb61f82fbd5bcab1a2526195dfdfe95a345d24fa9624093bd72faa58b9c908de8efec9599d53e890d0b95bb3c48961b817a0c51af0589b743166af5737c564cb1b7af15ea2bcc61cb6107c55e82e89035e9d07bf01445014d91921ff8d1c182a5ff0c0461390c854c700ee9d5f8e9a748cf0ef62acd82fd498d91517c8e6047c89774e9f98736272098cf02fb2d9f41e3f153299cb0d7ffe0ed69c5a2bb019a4f514baf947f5597fbaf82c02f0e7aade262fa0f5fff2eeac980b99f712e2175e3bec1e4fc85ff42d9831d9a13db4dbea55c65d2e15ad7a0bd88cc21f9b81594e3c9551ca6e29d22c01cf02152d100f927373f155150d036ceb0fce1609a49fe56eef4cc0f66987a466dc095fea4ac6a6f8af0646a1b25dfa778b7410a9c6eb6d2306470a1dfd0febd68b79611441729e7b72719df4754a587bb032396536c3d69bdb2684ef4c034bb0c8006c503c9c6f990311dbeabd7d87daf46ad9739778d2ba3ab812fc15da691d0b4f58f22f42d604edf3ce14c8590c491bdbcbc534075649253355b480e9432040712d42372d9a257c265770e624877d945b053829f295ac4c13901bca69ca23eda86cf0056e44e6c8fa96631a9f08f830467ba423125e5008651a9327a1524cf6f8b318d37ab81c21a7ee13b2c0ece2a8ac2f05511e83031ec144090f1aa18373c69054421fa01d96105bc2024997057219190104edbf8ef507cb3b2edcc8bf1475e7996b51e6ff42258c4ceccacf0ec945549e8cca67e775c02512c16a729e75808e2bea04336be7e92ba9c6139028b10dddb1887934b64d2585f365781059e90c0c0a42096c3864b682806a00b2ffad3615321fa2d92807578cc1ab28df3de370c62864f5de4406469fbea1685a37acd4d47571d29c6be2494a6ec30c6deeb64abb41d3ee2b3eb87548bd04f98fbe2d7ee11afb920a8caf4c86b64b449e329eff137a4f162116cc31cb0f588f80b9997871533ce20e7352a27c5dc00ea045bddd84ad50e7b963a4897d06622da5db95013c5107c9711504fa873bf2cb9746a3289c172f91b91f31b22450f5ac33441f0f65e062c03842504ea7a53b25920d22a4e3ae5603b0cc54767e4188fe85fb84761d92f5a38f09be35170aff3a8a0e731c7ab202fcca6a272fce944872a5d2110164607f6a691c3411fa2504557d1ee35a7ff7a892a8d15ec11fdcdd65a4208219becbde5de3b860a210bff0a930564bb489cee851c25ce7c61c6143a9a20e44b3a912d2aa9cc578f532c6990c38d47d0eaa1cc42f3505691e9250ef2543ae90116a101e30c656c0522992d0ac6d92a3ab2519d3e031267e6f41b88424a107399cf7b0f6319f5a537185e7bd3c3684676fc70bb3f3d8c6768473decb6bf3c8c3ee09e3a2a41779887118d09c8578c11903833202d326d54602c65d51416bd0a9441a517080003de7268b37c6f2f14e1409473a6941285b410c4a51051480ba629d9f110f7271968479db6f022c43d55447798d3cba028a48512f46419d0c61af91830c69a3e3dcca7c59a7eeabbb1a6fff265b1a68ffa6cace99fbe1a6bfaf8a3b1a66ffa3ad674dffbba2fd66459d217b214924432089feebdf7de4c798032a84a20f924666e6470f2dde00af1a68b740a3a774ba0ee01f76344d2ef66b9ba76cb6f66d9b2654bfbeeeeeeeeee6e0be6d0ee8efd8ad49312818b3e381984a594d8a249602d27814d5949772b23f5f252e2c4a27a148544284cff8dde63bfbe249f927e9540199b2dda20d7030e65908fc469225f3df248a82471be595ec9386ba03402c197e40976f54b9a4e754a297c750218dad78f40e284325b134a39036550aca1bf67e079d3e596637f82abec3d307a24f3267095f5c024d1e9aa6ed10fed988e8f4126433b988232e8d16502794c45ba456fca1e614319581989195f10c98af563b2288bbecba104426fc1114820b4532581b95c41162410fa9b28440a2920be64112e1914ba6450e89241a14b06852e1914ba4aa00cea167d97043ebd04a29750623f356671d14b2259450649214bb4cd6d0e13a5aa24302ac7abb0c4d9f27caaa84ee99e65599665dbbb7a72634e3a5fde331fef4d0ef3c0a19a655f98cad9b52f4c65ad7b9438dc439cbbece1cd1d186adc17259010e7ec977efbeab5d40130cdd2cb1ee3ad41b3357f6b507705fefce759f3785e35eb95cb830973c0b45919015798b79738a94c4fbb50394dcfe99b9f18ebe62e15ebf44a0f989e0216bc992c0984430944be7e3aa048ce64d1ae61aa2b72ce9928a448aca1a7d7e2eb84d8a2bfb929a18456ebfb6666c209cbc09c6ed17ad40181ab18758bc9a2dfeed822d3d78e0a7e8e77584172e30e2ea03cc901468fa45f3d40fc2029094b100eec7003469f435fc40e818b3ee4ab3b5a32c2f3fd2b84e955b40aa52cd7f562cc40483d309d9bfab9ba951dd7db0a9ea7ce47d810f8636564aec09aac2cdf40cadd319f2b8443699457db7b602889d3e5bed442e2745b6448d3668bfd89422214b92fbbf40be6bd813a3ea7a7de037b79f8c37c49baf529f9509f44fa5e3ed555dd0bf2bc3cbb41174231588449a16e0d798fb0a1d509deb57b46b4156495c9caf6092e92381d1812f1f223b28d25a888c1785c00632f7f015d18e2fef2bea442e270de397065a43b770e5c817b17b307aebaf7e42b244e95699381ab13b49edc302559aa9038dc2df7ad4002e9dbafcbbe28814820f22f5f6a26401ef5c40a5ef76577056f05fb80ecb6e35051606994b4051d47624c9b0692405ecc32d62f2a81ba2c518e21d4f0451d4ec8f185180168638839b0c8010e5d50c10551aa43fb56f431d77316f46ebf5095adb596ebee792dc8ec8123e8b20557e87e99dc77e7beeedab7e26eef3d5981c4c9c0d509ef3beec81a187a5f0952a09dfb4ae09ddbe2042414d1b4735f9198bd18cbbebd53dfa90579472e79d93eecb86c1f6e5ab60f5588b12634657beff1e5af1790ce16f7fee438d335fbee331d7ff2c964f5af6d097485047ac951c60e9858628a1a54a2161e4c5182063e315822d667b202895302c36dfbcbfb49fa757a9f3b7d2fdf492275abffd20343fda65bfda1d3e9a8fee9053c3d64bd3cc2500f5fbe081be298dc0cdc59fb0a56022df41db15b1c772e86572776eb26880d0c4204cece6580036dd6c92d26ab8bd87b20ca0a9647a0349aacdeeac0e176eed94369c47df7768804122f440289bf200324900c20d191b92787f8d503b38f9546c19748928b34924716a93461c8e0708c91011c8e99c3f115b33841858aa22c8660a5c9ab668e263a9811450e8e2802161f63fc8df1b101b4d67a31d56e0a3a51c896e9eb0f124ab38ad4bd0746b773dfd9ce3119b2e00eb7b2da996ce0cadef7c0aefdbe5dfb9064f40b1cb3f69d7b0d643264cfa47f77ee99cc5f7055c19b1c4a294da2d3ca5093b2bd3ba52d059d2884cbf6f5270ac129ddbe2279954bbfb8f78f98ee813c26d379b67aebc7ea967d89ead4c72c73295ba150faf69d12b8ca3670451f613b1cb8cac055f69db97d45c11b584eb76c12fb14ec75ec918028f44bc740bf04ae320c64a51fe15e027986764adfce633af723dbbd7b204fa9942d21a25dc7fb4ac7642a954a2ffd6a9ffd2dbd7b5cd59b3a90c7bb29ab5b26cb22b1ef6cc9c674ba65b9204d963d52129dda162efae0b40f75cef4ed3ba66fe04a7e7b0f6ca7f4fef69d9667e2bd2fc19dd97dbe07263d222d1891b9be0211b61d45446a9f85d94d56bbe2b60ca6693bf3a76b3f72ba86cf93fae947f05ffe02f268c7a8afc2380df52544ee75505fe9a4525f1dd1ae3d435d9a9d8e6f53a9bffc887614c883baf6179067680775ed3c2f4f3dc22c3ea15eb454ea8b302dc2b08ddc77fadcb7cb9fc9a25ce7954c974ea4cf64352c6adfe96be05715bcb09cb97d114629fd0dfde95934ebcfaea7ce0b44bd92ada497f4b348dafee6f4280ac1c97dfbd8968ba50f71fe02cf2e70ccf50480a16adc99781cb8d39d097e07aeee4d8fb0ed937986cc75b77d336b5f96bde373d4fb4e770f6432b49d097df70ddcf18e41267def3933bbb9563195ecbd6f75cfea965d5d50e7ddb202562a9365373bf07c58a9e8f8d0b7907d7886bccdf043a7cd9dc166b77427cb3ecb370c5cafd8186a9665b7fb6a965dcbc0150afdfb23f31ac8a3f10cf1dc37f7ed3ab85bf54e56fdd5f191e1be2f83f6954e832bfa7e0f0c5c51905270476392cd9f8cceb0d2e9fbfece7df78ef6093299d79e3357d9fd56d9ef6dc59fcefd62d71ae9948a94698c2f9f18e41db021fb2617e6f9fcdcc0bc37b9b0a9824d166c4aeed1a7c90d2ce63f17867a5819f07361310fbd05fcb06031437e6e60310f5fe8a7927904894049999c6651a4280427b71166654a737a57bf4aa7cf699d7ef1387d927ecd9cde64fa301ea96e7f4c1675b1268bda3a6cad32fd799a2fc2faa56f7a9e374f633ace993212cf2dd347afae6ab567d1bb8614a1dfa1d5caa36913d62ad5680b4acf4d29a5acd2afd46cd1d3148d39e0295bb1865e7a1f01648b7e7e3e4d0e8b12016129479980f000999e0359b1869ebb61496da19e034d88f9ca4b50a77bbe043da82f46c7c76775d37d781c9732bdb7a998c01d73d491c41c05aeea518f302432de513fe21d553a0fea289067be04ebbe08bba9bf745f7d29d577facc07d31df59c79573a9e573a8e30fc45d8ea8b301e5f84cd68a7c3dcc727759feede7d5eee837ae993f956f6f81116f3adec4dbf2bfbd2e7ea16bdf7a1e83bfa972f762bf5c56ec17cb15ba7aff34e474ac7204ff7d2eb77c4bbe9f643c1bb09e419dae95e3a8fe9de95e7ba6fb5a4850a4458f72544ea75ba8cd52dfa1a077d167dbc97aa95c9a22b7b17ccfb5616d461750bd67d11d609e17aa55661d52a15e9d473e74b0db4bf3dc21a5ca1909dfb11fb0de401798676b6dbf370cf6e4466f90a44d895738b4c2f270a52e79e933a3efdfbad562870d77e64fb0579ee8776eeb7f368308e3efb62dfa8edc4e1cee475c933c51466b95af5db9d7073a8d386d2a44dc7d8194c7736a6528e075415638cdddd7d43e96d1e44e263476d2ece9412480eadb5d264ba22121ff1a53694ca95b54229a52c9a158f19954c0c0c4de9e60917cad9b4da4e7e25642eebe4079379e45476658eebe487332a735e27bf9bb952273f9a39130ba9159540e27b64123277c2d854f23018dc8b8ca83971a69c12c8a9312d35a554956976bb5b39196b68d36ddbb44ee3eeedba9f042b99674b5dc599534e09a49b52109b32dcb44ff3a56321fc0196ef7704b76d05fab190ca8fcddd20a65e50276c2a795d6aa649eaa5793aeb2cd74bda3c9dfb54372c392abf138db1e6c65ea9e3b0e92b799f8cad3aedc6759e473579754a38c004ac8e76b7adb260939a953031e1cc166605ed6e9cf650d35621b871c668a7c8aa302be4bebc54eb3aef84d9ddcde33aea751f5742ae1438e5fad508a4662a909155272acb53e2c808a43ed6179b4ff81481bc8820f7252763ebabf3942b0972d7c8aa5576ed4b4b87806be5424ac9715ce7791d57a55e9b2b29b8cb755ec944c25327e17b216104cf084f4f900671b946e8e119e1e909801e349e111654ea2300011040288192e4481f86086000178ab3544368563dbc13f00058de0f166bc56a754849646b761e90f3f30712145670213424d6cc5f21648c478b005d9004923ac85792961b87032c7df0f07e0214a00b5abde4782e874dd34e5e14e04707d4adf94a850492507ed44820f294407187f84ad22d1ff2b4828b05c4d61c22444e2c1a92032ccfd5f098f18cf0f4344096e11995678404270cc4d54d89231f2f4a02998f1e9e02cb1054c978464870c2ae12ba3b6d64a5e2d2584296c5c078465850e90128cd19428580eb904abd40401843860c19121b105ff7f38249a217396211a5d82ed0044a62031c24251363278f33e46c5a53f645cabe12274649e73d65ca846a1b6eedda4f89fd680442eda3cd7405f9a3b22b3d6e8f25efeb382ab1e76ddb26a4fb91535187d2eb6cdb86ca42faa92d66995b9077c89e4ce33b8cd21e37146cf8d4c5e4f8d2ed64d7757793afdbad79ee9186fb3629a5bcf714ec08c43e469bed0a2a654251596ee9c27cb139f68d4a706aba90e51be7a87d314eac04c7749772319d29654265e612985e35737a699fd7e99706ba666b8600d81eaef27ce1ee74714ccc7430a917542a9b236ca1522251f1c39d425a9882c0208588700e2512b20a298fb284a3053ed062471969f460062cdec1c30f8248da82049014b0105f91445956e10150d64ea78d4744cbccc8d9ad4724dbb2fd81850a1918654a1330451ab97bce2b91b226ad1d715c2f8a6c14230833945025e4f938451509b4d1035212192f10238e184517330061e50926a61411840821e01eb047a4e518798641642215f1502a438a2ce8bd31e8c8f4d48b2892e04ea0c40e36d0e24a1a314c8185ae4c291165a091c3113588f1821391de5319641162898c070f320a27609ed73832a07023f7c8f4b70920caa8830654243147cc1272bcb1830e2f6cb1630ed1060d57460145115d670137d64822085464107a01169b10a28635ac3c81811b38c0e69435d0b0b50179665123cfd97509d041080829705851441111962e414fdce0081d888019499d6ec94f2192ecb072609c658a3ca7c032852529d32ff2b4a1541691691d9926894258a058606ac2ed249628f2a45dca08da4d333bdf19a5b4b36fc26277dfc07d4a6da535247d84241c76f5315261c4946cbf0853757b54145147b6df8451b08b35dd069e5de7199145e81fae3efab0b94ed59f647a4ef66b95d365500a34120808262bc90b701891327d920ec4d7e917882dfaf89910735b20be5c366164aad3af075c1ce4304691a3ec529b10852c892414894296d8ab4e884296641f04a29025f7a1ca4814b244034004a290259b0f0944214bb8004c200a59d209800251c812ef31d68403c8f4f40f884296943e42ac7921d6d0c74fc2106be8ed27638835f4d9277d3e2943aca1d73ed924d6d06f9f9ca1a3a1864c2f65904102352863b1861e049345534832599c0db82f3f04ac37c95cbf9511b9826ab228ca4a926913ba6e6e268bd21718ba26ebc6956990c9a2234814143894b124d3759344feb0543f240e8d2a0aa9a79f89af6b52154d16fd8906999e334df115a37cc1044aa06ec53e2593954402a1a61c91b8d909c195728cf6466cf5a7c4a98f9cc48143e2c4316dba78e70ee50950ac7982c3f8048712883b02cf679741fde2c0153d2773882f99145bcdbdf40e1c0a89e03830c6812e98cec3bd3b93a11d7c0cc6ee1e901cc20d427c491f4984dc2f7dde77b5609b031ea22f112056f2c0ee1c141c4a9fec2e08c5a30c24717490384f244e05fb2b23326b79843ac2a1f40192484c2ad24a16ed0cdc9dbbf4913f5188f6bea421da482e930584c8923edc3763dd6a19c764b50442b489b12fe788ac7e5f5026c59aee82b084024aa0c99acf40259305ba208174652281f45b281285c82e27c4979c23b6fa37372594d06a7ddfcce44099d4ad06ea4b21202853a8d04f0a0149209dd49f3f495deeecbca059a6c7b3452fbb5f69723fc6546ef9e514992c0ac310380c92a5e020d3868411563c66327da8424caca15721c69a5006658525afbc72260b29962b06dc0f5926fcb8912041e2b73222b303e62b46b6e847c8f42c7a170dd21d15b808983359946b8241ec43e2a42410da31b6e82da8d32dba051b65338091a6d9e03ccc01f29c947382bf3aa1d20a8394aeda4e41bfea91f44baef32a20fc5391729555d67bd319b83fdff5575afb22305f1156a3add656a36ec34786a25ce64fbf528fb56bc6662cb4b79fa57fb5888eb624d591347da4119738baccd8fcc97d3b6997a4389228a5540a8181638679eaa12b26c5e83572d3f9d3e36bfac8f593b1768263d2c83b71a96f5561be550557f5d935eea43a7eb9ea3933fb76609e7afd508079eaf508063894b19fd5230cd68f31e7f123325f813caacb9c07c833b4a3bacc795e1ef308cbeacb638e44751926431ab8937aea4c5e0e73096585fb7ad5875fbea1fa9576d57b602b0d5cb2f37215c824820fb1ccc30fdd47580c4c55653ef9d3ad7e89f5eb05bc571a751224313caf6384c6dcd6de2f54a13e54217ba8c2bdf65085eda10adc4315ba872a780f55305d85d2b9090373020a0c4fa92fc2a6cdc4f918fc4a9eb67159756140be5e4029740a86574921f97a01859acb6409f54b6e315b7d29b4a54a9f8b0257d0ae61b74c16e70487d66ec97d9bd4cf6278a2b470d107b7ca7d50e2f8a895473d1759b701cbcfeeeefe0d9149d554269b3e8d70e9dbfc0dcb1a65df3e16067d7e6e60a537b9301f9f2687716fe28275f76e02e99b5c58e9f3bd7d3f620d6263cfc8810c365ed1074b84d1860c72d0011c4930af0212b63d67e24f96be568c09084bc8f3dd176309e12b4f048409c8f3dcd661c1312c8d1061f5472381cc2af33777da2ca85a5f8a362388465bb45919b1ef3788d3af9ff11561159525d03503f7c39b70fa3f269836ab13ec5d9365634f9fc2b459dd2e27052c4c9b98e56317084aec27fb5638856b5647108db2edc9aa2e0af6f9646cb2a805b241f45688de02d15b28f4f66014627500407cd91d628b5ea57a79c9e133976f812490ae8a058afddc20a892c0524e1fb44f7f8993cad307f8c99a4960190ae0ed8ebbe66fe6cf9a70d4a06e4d2ecbdf695b90393bb941c312f4d4a09eb64f3b5375b89fd55a5fc12a44bf3a65b2e64c8e950dae9f4a02d3c74b1c2ff77db088a8c0212956dd26385541d54b1e4134ca3573f55bddfe4dff2c1f3f3a0ba27aa55f8bfa95a85621aa449528f75d3767193541cfc9223d6bac0a0cf2358298c4a19916e9168d6de810db504223a74424246c6c63c6e29f6f286351e28429b04b3fc856b6e4c34806fd7d21c75789635fc8992cfa0a466ec6623f40b11f1a62b1582c1693b108e81409114b1037bc807332259b60e6186bb8185fb6ce39e7b475c2a45e26aa4f139b66c9ebb8b969f366b81b0cbb158dc0692bed9b9e73ce97ad73ce396dbdaaee9e73fe180355e5d7986613d4c659be2757abd3adf9396790b96212afc054d05701fd89c662fdb98aa00e0563de8957600db2ba156132272bfe32176b49ba307be6bc42d0b4484d5e0af617e36bb55d29adad6989c1616a5a6270a031b3f722c5d8936f2c99b0b66d1a195946eb037ac2a627da75d36e66378d8cda37b31db5e184d2341f2f507d147da9d4b696a524e0192141aebf5f8871dba048636c78f94b4adaceee750fa82006ad97a230b4376dd3c8688eda904aa562686fdaa691c1d9d0b18dda10d3c57844ea63a80cedba6937b39b4646ed9bd98dda2063a45fbfc5105d2ac6a59766b68a416f66ab1320b6e8de79f5d28cdecc562757d2a657ca1b5bc6a8934b2fcd6c1583decc5627b66697da9a8951adad99137a6d7572e9a599ad62d09bd9ea044609a4d9d4f66be37e9570c9298c8167775f6edbc6654131243a3762b95662cb89f8dabe813226a5c8b108c48a745de7554dd3823ccfa3444230e4691fadb55aa954327550ecc6e58a75c923625fba78ea04992f191434244b58801ca1e4ac1571641409e43e06e59fca6641518282e6b51b83228fa4510c4a3d3ee7bad94b06758d38322681ccdbada98d81e5b35c99ac298b741081ccdf692485f27422d6cc5b19abb9416925d6ccc7dc804f37d334b9a5d80803c7df1cb16871a29b8b66c0f6a87ddd6c6da0cfd0fd7c130cdbdedb9b6098f66d035773dbbabbbb5b9b4912633addadf54bab7e2abdaf44c6b836b54ca7babba3544c8b74ab29c77dde4f5ff719eddbe9dbaefae4767c6dfb12fbd5d4e6f7b9c0eed75a8be8a46082eeeeee5a6b77777737fd6aceadaf75779da08ef69877641ec3bd7c72a63eee30a7c77cf8f593f9e59330fbc9ace3d12fe654f45ebe38bbbbbbbbbbbb63769ab0d02b380b3bb23c9d4066cc70cdd082d3b2c466c618593bba148b192d38007de5b0cb0bc8f5d70869079b03a46872fbf811c466480e87e43b06516e329ae426838cdc6550c9dd65782106d2c477087c053f8a7cb92dc2c82253faeb031bac327d06461837b432bdbd5fc829c07041a6148c2c99be7a44283e03a7e6ab63b1580c4a97a2f1f14336c9619021996689346ec9d4cb618ce3470e631d3df29450224105a55f647afa8b5567b9c8c8f437394574ba0042a64792c4874c9f829221327d0ab8a041cc0ba29f2dd32662e9028d202fac10929269c4725447a611cb1751647aea11a140f001c65bbc20cf4f0ac71af2e2dbe11b5f118605d083376e07649af33dcf43f3ae34abd32a593d3acf8ef0a292c1a899982ccb4c9b29dbaa649ba9cb36799cbc771f01638cefe1ee237c12f6f2f1accee3a88f676867751ee7a1b969a5fd48dff423b37add766e665956b3da73bb8f7927dbb60aeaf8cc5cf595e948e6799cebbe55fd08ef81791f7ef71dfceef51dc864a8823ba58f003259bdf41eef81715ce5b86d05b4a58a0b7218440a20949002081f29b9ad569936518a1feab06282cc758502c63d402634e0aabe37706705ae50e89bbe33cfe3f33c5efae9d475198fcc9eba9770878239e12a3ea577afd854f2e29d2f5162f7b1890be6fd07c3ba298f02559620113e4d2e4cbe0986f5c3ceb1458ffbe5f3e382f59b6098774a4fe7dbf3c0952655498ad458e99426a97a743c609e3a5dc5d01745b9a6fa5d936ae6a5f79879f7953ccffb8c577a0f6d2667caef73819e0108901565597d119d0ca460025a84b4465297ac76394a8a23e9a85f41821c05390a12647baca12bed0424108845518b4c2f8bc41a5ab4dd8416eaa5b764a5813a33251d1f9f98afee03731ef58b58435fbfcae5ab6078f5a86ac9f482f528d65c1466ae3a92d269aed3e3a58f67befbccc733f3d24d8f8f46f5c56e0d0183cc7c43da4b1f4fdfbb59f5d577545f814c86ee99f455bfe08ecc79804ce6659e3355e7f1c994beeede77c09df9ae33a7fa22cc743acc87fff2714f7da8cb7ca5c792cc5977999cfa224cc7c747869e580c751f9fd3f17d4aefc4982caa5332e113eaa72756a45b14a91e1551fad2934d141a7dfa29658d809c524a2adff2f600d3e75823604dffeacb17e9170563e36ec5c965c290a38fd0c6f276ee6e81b5df57a47e652a55f66560dbb6e42d1e2501850f599061048b971e9188bbb55dfb88b8ddda7e7b80577147fbe5398f0557f196037778327015c155dca9d75eefcd4a3c590f2cbc793b977d31592669ab660ef3ed29f40b7f7b0afa85fa76b965db697bd94c9b8cb283f4384fe6e3b67bf4fd992fc2e861beceb2ec34ebcfc874a00ea5adfa222cc24aefe9efd0ee974026316f701599947a60dbfb63d19f332222bd9507738e76db167f2b97ecf64734ed56035791477b06ae62846db75fce16dee46ddbb6edd4d3c9b6da712a26709697d06c82cc4ba807ea64194cf7e9c88029f496e56d3b927ec57c7becb845cee7e527e9ad50c84ebf939d824c86ba3329812beeb4037760de20931870c5812b14b4d31fd14eef79e8b51fb92fbd04f2f4ef230ce69ce9a9d331ccb9ed43b21d6f3f6d476d7fd99e326db09e582cb66de7b62d6ed45ba140af1d49ffbef44558f60d5cc1c6e83cd5c0451fdcf67ead60f72c7cd38f54fa6a02578f30f95d9ca4ef53e87b9dbe47d21883ab4ee61e31d39c36127eef53e8178f5fd5afcc6fd24ccc3502cb29d2d31e3d1d1487d2ee39ae47844daf02f83bddf4d15cd6e3cb406f3a09abf378f4565d0fe9795a5ceeab4e27d6dc7337e025de4d882d7855c50447bbe91159c27dd5813a271a1d1f9f99afeea33a8fa7708fa45ff4f73a51c8cd3756ca4362cdbd8a093c733824dfd37b1c09cd4778f47448a85c48f846e8d109e1ba0658b74cd6bd5bf23d773ad1609a8f87f20c75e7a9c7f5f83bf5f81db8436f5a7d1186fa224c5b7da8cf7ca9abbe97c77ca5cb7c30e7f19d503f7d2b1dfc0a4418369dc71761599565b612eaf874d33fed9aeef1fde91e75bbe4fbd43dcc054af5c4ae067eb2eee571b9c0b29efea27aec340121c2ec8e760dc4f61a78bbd53044c0d2852eb96a9fddd99e6d9fdebbd5158830150d789e3e54b140b32fc222855d29e79cf1540a579063ac913d67151b304c8e58ae60230ddc9951c7155dd45004e50a1c58b942685ac12f3962b98287a9c5153f4d059be6a043609c86ec82f117f3079f8aba087756fa07734a4839b03da2c88a28d305389523162b7ee60dd834470381711a34098cf2820b6cb39864e03bc51c036f4e483a3047850261ae0832aa0822081818555185b5da8401e38a15188f70831b5641403d8ee05144e3ba31c061c675e30a7263c49515161b74500101c4c375b1c040838cebc615e4e667e676a92146882eaa2d4f6082380a02490667b1432a29c6759fc082c38bebc615e4268a95285960f01336b801454451ea092176381901f4f204141b700c504f00fd98848c4e4fc47043498a10dec11c35783f1c9976904443170324624065b6620e2f4e63861f6236146d51439e1264c50c425469a205322052220f6124444326e2c787a0241c6ec85292b0ec000627b988203a7e5837ae1ce44628d6ad78d9b8e107a26e451e20c18051003168142f72e89a30601414b307b8bbc1bc037743730011a5e30022072d8080a1b3604f08f3094cdf6825b005422369692fbe00ca42065cab9048f866996f60ee0654075823923e20623a81b31cb1fc10b4056b3962f9e10530605828b85066c9c9adf391ea702a1aad4ac87107a81b24731e2e5d53274f9dfc5499436d91b954bca2820969e69125c8424a7e314be6646260522f2fa853662b6d1eb81871624f2981c47331d2b6944a39e79c734e2dbdcad3049a4ff39833dd2a2a5363c750982c75fb86d2eeeebeb9420331d7478f03979c80412226ef26225dc8424a226165b66acd553a1ce0572ece80c10537c061c3131dc0ea0f1974308428551081c50fb05aeb3ba4ee9e2c23d7d77bdd1781c9aa47c951eb7592283141ce000bf6294c1beb651988b7c919d9aedd848d03c3ec28d0a696d3f53132e0bbea8f69336d3290fa6005b9a9a679b281bbf1b5de7befbd5bbd96afab09517e25baa1b9cfd201acc317ddfbab81a1eade6d0c9cbd7e03433c421038bcaf0f2bd10d2d68826e21b93f26f20c2c433f3017e40d4c4441033644cf55a27e7ddd27ea578455b992bb978861cc008a2662d0431c4be4a08223d24032a2062e50eca05588ebcbc3dc81b3e774f68577857ef42c1816c936bcc9597c16f6e4ac43989b5c2bf863b2581306576293a026504e5819c99952601122160310175760b608aca45f3a462bf457d40e814309c5d5adfebd5d0d4645596691014a1619805cfd52a924a7244949bf22eceae0d8ea639584416cf5a514a1044c4289354b40e9cb1f2529f8775fc0e1cd4d8425e2e7300bc6d9a24b8850504699acbe09314b28ab2ba04869149ee6fb78aa7d751ff3f6f88ee67abbf2316d3cd7b459758f995b71ae7ef9f02107963ea664a12284850cae98c28b9ea12c340c495143d2141b4a1f0b8b22dd77bb283705763071a58932ac48a30b0df040063ae812a50b243736ed4a992f927d3f7c18e1234608226e50c41a5ec4ae88678cf1d386166bfc6c7106966508e16305094b1d61c01c1005961940a1450617ec009b77dd58283c82206bc71823555d33039bd1dd3d678c34968438a594d27aedbdd7d6396777539517ddd4d6309b8ba65faca1e006725fd68af1e468cd322e2a17b66e31bbd66e1cb6682a31c6b8d6da8dc316b5bbbbc034ca0cca6aad9946bbabcd7c5148983d00ccb4565b3d231c08bf13be7153e6e2970b2a59d1a4c8852173514c2a52a24a9a4160cc3be79d36e0538e597cac28820f152db02ac72c3e51b2f8bce043bcf45631ba170b9ec9314b0c5c620c465022ac0a9478b36cb3d90bf8de23285cee80d9000a172d31802dc1a5a807580db85cd7134de6c5b7c34c602e493c815129e2e2c30e30268e4c34dc99658816040168ca10863065cb0072c4428416584555bf74b16e54f2b254aea42da63cd5b823cf39e79c73a6b2c0f004153138c9fd4fc5c619332facd1c571c409020f3c6060801c763134bc78a189dc27c123d2780d187c73ce396d46a0436867891da145eef7c0734e36aa909973cef9a2c10ab80306b9bf82e30998225891bbcfc323d2375ee10697120acb8b76118228c08c0a8a8863c71c79ce39aff2884c35e0f064587664d1aa376280c139801d2ed8d2aa158aac0011f42387195e7c4007357431658c28b82051e28d36e69c13c60e4c456e551d3ae4eeeefe135d8a664b9ef31713414a9e5711818e3c8f3a123b8ea0b8800c3b7ed87204091cfc9071c7143c202272438e533c7944a8a4828e1e884cd7e753850ee8c0820e2184e8d9810b164adcf1859520fc00d6dd3d83dc6bb0913b2677774bd93bcc540f0a30c70cf2a4e327cf63d559ae1b2ca902e4f0c71086c8f343e822cfabba2891e7cf9ac327cf975c7db9051c544adddd2dc4935d8ac6c72bcf5facba1c6d6496942fb4e040099f2237dee876c10fe6b0b283306ae8c20958cf2472271991bb5f2402dddddd1e117ab40a4395c32063c48184107c840084108880458e2e8280459e376be06046f38850da81394c49118421387e90fb570547504a5279931b8e2472df7a44faaa6820c20c7e6240a207467054dc588207666c39030ad8d19c436a6c99734e1a20622af71b41bcd1456e38a4e456c537b27477d3f860334248e3c38d29bc480a4a6a22cfff8d17e479caba54d540102277777723a1472c7049a1a916d2641f39fcd1051b3d60438c368e70638635ecc8f3d3d5c61324259ca08183345a9003c50f5d880083157404013623529611c94422cf27f24c61d161983b70c8ca52c5e3898a0729205001630d1c721faff145ee4b9514f2658c1cfaf891860d5aa431861a4ba851048421ba14953736681f92ea44d59c32e4f9aa135bf30d86b7863c1f39703e4a95ec215751b39b755bf6ed6bc185ac0de150c64a9f6f03975e2afdf49d929d3ba563904909348137567a09bcf28c19a821831e6250128bc15c47c196fbdc3bee71b2c2ec2c74b9e3c090e6ee1ec759fbd8ded7c28fee76d62d371bc54299ac693b299c7d92e75fa5c07dfbd03e21808f3db77944ec8dd53332df359050644cfe74500765369115e26419a193d5fd15a549a6cd7c4918320b687452153628082a398c4768d491c3b885841c462e31298c71d0acbbfb26ccdcd49340b747845e02b5808b3eba548337d2efa6f4f426c8537048be60d84781ccf436fbdaceebcc2399cf29f20667efb52fbbfc26119d2bc17869a469910f230efd9c728909682622b63a2261f51256c19f0e56bfeacbbc3ac194fba64cc40aba63b722ae83b3e7a6903c02430a214d23a21009b6161f4f704881a2bc3d05a23710a24014880251200a84baf784b10929caa44fa116ea5817d1b18ebdfb06fe7e664ad0586b3f83a290ecd674fb2925da501a22cbde9ac046ba0a772981e10fd905e581e10d2f8764b105c3677b497f628dbd07865c1de1c137bda85ff108894b5214223f26a7f3c060fa095c1dc13c2726a69f40ece998c06f6815272b0a515fe9a226ea2ad65a6badb5d65a6bed4d4e111d24497c3cc1d6fef64b757b962b15cbf1934b9bbe1e18fd49b2dd25dba657b4e9c53450d3344dd3348ee3b82c3914a96ec8381f381c2cb64135ef9abff3f50c3b4a4094871cf2bcebcebfa59474981ccba59debefebceb2650679e23c77c8f3ae3b7fe9e2ce711bd881b40558e6985599052bed391fe58b1ddc7dd0c8a654d5612a1b3705caf41c125273e9a4ee4263f4a7632dd4b17ed5d37711d1a6bf88aca37e55b089fa0fef8f8edd7b338cad4a55a7e0d9f348e24cb08926185148834d833e4aa289ba7499b1f933812694195454647484c4254942e921aa7225724941490afa4561577e24d0cd4d4e119dfb7882c329448455fda2f10acb45d4d3680acd2ae114a24059109c8c1e88421a0c650c59e24850fabc16900318c0c2986548906c5e9adc5d303c2b4f0b821d48c15085f3a4d743c232a75f49c02b57a41097a45fd227b6e69d70791225c9f4640cb1664a24e41419439e9753f27c366b0fdc121370f65949f608db8edc679bf608d3aaabfea6fef5acfaecd3c96e0237a5e6068c5a0b6647e49c19bd0a4c5806baba15a336d87353b8c0499ec9710a1e9232d79dab5f1326c11c09c4de1e15854c10166baccbeb38cdf4a5a05b25d0bbb7c39853a9b6df5e5089d6466604ce40ef0b5c7fbb1575ec91d8e7d817b1ae2978fef6cc497dc04927ae6093a4a1a69e634d9bc89aef6f7eb1adb4968e00546bad3ff0c9b5d64c4a4b25d0939a53e4e27eade8fd91ed573bcf045774b347349e06ef7610c378b8f368bf5703573bdafbda6bad2d303007ae76eee7ef6badb5d65a6badb5d60cacb5d65a6badd92acb6c0fa25a6bf50151aeb5d65aeb118565960ec13a0ea8dd11755e9957b28d77daa4723c0f2cdcf09065b75455030e25950c6502c5f840afdd5e665f4582fe8acc326d680426cbbe3f7b67db401c597163e292b57e0e982c5b3b2f67b26c0b067802d53981a2109beda55183abecdcb7dba33cfbf34738f076cb7e7b837ddb0203cfcb2a13680675cb5a6bed75a450b7ecb66d27620365956e59fb9dcba09c22560a6596ab5bf67775af149a2f9a9824b2228b22962d45d93e8a91add11d39ecaa51163d18c561b31c7b3bc401b6883c64dad4cfdbbbfad5b757b82f9a9c410710d8c621441bb20e7c7f500712cd82352fda086cb39836c0770ada04be3ba03f60ad4afb802d0d6619d822415b30443771460c9c9ce1823ec3a7d5a043c2a1050e2e5ab840411a023ee2414b1632982ec795e8a0e1078d1dba7c814392154ed8006aa2056c74a9810d44515e90d728c2a2450b6e504387dcc615357608e2820720206521050e998d2bb8bb4fb171af2c815739629182944397137cc2228a1427c29042d486942957a4e4d03d9012c318d80d27882e8d416e86dc683192c901a58ea8242c980a9b182a4533100000a0008315003020100e874462d1681485ba6c1f14800c76aa467450970ba45110c328ca18648c210000600020c41820a9a26d00847b6b0128020ddb83f41b19e8719d962746b226f59aaa2f1baec8fbaec571efad7439ff382dcec3a0cb93b35207a3d3e90a71ac67fa6383a2037d13055532547de8ba807ae399f943884c38f1e1920e21f60abc26ac6e68422429729f84304cffed49c3260cd244c79d10d55ebf86a49426b6ac0d0b03f6e1d54aa67ec4a33e9dcc67e5116723adf2d2cdae1c6a0779268927d241efb5c250862325efe6bcf7d70aaf454b7d3cf410dc859ad54ad4520e2104bd07e89220157958681459bc4e0ba02b770bd81b911c607d831c707d98e345d92cf6e3387d045123f0fd174a47af91db47fa7d54b74f853d7e83c917e059594494840a6b807cbd5f98f1c0741719359a3d4a9be956e247d82cb060bd97665f14b99cdb851f48404567636ae15b7c790f9e0bfe33dab8868a07e298eb7c1ecfba5f580b32e8aa6e50cc3ef31a70181a916d2144f29c4f4dc4a11f25d1584cd8d25b93c22c677ab42515c482860a1781dc2ff276daa2f6f54d6c2e1c2ebd2fc70b567987cc6ff3c66a9206fe96b9b05bfce592ba28f2cde0729d862a0aaf8275c15b895aef932d38adfb4f1b68d3867c1504cd2b1897640a2844481e4e35ff225c70f6147d599767489bcce61254d7a50751fb9c533369a07790e1a63065c862a2e12251d30600ceaa2a3818b2a27b37c29b986c6cd0d7b50057c81283412283968f8e410b37326819c9390bdf82dcfe7989374a715f3db6f3c086e681a2fef8b8c4357f2999e8a0532d7e1fe40177d91e7db6a418aeb9d4843065fbd3e89706aeedd517a469123b4b7c19de14e4314571ac2ad718c152afe532412c87854a5bde1dcb6eb593bc9685184664541d4e8d2f2b5443ee9265301d852278af1cf129d09313e0f9f8724ca1043eb4dcc86f8e69dea2b014616d9fe47fa782be0c3b2c3cf298472de81e99c92de0b58b86512851b075bb1cd37a590380c56015642bce7f403e35d690e5ebdafc9c4d01eeaa31978b3dd162d56acce273e24d72609951f2acf15aba4117442f67fd71fdecd6c56815bc06367b9be34bc2d3ddb5950354eeeb51fb4e5cd55984035f5dec9f0320080d96652d15e1944277536895b158577b60a98cc11e344de02b3dda58b23e66e4d5e5580fa09f30b8dcefd19e6934a8108b5dcf4506c3101785dcb4e42d662c8ff2ebce2df821d4ea6a8d8b41fa55cefdbea06cad513ae55ebd6a68de5e5cb61c2f7c28e9978c57a5ce838c02cd661df29c8d4f5847e726f06c56db3e3e39a0820208c8176c803ce2ba89f2bf74605989309cdc6ed3e1eca5ce7a9e4dfb3f153426488c65a3cc81cde822dafa58272d97c6979544bb650227855bd71dde2917e11201135567c89481b5a958e16ede08028494c3cc18dea3b4b918ed1ee54eff415409fb197521e2133b6b192be82778f45879ff37878c2f175372a0dd5a94879ad440a4d7c9ada82819f5773d4eb96f7bc10eb757f44f49a8d650261a38107b6cd74a67ee718377f1669f41fa1f1d4605ecdf266a8adca197be84a95d5d83636d81eb4f59496d2a943433bc456d2019877b2efc731c9a7494b74d231d8ee2d3ecdea6d4cd5d18747486d0a7b0408c050723d4a6d6bb2abc63c05faf605cf03eb82068ece8a579d17f6c3d767b159be05eb414b7eb6f3623a84cb13814cde2d79b60b88c9eee893f53bf237521313ae28357f95fb8957c37a2d1a2d5b0efda89ae60ff9e1d68b43a74028784e7172a2ee8b47ef47b39529f94e484ace3dd580456e1f1a567fe207bdb36228fc6e22c9de4c75b00c87bbe0979815942aa8a524ab313e5a015e02f58514a651aa54de4628bb60b4892f6dbfe9059ac5218ff1448109b2a7fc1142370ce66d2389335c3717b0b074b41f24a3ac02e9f0d4e2033430991d81bf1cb3d2c24c18f92d033c2a0b26498b3e1a102b11dab56522a4e04fa6d5122e3e663f20e8323abff23e922cc493d480db50ff1bb7e3f12100c19325e542b8e9f65aab2f859aba2c3d272d7c409f12ac2b4e869e5735d25d635c10f5ca412948fb948b4ee9470228a7f88baae5a3c6b36c5b7a0beb69022ac8650fca09a2a88b92e9b52968d59a8be86eee706afb1834498fb0bfb1032f3ddc7030336f6b3fd1615623b42082730f66e4817c31adc8fe2cd1ce02446a370c5285233184556722ea27535c7a39d102b39d5224616cd3cf2b4c9d6bd732c2535c601f144cdaa7f6625b861f96936270e8e05544b5de440a7d34293d3c1649ee2b94e1da28de9d59a5e1e752f561a91b63e33d734655d93e3e8474de43e9fd21b082c682296c4e33ca9bf4171f5ca5cfd6eebadbc13ab7ed259837710e1aa8167d9d449da57dccdd0b9d184815bd1e66f833113e4d6428d8f552f23864ddb4fd2020461aa8854308ab4d4f17f17c2b2566673112ab813045f75f5a7b53c470dce66b282e06c7057bdead702ca23ad5574727ce718eec94fe8c69dd579a5f6089e5b176880f98a3f50c7171fa66f16de9ab7c3e6ae775d170dded8e62fde3ee89f59d6c7862e76a0f31dd41c385f542975b7d0aba48adcab569330ce0d8e1f94318335489f409bf11a14b69829f46dc54a0371225027dcba72d8c4eeb49f411d533a50007a8fee7ca1ab83eef12d15b0e82eda743b01553b4672144a121164ca48a78d8891b5bd290081aa9d84dcf30364b92592f147800465693408e7b352381d1a9d788166b44641fed08c1420b3e7cdeb36f0cc4aab99d2d94acfd9e4f1151d6730dda7c23ceadc56668736ff21a29e1f9de5222a6fbcb1a24b300360b35428747fcf3970346d9655ce086475947641561120e224e41a4964277fb568465d8bf0ae1c4f82f95cf940c72f7d865fbeea06ca344465ff5265b21ebdf9af22ba7c63dea76e6aa121009e6d7ef06480b818544a7fd578033fc827fb96357da2e49a67b64b20ee6c9c8b9d1bf875e150718de9443b92b52cdc9cfc0bb6868236897e43b3d7e31824ab3fc6d181262201bc85ab6509281376aa9c98f0f6c92440c5170baf135219f39298a341293d7d4989e1a1548f1b7ca67453cc41bdab1c70c9969c679c70b1864ab4e40174a2530d9ed3135417e63a8fe965de1b3e6f3b072c256415a385d22ea731047122694537262855c7b1237f32e19f2b537aae8e4a45402ab270c050d57973bb393258213f9686dc43fc82a08c9891c61fc229bbda8fc2d7bb258911496bde646ae217f2dbaba0707eba7e4f7272440a0506085e45963d212b570e0a9684b157a58c150581b5f458b2dcf445e0c8df41ef22b6bb4141cb95ba2dc8938905323305f2651d0cdeba796b9ea688260a45d0e684ffa525837103e67ddd0a1113422e1f5f7833779ccf91b0660ddd6085d94e138feb329069a0e69070bb2f27f0124c98968c023c5c5f9427d546c6fc19f6545596c2fbde3b2ac485cf9cfb27ea9c99aebe4236a1173bcfbac56ac85dd805a96caf94b81b0c9e0cd5a8de517e88260e1fbbb59f07946dff6d08db31999d182ecf6724141e94ce2ce6559dcf317de38287828bb9c1451af9ebc6019c25e814419a2f0763d5be84468db8584791c61f424e9c52fcca8d967d02b16ec10e29d4ed8d384a8ffa0bfe301205e6455ee798de14a562fe65d615b446bce63d88f06e2ee45ee49e87fdff83c3ccf159630431c31c4e4eff0eb83c6000f2dce07094afacb284a17ef2eaa52df9c0510a728e3907270f507bfe023600f187b8d84a8ab62c95cc143ce8545cab4aff640bc215052eed7b11c78ac144888e27049a740b91d0dd0991da6bba884540c2d44a313d7058d5ed9c8f6b1ff41fd3485b3124b06d00cdf256f620e004292c49bc8581a248f541a73185c8b0432841e97dd5ce6c786328812f40ff7f5f9e07c2a625430e98f4af66e15e3005de84fe1da5928713023c86c042357c83f22f25271f82ab1cc6614f229b23bb803bd4f1d3f24e23fd019b30c570eb73421e0923b7910e15076dd54e08dcf91f56ab7721d1d6c01c84ccccd5bbd58975aeedd5b52d9205b00ce67674e0fdda955e421fbc7065d71132f6ca35ee79262e4a5b5b4185f52bebbad82d555319f6bc7b919d5fe3012c5c7789080ae5d00ddc9f22460456d33a350c837f90a050c471fc1390038daa7e2629e496a511838f7cc4606dde0a1f58fd0251c310e28e003a060902ba1bb4f963319db6621b2164ead83ec4e47c990ffe3020ae255c1cb6b16773421b6f5eb6f7d488e36bd21dcbacb6435b6fefd8e09fbebd10c1a6b54b6ba977875244a9ffa4eea491834651aa2bcadc858da53be622d7da9993ad9e4b984ab887bcf06f2d991f30b9e9a8f11bfd9534e41b507a2d7452e0bcfe94eb3e8e5c50750f44d1df5dd9e754eeedd115392f70368c5c950bd081aea9c6d198b7a0c79503869a1fc149ef740aa97c94de69a28b28f449566c4e23e016721a6b3bd84422f28d36f5881edb61115a6c31ef5f2e4ee2d3135711ce1a164a09683949ca975072e1b1f492f8abaed120779bf375650f4285c5cb78f3821a49b6965c38c27e9d6446f2a7f0447de7342d07d61ace8c79a967a135edd5ead80878c451fafb21b9a84c823bd89d3331b56cc237f4fab401e194e5154b82b686929b08d4236491549f568daa1ccf2b0ea3e8ee73ba5760e34b7093a5867444be94440a9399b6a04200754d6fd44a3db7b8a3d245ae41f5f4fff4d390eb49274bd055b1ad899ec0518bfe492c2048318e08460441cfb9cae45bdb09527bea58e32428287153679a2c9b6df508560d4fbd38750406fe67ee3a1ab97eda2a3f3f059f12dd1d8da2f39a4e6d7e49571190e9aa8a9f093a40f10f35c64b5da72fc1e3808aa6000ee29ecc231d18a006fe8eb203cedde02f66c46977995797ae9615187922d1d6723f16e35ba639017334e9d992e1b3dc68278a6b36cefe0bab837c07af63f18405459c2d210aa2fbb0cd2090167907c27a0c230c9786048d176392acef4ff22a3fff48fb35c2cb324d375bdb2aebab18bf44b7ba52671eb33a0c25e6a1123fd329a40eeb4b7685eea885e9fce1f5a2582e630fe9dd35a5547b2bbd6546a594ac48a594141946b2577e31b1a78860ee89a56fe18c24da6b149c9a436225ebae21395e371c7e728dd9963310158b8927c7da97d75cd5afcf182c6d0034e07e8da5457c3e4f9b8b86acfb4a6323629414f09ec3b2e233a5498f01a543e10017a9c825a2a201098ad361e678c99cc6445c820dac609a05d13dfd9c9fbbd94012b9a239b6cfab35d8a9988bb594516f74ba30b43bd4a20a99f29f5ad561232c3bd4647f25faf0562479d7595ae2921a5a5462a8380dcbaaf7509e51d22a564c44f7621ce70538875f97bdc180949b93ebdf4d42c8cb8ec63c401145e121c690c0d0beffdc3e0fc16022f14e29cfda1ddbd543e02113a273cbe30853ec7de72510b2f9f4520c1233fae1f26625899e9679682b37016b9df3044d28f2d9b3262039b5fdf141d0c262e96dd9c9d513dcf9646935afca88f45e15cfad0ca44b315a804c806212209b50be7a6fc943df6dafddf3d7e2e34d57b290ee0d261fc4f60b7061259f0d8456007ffeaae4402b0dfbea0e0e722c1bc0b5776f9497253dc23ade308c5e93764ea6a57f4f444a3da0989d7c3779dec3cfb3929414a12a4cfad813b760cf8d7e3db2b81e23c3c6c1563a819c0f28cdc74a5d767f6109d52e97612db0ef2bb874678c4569829404e3fe12331cc5863307d37d1e9c064d20537b34a1596d6069d42c8556efe010f2e1d18a6092faeb56739d10f497894e1d02c83a4d74d217879e8a88318f8fdbfa3a764722b96d33fb28246e38b5515d08ba46866e070949de1b977e68f422e7849ba660f124f02dc4c369fa0ac8c3b939b07d9e19e8addd010e68a6add1024e17f88e610b5fa38c7a2876c04a83f1099314ede4fcf7d57127cb120bb433c362d4a6091a67d21fa783df68d00391815ba80e2798b331c6b47939a35cc6d4a2f405d3cbba5c6be75a0cd62b87519b6bc567b4be4b4ebfa588108b6339195a3880f97062b61d3a6994fdec27475f372b16958fae683b91f95b558f240e23a4eccb087d4d52fd3568e63ae22888bd2f40aba91d8b80d86d160c20f5c874310e8b43e9fd07c59aadc2a6084c2f51c657653bc39ec776d4df00877b11d00504b811fa827f5d26b27b03cb8b434500e5c8cc18dbb984613bd20e9d0259a1d4b1f2e4f7c468db50239c30456e5c8870faa60b1ece396cf76cc3cb39f172e66d516d68c33c90ba78c653c3c85383dc8a5b3307ec24e0d4f5a7cb616f4ee30389b82fa8b8ea726b32bdc08bc0016ada7b1b1a300d299d2325ce9d77561829b3211123815dc2ee2713031010591d6417bfd48b745ed8194b24fe1e1b67d26d6469a200ffddfa6ecfde2980256dadeb12fcb830d5d47e0478ce74351eb067e40ac3fb25565f00b373173e6547428b36369a697a944c514af595a4ecaea922f5a20d2857a48af8e28d73193d1eeb554d3a403c01c47e7d0bed9b8e1339a1cfe4b347a653722b3484ed2ab1ba5cf9635cd48c6f65a97a68d40f4b87d200caf4017c69c7b4c1c6128304acd32e4afbcb4eccd213c6940032dd3114e6824ff343dc08b34eb72bfcfb4fb58235227b3f79cd77685562ef7587f29132439d1c30a2c85e590b83f1a83bfd01b32f4e66d735c27027a1c208abe60c1a55a1a65371f00c508d4466ec4e98d479bf272819da0d60989524d3bd211e725d4400f03a146a2f468809cf404b640d2f69dfb021fce442d4839ddf6a75359927384288e4de7aec48675e8a9e8cafac7a8a36cc0ac1a2c9598dae8f77266453f60c1e3520317d105fc1150d1fb70305dbe7c7ad4e79c1d7325fbc1e8484f2a8640088e548063a3959e59692d07ff243642f0ed1641ef08ef68c4b80b5eb37c63ed4b6c4157d1cc74796329fa6928fc1108ff4851c049e79b9a34141375124ea6cd6eac3f6b8d736b3c370c8a78b91641991af87b0ea671b81a8fafe60b1d8dce49c8b79966c42f915d5cdfa375dad10c4029a74b6cf0137e6839b272030455fd571ccb0de1905f739559f7231fc2cf4a0f503ad91815f3bba8aae1ea6846730c8d8c3d737c2096ce9a428e1a0e93a7fdf83877ef6b602250f75aed32311c3ee35734e2fa90248937b47103e8ffeb49a9c19d65cdf54646b02118070f46f03639529e2138bfa207ad7e0a1ffb9b4e4089df58b5f14289635996c07c8771d4d1b071822d424961e4f0cfacc5de9c3a536dee1789bf819e18d2f1932404ba47993f74ccf06fdb327c6aa871e5b208e4dcb127155b6a9f51408fd9b0e617bf2cd706b0b80f9099472d7a039656d2a1e9e495c9a7212475251e23ee98f25c87cebadfce6e8298b71395a54e63836087b10c97a89c8c2b572927f513ec750f2c971b816e36003fb2b8930b34f0808af1323a28eaf67801004f550a495731713246d071f91da9dc2daf9a7b0fe88548aa62a1445aa6b7ffc93a5743879b35b23444da2e6149be041794ad7cdacf33485c7bf80702071f643f5c167dac740512a9d16e2e8c83f8c0151319e7f90a632addbd41e42e0127224f18de94abbcc1527f9084059480cd239d2dc33f0044ca50d2ae65da49bf24f776824f0e6be97e540ef065655c42fe069b1d0f56ccfe606147789be950b60cbf7d79197f327d9e6cbcf1e25e81dadd3372686f9c06ea54cbdec32062b7d29872c2b908b5497e48b545b0dc4c7244ae958dde4c794b072c89c3ab1d2ce73697e15341efba8654bef526cef5b8cdb1f107e346ba990c700c696af75654818d4cbc7506f374d8e88d929770af2a36ad96f42514929eef4beb8686305cf22a22c95759c71d4712af0dc2b8a605985bfceccf3ac638095ed5a9578a70aa796d3d1ad58131eab8eb66fb7b875af97f6167958e89d97447808528862a656572284c33480c6f3738010d06dd250088efbd1dce732ae81fe57f7eec91e5dc7e6dbf643313143e674bcb3c1a5db3e1a7c1f063ad89cb5dba7a43dad9dcb65521138e74373f5622a101ae142ef723d36c4d79f1a49155c394a70e7a726643fb8abbcc2762070b126268b39356b38efad7e2a6bd831a6a1a1a8efec26ea6f9e154049a476eeda6cb3023c5aa10f8d526e2e10c172a1acba591bb2356eb1c026df288afcf5342eab321491a2291974b9b32c7514007caa3cd5313de93840b0ecf032fd170ea93991340c4403ddf00b69deb2e2cc161497e4f2e00da33e53aec6238e355e359548d6e82e548487ee664840d71c1ef5fc70c0fff9f856850aeadaa9ac06a54ba656671de111217deb4cc50f68ba5604a50650f4b3378d00829838fc957ebcb2a2bc42c6e2d60d4c3d18fc511b4345a9d6eb28dd75d462e06f4accc2587f41072aab82a9cb14ad6dfb7d30599d33454c5dcfbc9f21502ce2ea05a459dc5bed2813131b8f5c77988dd72fc79218e1854a6084cb61bddfae63fa2ffa8b52fd145310d253648088459f15fd704c4cb7c4c7e97c31791eff10c27218c5ba25c0ce804c043d464bdd607eb16f2e71fd723ad2542bd790a797d0780ae5d610cbebe8e3d4c00ede13167b5dda3ced6d01a098f3973515c9fdcd1a2a717a5953a1dcdf02a05248afb52a20b931dc979bbc13ddd9ffcb5d1e72efb684a6cf5fd6543c7497b99cb4707f3ff0ce6b9b043119d3ab0a5524d797e40c3e756fde25b70efbd5897d58462938c54d8183611ef97594d6b7d8100c388943ebdd98a147e22ccd2a99e08449f3ab4cc440f52a043fd0a909ecdb939ae114d1a328bd23f230f7ea5e23deeeb9c4bdb92416b09f95d84faa68af474d62dfd1443cedc775964f2620f2ab8c069f8f111b3852a17e9e455e95b10efdeb9effaad8bbe2d0062d4ee9485bcc633f9cb8e2afd1a9dcae45d67c4cd5dc39a6f86a1d8a8f3420c3ddc2f7bba4e825f5a7f7ffeac5096280767ca3ed01ba771984235398aac64a4017751ca0aedf5bd582a636fff5fcafe76ff4c4091240bb1bffb2171eca474b47ef3f7ad87334f3c734810d81d66c04ee54cfef97265e6cee457b7987a631c20036dacd95f6451da9d81418bf4ea4dedef2afd7efcdfee6bfeecdf64379b1e39b6f83eb8d0c8e0bea0400ea560216609513530a59c0d29fdc595b3981653722d8d37940db0bb9774a999b66f6a3682d88e0c5e7287385a3f0ff6d3b6091e1c97be2011de621354974a2d9d7636587d0c5d3990e6f2e4caab1267e6eaa8a4195a53b565dec0d2a7542a64266e85cb1c6700b9cf02aa89284a3c97aecf7f8c8f963db0cd8ede12b4ee1e40cd71b2444315bc654a5d218cce8133a5622632ede0ff3b9bbf3a9b7a67d206fe6c7a5d8f219540455a32fcee5dd42a9915277ce9be5e3ed89df03d00a7f56f6161a9f3d0a1c841cd629ca9b640d99924be2e33dc8b30646fede2b35df13da7fdccd1f1e1d8e3e8218f87bf08719965f682ccfe0d91dba572f5ad720c4865b84d3eab53a1db0b7118af154a4c8c6569ca829b079fa5a627d3bb4dfdf81c0c3a350f8305020b47ecc0e5b2f8647a3901549b24f47fd5cc40ffcf5cb9a91e5584413a848b9612e1aaa836c353ce484e2a4387e7ce34224860b0025a4fcfeb4e3d9c584b30bb6c33174417f9f4ca20f7068f2cfe7df0c985818bb54556e0b36090f7013dc45f827ec6e73b50904821c994a27610c51b64020c75cacfa8da02f69e00adbaca3621a97dd796fd5c931cdff0517e5fb59add9fb8909a987c0ef52f4d470996dd85c3ca2e7d9765149b0e789426acc1a55f82e3abd32cec2f8e484d82f8f984b40ad2f16894693efc5b7c4d6f527562b17e692feb18062159f63fe94e79ea9b694906cc8e11adb3a65b6bf72bd6bb40a1375c0ca0916b745a2bfdf1c72b53cd4e3445f601f31b9f87115845c129941be550b65ebeabdeadbe164104a3a69b6aeaa419f54f1578f8fed84c79f2a45a7d6e32a8ab6ecb0d5dbceaa2af554f3f6149be659818c78d63f16dbaf3393acbf5deca42ad83d293727cb43c4b668ec396024ec4548f924bd16a306b8a695d20aab9bc5033cc32dbfb6a906bee5c59405efc1953c7ecad1cb147e0720a5614b2c26ca147bd704caa378b582ac57476c24f8ababe3a1cb169d39a50d9b1562327a22b9fdecc9841954b5c5c49a10fe1931009f2b5b55350510e33dce6a178814ca86732656ebc7cac4b2ab86459b3268470cde912c898f8a895dec3734a78a469ea8abd78f1d4b676dd7921fc294a1d054f598a241edd13e95ea9839776f85898b2e471a76a978a1765b0544a57e2a8d4473f995ca656a7a8367cf8131631973f46adf94f0066740637d5ca6b080ca3b4f6bc1ea3b2311dac6452bd555b7c9ac3e0292c9f70cac73826aeca9f1d513cc28824abe5d5e2fd63f46958dba25ee1d4c251d3d51db6653edc85a95191958e70454e33ab5bf94afe524768a937a70578bdea9bdac7cc7afb0d5bca13e47e643596d3409e3e03b7972a5379e4296f6438a10fd38b12439148d97f86f9359903c5d658e49ae9705187319194e46d8d32ac3db187939086ae8076773a6d0d24a3ff2577f457f9d5cd7baa8a5555af62fd49345d420dafd48062ac1024dded51173cc5e5d61dc24ae196d25f2034cd285fec6e6aa68a348a0a243dbeca9e3dd0f7c7ac82342a7c8784892023522136a56de70a724430e4ea77214948f0226cd75b110c16cc4fb1727237642a98ff6d024e69928b4d68d263109a020d76339d27333a08c274a1dae534c549757a401201d3443ee03536bd9960586a901cb445a4ceeb9e78806dc5ed2ad24045793ee652712562cade01743edb707df96ff78732551ccb30668b7eaee103fc8cc9bc2ae8e054a4f2ed53481e03727e3f641a609fc016c524604eec0ff810692e291942af70c2111786f69291ac85815f669a2ad5a793bdc280c0e396d9ad195c8db6e9fd186b47ff56662538d0a59c2cd4ab65fb7af24b0812e021a48283f8106c65fc1b9398bdba20e47fa821c07b94221cf9c63faf47a261aaf55af50e8e3978ad261750ed030c4b2332539a825bd7381a903a5d4dc6dbc0ee2729dc164568dce1ae1f3b89a10240b5131a0d2bd69e13f64462fe3a6fdfc17639ea840415a8064d78ee6c34e8490e85aba5e101f7e949b579483026f2a5c586ee5647c0aa0379f54398f2f4e602c3c51cb611e103713aa92c0aacfd955b2280dc1be96585485666f79918236c86854109e2ceac80eb86da3faf9dc1d085a66dffa75e7cf7b1e52af89b0e6a8730b6c1b948b56f25cc6ea6c74732440ed2085e5e31df38146810634bde6da1dfaa094b5a50bab7981f9ee7d0f4823dab2592a09f00de149ec79dcbe7a22fc56c1726e094cd4047e42db1231fa203273f2301512385bb376b65bb5c1857a2a81a96463e7d3c75f12441a0a21b569842ef630a3a54507ff632e16bb3054c34b4e761acdf742fab3b1399928e55de96e021b9510124c200a34fbff1450159c20c1fe3369a8609e2265c99bff444f0a4eb4241846bbacf1e8ded26261106f83ca64c9b50a3a573a660a562d93718b407e2cdda2586f316a2644469910144c8864ca4bf855b0330a8442939575f20db96c09cf3ba782ed2bee546d5098d72e0c3b8020a76a269d30e30416a046320bd5274e19084a6d3a2fbf085ce14d0668c9d44e98eb083481aa4b4dd442a3978d01f35b1e1ca68e9ee30a79ea6959ac953abb921c9608e71fc23c24c3489454ec47867246f8e2e37aa226211507f92d0d40b2ae74714ede18556b48495343aed328aebadf3d2e44a03c8077e5ccfc73913afe41cdb5518a467f178912d4be2f554cadfafee771fe23f669e522d1efecd1f551d220bb48eb1549f5dbd8e6228d5d9ef5cc85c4b5b5091ab111e4b64802f15a0f2f694aaf50b48a658f48404f80f9dedd48246e7cac8d8537dd979e81cad97bd4780c9cd6c3ec9017df4159029ab3f576ed83a57e902b77f7de3276dc1195df7ed1f976848d1e46eef19416b57654ac604080f152a20903a6788ea5ee49cf49cb255aad6809fca16edc561966f0023af5298a068dd5b4758d3118ac82e1b5d015d22a7185094db5c3b9eee6d538ac39976403eb600cb420055a7b7060749193fe222a0f20546daa0273ef00c0164b977102690256414a1ea7ab747150a518e1e05a1c7af569e2b63187acbf812b31113ac78c310fe890268fc1e5a1517aa849e0b5e05cc9a51b0b81088d89001d04e2d4af57dd718a81f575719ae0afe929286a8868fc2ee0ad3a240eaaf52bb9c841c6f509756923562c78f76816ea6b5c655e5b0efb9b7cacd5f0892ed8dcb76f2bff7b50003c3b39f3e3a579fc428679435673e7815efdf59735c0242243234236bf37c4a93aea8472fd7c5bd53252ad0f530ad57f265b3b84f39a591da6fe592db6742b1c942845c85dc7e6a858296a309acecb9dc0ef2164b6b8ca805a2e3e06e217341e9f2c327a627662548401ff895f85b6bded91d630e1f31fa3aa154f96d80af575bece692e33edeb6d912303792a450b4229408d33311de9369d0a53f730ed042ff0d315c1c5b23c0be266060947b3866a4d49e5d233f17dd0f158fbd0769f3603d6eb4c27fb7595ba40de27c4f72c435a67d8de56bb732df7cb2e845f9e396bec51c5f39b197f176fa9426e5bb60f624fdddfbc3cbabd8a283ab082c0eb4f168d8bc2920ea26ee279c3f6c32a18a9eeab19603c379590e3f3124c2350308919b8275fc8852e1a6a387b89f487c04496a69e4afdd0a83a5210c3e9d05c4c5dc6f200a5428acee36455c685f8e347ab4000fa093cf6509c542f33328a4b5d47320aff00a33ed210d55b0139d83c6252b4bb803f8aab83d8c3f157bd8c9c05cdbbcf18fe51f42ab116ac65c52c41011f827de0279563639713813ebed5b269cd09531797439489d0e7719df439327086b88f2d8482f175210df36472bcb23d2fd3bfb6667b9b17ab40847a852e0eef0836a2339a88341757ef3a816b57c891fb2db2e1bc6b258ce1ecf0db8cd71fcaf6c80b73543b0ea45cfed282992e2ee891966d6e8ddc0b69ab784fc80f81a049ff5100cfa617894959a208a380bf570b6fe88c6ee110455309bfda5708165719c10e16df67b2f428235ac88cd4c99529b0974e5f4610870d394edfd713658cec7905e7a0b254cb0e05d6af120028318ceaf6c1fe4cd31fbe60ce1b83e9efa2dc25055a906da73daa003045a1a01abce0d8bd1fd7e1d10094c2df9f316ec70bb460aa9492a58e61f821559276f0347f167e80ebaf73456caa0bc4eb3babe68f97b528c85ebfa135828b7d2803c7f45990c5f0228794aa0376cc6ca2e6e0aa5415fe77773dbe1d736567a1eeb6f1ea2313e8832535ec21b973635f9c85fd9f30256027761947f0d1d98d0ce9a4298bf6763dff263ce6b1ac69a853210847112832b7e25564414a9ddd70e0801d65f4baf855a6b3d4b10ed1f5812caf697c9586828d572e767fa2ceaa7ec64d519f8788a8856053db456f06dbc9265aea8b7fcc7ba564191cd3b0232d156213a687178655b66ea34d69e00e85e80797a7b00fed36a478e643c4f920bbfb259b626028b0c0a05436561f8cb29523b135659809864b48fc9852c1a8fae7af02b1445e65f51d2e18aa87d2015f27420bc8f03c271fc70fcc89fc8d69ae480b4501fdb0e2c34eddfc310f07387f2f35c80f10a296735bc6b70726ade9355cb0d7824ba0e225c8158102462ec466f6ee15aaf4f0826116e56fc8e58ac0d93f62a8209ebaf34427725cc85b7e5c5683a2c7d0541433a7b48f9e90c9e93aeb4717f4bd8079758a5e0f8f67f184c40c27af720640ba79769513fe7f189400d75d69243459e4082a32da3f9138bf22051c80d3535f8565f9c1afb64398dc0bcc72e3499576f6995e4aa8c4c27e6f5de94290f759315efab1f9c17dc60773de757f6899be699f931329ee04a36a776088af0b7ac3f2ed6900ab155bf8f428e786b672bf6bd202383936e8436f8aa0706a86ef32235dc23a7815348fef6079fd06534c9494372e2c818b4546fa4738897749edd3f8cd0178d337191b0346fac14401d47fe095048fbb1c48cb47620965eb57867a60f17d201501e32d6ea563bf080e33424af284d0fee82d546d104971c43865fd88cd3f3a0854181af7a2b390013d139453601a2df6f59926cd2ec03605ae4985c740ad1d16ae1fe741344f112e263d036bb6e2b6cf31c46dcb02ab3cc30b64f2da88e3e3a26c2c050dc3183f5d452cb81147453683ee119214d650389d13077eefa80b8d47612ba9d4183b072a60faf6cb6e0ed105d95de5f707857082e281d607e71002861196317527356c034b4f718d71130040864454f8c3af86fcec3a8bc0116f0341f3781a6158db02c62fc8df97631aba07ead233d06232321a4c8354803af67b60ce699615c2d5d5412d26024711e15129842f7f1113fbdd7df55ba1fa3a90e0d0d577b40aa1352d2c73397ad4fc6073b905254caa144cb89ec4f35c36d7f68526a206b8fe80871baa350b991e0480298a3bce9dcd4405402092770b644946cf941d48609f59d2ae9b141c462607682450df3dad0cc93f10760178c23003e254c4d204d9de88cd20d4862ad2327a6d6631c2507e286806aae6bd109976900b01edc97719c17aac207fa698776064a8940fec2b4d87701f2633831e714b2014c0a077abb4b2d225ffc46415779afbd7bcb68f8d6d88958190d4a7a0808a68a64e140f330c9740ed275cb1291a0d35444b7615a72f1168df1f93a6d9985fc50e1ab66f02d263f88ec3fb5d07e114db656c5a73412d9955629fb1072e34a24cc79741abfc043a70bddcf93b186b99cab3947efc7122115540bae86601ee4b7e8c3b4c16d6568ba349aacccb173b312084ed593cbc46d84e9eabc6ef265dccc94bacdee42518e79556e5ced649727ac21f2e8941fc5494fe5089fe431f6448856094581fe2b8707802123a254aebb5a98467705c1f0f934dc9d5ca8dd1045bb78470925f340da7a83bc39e43dc95d54c7ab14e244f8766902818f1ea346549fc548700d035da23ddc5569fae8957c8eb3d272c907c493c8a1768cf6cf87f525050986eadb6842671c6702bdee12b610387c706b01eda3978928a94d333c4d92073fabeddad9b8dec480b8ed06e26bb157a4d0e887076145b64d866378b8a2e205004885ed2840204297700d516fcd8c4530af887834233cc438fac533434dbc6ca954dc9670345ee2ae8243bdb9ffdc99f2efe1889da5cdf140a6a302a6590a90568f85749de91755158867819af5d1b9d8d656f1c5447f526eef9b684c69a17b2dd86c14bc992011d6a505fb4963e2e4b2097377fc6cb490d5ac0a6befd07087c956ce1437fbdf034b48ae2cb453ac5b0390745a8e60d19810fb95f9edd4ef679736ab7cb7d52df93fb69fb63ddbf9475687b3a3aa0502b279d61205514b10e8e3c22067ca4f53a9a52912281d5b42ed50ee745cac33beafd6cad30b1c55bd755b0f6dc4e2869019f0c5ffa5bfd8309d807a0d7c50dd043717cce9bd53ab23780d90ce96b042cb7f667d0d2149a79e7dab478989246a87a5859abd5d909c36f43e3a641936ce6765abdc4822280330eaf382cd6cbce6af39865bd36e8a1760d51e1fc727d98acb861e161ea6934407f561dfd06a4d8d2b151d3ed9231efa004518715cb92976d2a92a0b7629275ddb2adf99864d15631fad7e034ca8985fa1a6c1abac4be1f0d4c979c58aa99d1616e22d1ff401a9229724e3fc115230356373ebd2c0bc3b3fc352ceb3e8d187b37d650f4e9659d8a876ed8efe52e4f45d78778cbc32044b0be8b77ca107abdb712cc83d03dddbb4e906230e497d52a34ba675e4bead00dfff213eecafb95fe4be48d78f89900ca84b4ceebfb0480d4c19b1d98334d68cc56d8f0c08c0db5c3dea33de9c73107cdd8e0522a3781856eef151186a4ac4ac76bcc16c51e6c20d0298106d97bb3bc957f20524e6ba317e7bc10e9f1877c33d2ff75caf4c3538b008358f7ebfa1f5ca7ba176958ddd92dd7223957a561063448885f2cf25401b8ab8df33d12df0ba55d988ae8be7b5956a839b938586c0335ee66cee64281ee04dda92c260ca6770a31efff1aa842009d6469068b73053225f3da13deb3ced653635fbb26cb077c32611cf320040879f50e02b4e40140f68563a8dddea80bd17f51e3dfc11d56f5b152f457548f1b833b78758b231a09115316cee0efe4edba11482be86b8886d9bda6f079827c6941817a761c801974d5d0dfae50cc8a6a538c9363c624f1426edaf8428fdea0b056b510d358a83d9fbde6ac0bccf84d8bbbe74a4d10def9d3050237a42ad62775a0decc28ed3f57c90b446bc4a8e4bb88d096ec271080484b762516a59178c8908ceb3c5664f166be0e073c3cadc2dfa38521828523ad8915d8416c4a4bbce794844755531faf5cadf449b636085f0f5917bd616bd36b52d38a869691e280035356b909957aa1589b1bc86fa937244bd46d14ad9e92b788c46fa38f954e813d79500ecdb4fee947194367a482f338402e4b3faaae0d6f803c240e08473def478a2cd16029ae0cebc2e5e4e2819aef178f9dd601c8351bcdfcc0d2d077480c8df13d20fbc6ae202805a7e106204c45044bbe481c7f28faded83630e432b5821e83c644583f195ed8f1edab4a1f1a0e183ccd7e832c5e6793dba84e6d0b98410bfc1169079eecf04c7a58a9b9f26929e990b7fbbb46bcd39d90e61853cdd01c9799d2caec85595f5959afcedf140e46da5042a2fb5f2b7946bb51cd5292f95ed13027efaccd753092c5896ee3deaa85b3546b6346dae5d702f286c3888c8369266460638824db4dbb7f62ccf8fad5b1d1d37afd114fa815458f77f68d1b2d8a84df5642655c6860e883992c18f77b22aa1f56e7fdc4e7d4b2195273f825a3cfb8a0c06219015a6eec26983b8bc17ec0a9294f4cd7a79aa16d59464960f4a57bcf2a2cc8cebc9b11be172bf78d8de2e6f7fdc5c059984c8813c103b11b8a12e60a6df07d5d0955f0fd0c0aca270ee3472926f566f07ea4023f1a76a15828f2f7154b7030102ef35d10770ef5f35a523361129e0ee7a225f70b7fef601dbbc72829b48bf0a40f564c02f07c1efc1baeda74b95c0a6f1176a8e5a7e21d61a7ff9bc23b360ab72ec7c70c9864570050f30193de52c63078a351b603181f8efb75648be3cec4f85dadf200b33743b4c5429fb1ab51169bdd521cb762e63c2a30f7aaa77ab7bc15eb3579d0efbb8493e68c954efa966807a4914e6b3f698b0031f07371f47dc21c857633152957846232a5f2bfd9d6c44b441b74d92c310c19469253800c362444093d06b3e5e00b193d806b6cee20903da379eebe446e19de7f846d79b089a893039f084b49f6504d393a091978e12e37a12637c97ecbd992d2d7899345b91f4ac413cde22e51d539a1323546bea32f9755ff9b7581a4ccab34bb649e0446cdfd178abc0803887eefe3ffa71697604c2a9426bf61a94f663bf4bef5883b8bc14dc6eda673de51ac04acdb29d5a5535f9d74a6d2bf86d79f3d1f78feffb9045494126a9717245e2a527ea598ddc217e42f439f9527fa2342384584a4f818c53f4365a2ccfa6c6b32e78d994cae8598fd4e06e917c94c87fe54813b17028eec8341d9e734a2c6b8f378ae1418a8b6e82f841ea6d89b8c5f5b31ba9f7f9c32c74ea845e6279db3cd4b56a7b8eac54de2ae20c2ffcb63c1e75413946490357973d6eb985d1cdd8683c8b230d7d98d99c30eff4d45537b2cb896e9709a94059c1ffc1dfd5d4bb704aa0cfd1c830ad1f196823943f627b5e365f65d6a5de073e50dda934cdfb184ad1c656f35142d320cc7d089550149acc944f0453e4a9bb0e840036d6516288c1461fcbcecde789af83540bf9e7ebf8a20cc51879e9c41b8c59b275b7cde0aaf83974888d6be12063fff5de9ead265adc1d61979a01d076465ad1861b4768144d007ff4d1230b62dac34cc95b538d7c4e2635ad88dce6f4f9dcd02abaaaaf5db453922b2b034d14ac4ed3150f964989d78355826ceef4c39da3b62374f7030ad889c040649df169303adb748dd7c4c418f2a4fbd0e9ddc63fa61799ab86bae0d3dc9fbb2c8983ec31e48bfdd1aa3bb0ccb72899dca2be1c203d25f187590bfa55d58d1168690e7794257096105e7da7e1a8c716199b534807ce001f08f1a5066644965e4959d5f8653c34ee2355f471b828397538b066f0077512bd8faff085d58ab43a14ecaa5c3aefc05d5daac6504e248febbb6cb27832e33dd575fa237478fb44e73fd64f11b3973f15659ad7477a714a4350df49654b3d05f4f9cdbee8af3d55684449798bb97a27b98ff1a1045b89331691de45ae4cbf57bbc07a1752df6d1d4b8daa56dc0384d0a4fa697c2c60fafe44f2c518fd83fac81ddaf2cf8412fecb5bebdb28b6b34085fd42c3aee265f2b4d04bb53a665342914d9081308f00870224142a9c42ecefc873de184243d59dddbdd31f3c5c98dd1a69840c938b6b080ca175f76c63a82b0ae3ca0ca95d4f9456d8d03af776ed5f719bb140a6e761839265711a99d5db64247e8bded60dd2bf3c1f86c1e11a7ed7b0617b1a284afe471973aa0a93102f322c80c82294b174457d2dd28a2fb30b2006ba7355a59b57f3812c2a0c0a6faac7685336181dca29e3b4f71baacb0efc385d101770114511d9f1e731096d65df32756521e9bf7444cc45b160380232846b2ebd7f520fcf7503bc328a4ee165ddcaadb6acb06676621045f0c63eca8d36f3fa0969d324c5c1e1de0b943abf6c9b8aa2213c96e8d4806f17013efff67411d43e1a71179634362ce381320dc17f79fff5750e47d51670efe44339f8004be7a969c2fea2e3c5433d44c9e0a967e4bbad32b852ef20e5b4e6411afed6f5d06369beb935c3bf896c5bd2b0fe479306ee8f693541b6037f230e202078e192fd4a483b03cda8705ec96f56fbf3dfffce36c6a3f8f49d700c4703ab64a069ed610bffd28441c4239620b33e284cf15e6810c09db65c82b5ae26efcff9f332c7b76004e79566fc7b791f20d115d2928dd6c9fbd063bf9fb7b37ab43eb5aba45f6d0685b240a580e8d376f06c381e94c4f84d2a34792dbd1c399c6f8188975ece66c435b090e36837534399c1514b47aa2985b35d554df681267fc18bf3613dfbb99bea0e5586a46f526ba90116ba94000864718859257600e3dee827c9d30fc4446051cef8eaad1371502803c71209741768884193bda7eb9fba46af2ef08c1fc3c997aa8afd7a0ff98089d3ff00b68082d42938f0356bf0877ac91e64f9398b6a56a3e00a6ff07990adb9b784db4ebc90e1541adeb3d563be612e362248fb87e1e1d84949b0b7faa1588dc22d1ea911bc04e1dcc9f99e6ab83086e6badad288d9b97506bfdff4fe48619937f76873694145ca65af2964d38c14c8f3a777db482e4ff703a10a2edac4f74cee042debb1d1c00af464cf18b59e95d4ad26058e8a4cfbfbc5575fde13156b8e56c4d702251c096a95c50bb3dd01bcdea4d18a65a7b003cde36a213c649f9b37037dc08b07d65da1cd5486974867cc5b3a3a668d9a34eeab7f8bb74df75e53c8216414d1bcea2f3ff0c2b16bc0c219e9e146caf6aa06baf33169e1610b7d4e351cfa7bc5af6df1944efcbaaf624926e53b66601dfea26d00b0d15a0a8ef3f24ec63fb4c8bd369070d718d82d0cda09194afa3947b434388c48ba5543f50f3fb270ab83fe7b4d8048db736cb5d0732a0cca13f2360f3933f28441a4796cd8dd6b0eea03e77adb501242fa25e07cdef854b0e7d5651b830b74b1a75e8155794c31bc5d290c19dd44b18d671eb290b387e257c7315f4d8b46a3c0a87339502ab1819dda9aa9132d45ef00e78f0e957e59a7573f898fb94a36f8903556e88cd460a7c5674f2ea434ddc1d3fc519dbb67241e7439c592dda99a11694e1a7f19b8871ef6608c148bb54a878a23928f36fcf910ea0a4cee08349a046737db72591198547fbec06057c175b59c6b76b99c472a2ed52afa6431908fe6c81e51b97830e6e247123605338ec937025c08ff6c313d81037b21e82dbb3156703edc4f2491757db34b47a97c0f5fa78035efc6680bed550715751ca35815b17bc12c50a2fb5c7eb300de453a38eddd50d30ae1da77d1191923525b8ca28a3a2714139a4765f285c831f447460b2b6d3c38aee3567adc0003334ea1fb1ecc80f5f71266319d4bf612e71f4daba8c47edce5c88a33b6a77bc30677a50ba48ade925445dcd09a32b14e5789fe967fc614d8c9d5c7aa84b0090a64c45b553476f45ad37183bf94a395115b010567572975d56ace270007a218351fdd7b85762e6be87523596bebeeb65c7be1293c973c27824e751323c6ab2b382d8a7c54a72ab4ab090482ea133287cb76069014903efcbd55ac220288b6f8b1a08c6a99b329b3bb4edd791a98d3b0e63681fad14132f089fe34ec668044e0803cf06535c044e42f48c8b9b7ba3070f89de8310df3131a98bf9fed457279cd81e4f3852d3838c203e438ae60bba15d5a7e57da7dc42c3afd0e45296cd4b9ad3e110f47957f4e7e1703b8ed00c4350f1ea1094a83fcef3e43e3f4e3b49067d66b0bebc4721d7a61d6e1a0888629110abab2d15810c346dbe4f9a32ddb55791bac674d6572c473554dc8689957d4cd7cbe4f54016560d73415f30bd86cf19e23fe99d1d990c568f99ce25d5f00a33d10e6166afa5520afeea8098b34c86ca9d7b0bad45fb09e606ecf1041940abcba3a50e5c519ab6c9c93ad8759c9113c951c4671d09085a0f04f1dea5770cfefb8bb54c4822a86dd95d10ae02924ac5d2d92dc5a7d2c6b10b5943c71d61a0bb5ec05de2466afa15c02e7740da8c5295713a923608eae3d63202fe4303d3f1a5354efe720673b1a9846f3fbfd4cbd1d2664c4703b57e483f1252547d7a1638501e19a6f233a51e6c4c01dad3825f7d46357d9c3a7b2af04a8ba25c29c57f24fe0426aab8c7062ef2afc1da91111561f3c28840c87494d1441088d8f2b82014837f1bc57b8681f0d69dff76ca2f3a03a63c8472d6fc312c4a96f2d6680913bad1fd834a30bd7205c9e88f87dffe95ac3fdefb2a34d8ef72895f78004f123b595bcc6a482337b2da51d015cd0d9265d27291866cc360b8a213eac4a71f95868f131ead3be6cc45235509485a9f64f8d20323170c7553b6b0f129927267bd719fe265ca93dee3367bb644d9d1dea2c1c4acbe0d62bbb94b4cc2d2d435dba61a2a6465b0855424bc7d4a86da0abcdeadc8094e45d3e9a3767decb53701d085c9393fd20365a242a32ebfea0c01c28595a9abbaa65e3493233ffd599bc15937bffa9c0d2511e9e94b1b912162d02855fd8af9e5356bb80a0ad39a896aa232e99e9126c2f024913d5bb1cd3d363afce34a18396d51586ab9cb2f780578f445ba6f23b40bc1293704c58baed4918660e582599f0dfa1fa61bdd51fc68f4c91fe6f500104781f60cb8eca54265e6428a17aae212690b4e16d44340358a2db15e60dab3950b6b9b76ecaea2226a651d683035269656326c2287b4de5757e6608a2ecc381b6efbee5d65de8ec50526b70f7d82333d13fce525133f03d1d43f830aae047b7952257fed9004f54b8187abebbb544a65671b40c374dafab463c6481a26c11b4269072f3283a8aa6b59062e5127d84a230f3092ca632859eb952917a72951c97e52fb22aad8ba1521ae4f0c1272b1d24c7b44a29ee574d242ff97d869d1e19fe112022614f114be6087c020edf545b2f4dcbfe42fc6de34686b26337ff6971a23d57bc9680a76821f1969229e170122778eb401a2195cf976a15fe4c07319b0afeedaeffd7ed93e70914cc5d321f22fcc0e2c48497b78400dcd398671f34f20cb75b4290e642a0e6820b00a97cda989dd657ce5aab55ada13926e63dc59c0539d8c92e6c43561196113421aa1a5250b303e0f62789be75ef4562cf0c1be326f7f9f27fc7ce6065683ea3d4c7dc88424d83925f6bb4f73bb1ea84f532889e739b4ba67384983feb3405056535f9c59ae4d72db53db8a0ee01ec2cac203560d8712d533e09be917dccafecb2ab568e7b0c919fea1a91ad73b79dafc8c7885c704aaa1e38b7d7617b86bdbc027af569f846e1ec69f567c3a24b921110078265e18da3375b6501b6616186064baf3da052c65eaabd54b3f7766abf3a96463589d3539c0b7abb85cbe7df47069e3329ae202604ddab8081864bc3d30c3b80620f4a79bfaf80aa5d5631368e70121a5063164d7287aa7c3b41e2fdf60fce88eb457fa21fdd8774d641b6451b253e9dad8c4cd374579f52f0222a2b108fc8a92fe3f65b7fe0668fe38b61091947684f136dacaa560ee17b9d84e1519ec4f6cafc19a0770e574438b2758bdeaad5b15ca2d45da420b1c9faaf92b81124e911ccf4793f1a1aeceeaa120bebf70833f50b39cdaf8250c1ceea15fcf4ac31ff38431bfb16fe4ea0b032251cc93c9ffadc25e74c234be8accebabc8913d69a650334723e8ff84a27ad643d678256ff743bdafc6e1968632654b2709514266d1f8ae5738c5b80fcc9ff2760c5e564b212020afc86c21681bf2bc76e773a56b4690ec579c48968e2b6c1237604c02e108022a0b969bf8b322bfa7490b22f683c3e2b12dedfa97758b5592c19ed5118ae954b75531c9ed9490811a967bb3c73098bd9e7245e0c1a8c229827ac5ccec06a73db538312ebb040d0ad6c7cfd17eb3d684b4f6bf1ef46ea160ad029d9e3791b000b003910b8607e8d0cda91072a4e0a430117e2c9bf590d2915f9844d76cd58010decb5ac692ff75dc31e860ce179c1eb667113d7bd835caa63fb631819cdffaf125767cf0d350a5c211d82cc49f54ea268ec1de773f1d533760da191c28aa8a16cb9861d02f8c90baef8e016f1ae30071b5972da8d1b452079b9dc1ed2da6410e30c21dbe0f4e8fc0449e4fd56486dff6d3f9f6a89bff2a4e1a22a9a4e0174df3df3f6e52f8d774a042b98f9f5816fdac0ef6b2e3944cb9e77681582a95bb42086387b001c9c862c2dbab6d08c8b9be63483efaa498f8f9b0be161949c2f1e1a7b13d328c673368a9cf68c49b7698fd26ab5a8292c2582aaf123190d66b1502aac8f9ef3c0809d8730c69fa656b88f001492e53cf7b81744c1b8e63a49cea50123b44371d435e508e8bd80bafc42d1813525a460fcd866e98db6a0ebccc5b285d245edf06a72307c16d320e7bfdbdc2ed44416b9dc49f89cd86fa8a7f72d65e006c1d28b0b77db7642de1dfa1b530ad3cbef62b385cbc1c88e1a3ba71425120dec7d8f2ec2c4930dbb92253e1b9ab0d760f70a313695eb3dabb9d4718d398a37e568d5ed6a3b95a3f0b7f4a66b55c0dc286b3f64776785c859c5c55b1537a8751d371999c895eff104e212dea745d4d7094153c5f09aaa3c235fe9ee49f36f37f2e276fa9516b16b12b23e7155ba057661137144c128c67dd86c8963ba090201fc81661be477cbf3d0892179b3012150ab27c72291ad87dd0601ebb59b934385d29634e07f582ca0fcdad424d0ca091837f22caa2454a84d0e0dcb31192a1405106221cc4704ae197357db9172add3e010cf4dec0df047555a7d6d0901b4f26c9d7dc2d1f7041730bf2ed5a2783279c0b99b6a14ac774fd77a6d2a987c3869bb8ad1fedb24b632ef85d611438a24cea1327106ed851fcbce29481e8210796bb48016afffc43ba00c004ba6b60a3935e6ae5bebfe32180702d1dd633de637fcdad74680e9b6ff3e247c8176155df7ed1e69fd5d8c21925783dec7e3f984281321d8511bff344cca0a3b039a367fb5b0d98946d82d50ea248fa5894807034229ca31aa5ded468c3b273c8d21f4f615c7280096c9c09350f1cec072de3d711ad72c1f8d3e3ffd75ad038b69e2febf86991f6011cf31c827393eb04ef38e3a3e119da0e0c75f7478d07029f339fdb478e280a1859c1ca2cd690a6db35226d63e4718d37fe4219f96c31e32c60b966f51b597737bc555eddf6bf0dac567b533fa1bcbd5aa14d2d276fa7afd0054c047e163766437418b1db874332be201508e89404499fd6b25f3a4a7e9a538641f4c94e998f2a9d96fce744913aa951956eaaafd5920012ac1f800db1246cc5b2f9c83e3dec87844d805f0e2a70f005266357a17435381e88d1e12a620b04a4009a96763e9655aa9e155ceda9b66bb231fbee17a4a64034934caab239cef3a7993aa8d24529ccb9f56c60c3289330e6409109f7fb15ad0c310c851ab8bd156b4ad88bbceaf320df96d94274821ff39e51127a276e856f7fe5a6de92413942f50d99b5ee8cb45c88d52dbb3d428f123b186a74986306e255578609ec739f033efcc962e9894f7484316fa286df560c623d1969a57108e067c8ea1faf48a01981cb07965a46f33a48fc85538cb7150196222737f80bdecadb6c156701ba06456c1ff3347236fdc2cc8f23f1f640e807f010e442c30af01cd45497f206e0837510e5f30b12432843bbb350727e405ebd30f565f7202a4722870a5ac34d02d952176179682783231c093cdf277575a4444632807d5256d36091651c528fae7b07b670c7503cfaffdd4bef14d02410d57112c3cf608e73adccd8f62468110b335ed3ef8fc825fadcaca8e914d4e41b574452d1474ea36c8919012d52d65f3bf074000f830ac3e5b1b62c4c63faf2a2068682d4ff242e100ce1b26379ae1622d806c314091b8f4d5550d2e4665905e841bcec4131a0041857c5a319944b49f5683e08bd4cf6958068de99b6d7d64e2fcb43a11a2d2dafa4daf4db1b84a18fbec67c745b672a046d071f20520a27f80400dd7cbef73d78c871444cb2b28545bb7bfba9c302d1b3532deb0eebeaf670828ced338961255dc126289ed2d5b971c5d5bba1a1965e4a0ab8c702848b72447a4cc4fbcaa3a72f81d0b4bf26e80d558abbc04c5dc9fd1c7afe5674daf8725ec7ca798c4b8eb1ba1daeba4eab327f0aaf1427a9842d93b1b97aef12a9e7c1d4b02c5b43254a296c095b3eccb72c13ce3a63e25908296dc0f218cc2496003c7d3c8464459964c86ac5a07d7a3211a3cf78677dd0fe9f2d1640bbd15b921715bef20495f70ec9c923113b44d371c71c5fb10daea8c43b3569de896df111a79311372df3766924338203a1b296c85a20f12487cba596298f42c5aa966251161ea9752aa726663023751b01688e19fe9890ec615498e5ad8c8ec52936ec5127ceea00ad14be37992dd259bd2ad497e1015a9d8cbaddb97280e5198b7446a1a4ec41ce218d069ad46ad3a4ed82659ebb0cfc903afd3ef0a44e07ad1c5e37d1d1d9e020f2617510ad91fdf9e233c1601c68b25ceb1c205835d63a7ae2b855350f6304b95c5b5797182d9a9bb5b9f7a3d5bbcce0a183f2e15e783051b91f2bf5a2dbd0ab53f6df86d2988586c33deef07d4249d3c06a38c7f8af02e1317c933f1c1ec319293c22f14e497fb4e53361709213f9cbd6bba65254c934822a713c7ca34f34d2bc03a500546e42b28f6209d7800bd4d6f943e1da184a3a1c78c203f3be02bc4f48b49fa04b52846d66f009d6e60793340a4163c13a151f7525843ac4288bc4e8863296f5c667325cbceedeeac4ffb80d030f49ba7c5d4549edb78ac614e45e2129171b0e8f38f4d1cf4d3b57b53edc587bd40a42738f64f559bd158116604fc01428a7f054bcba1cafa12a0e79d648785f168d051787e32fbf22513db92ae15be65bd10cde664bb375307e6edeba12795acbd752da68f758065a7cb938511c47e9d7e028cc3a3f55c820abc95e12950e97eba8b41a3416e6437d0c7c35cd60f52712e788842f4866159596f81b10f89cba085bb7d006bcb71e23306753c0e47fa01f1e561e00a5bd14f82e2b2baf3403df749b8531f61470f4f432825d96c3598670db12828a2d5db695c63a47f420a04a07690ff8a9257687320b6b13ded6b07196a375f7d835b856ac26a7ad3dc71504767db882528838677c07615da721ee9ab45e953f698bf2c52c1d2c479948f9b1591048738d563f6d1593d314a62c36db348eba39007ae91b39ded8c84bd168a2a5d15f7857e9c734fb76dd52e07a8cfd59317bcb2af0c52bf2818a6561d189a04afe1e145ffdc06c6299d89b56cfbec614372d494ef8ab095205c09a7460f17c48495242d18afaa5b0d06e14033fb78594c051378a7d12ec02ca260cbc60cc2b05e4e514c9e8f52f8aef6bf452015ba48eaede0b466b58a0b939105ff58fc325a33a07ee7d0706fdd022b6156c0d58447755632610f33b3319209b65427118fb2b29f96967fce2abe4d0a086a5caf1d113cf4139f74abeec457764413280c66f4e9c60753f6e75ff6c9b15c5023753e73360ff4c3a59898b2109be4e88005089e814a603b0df99cfa47488425592ca5657170834d98cadb8c262e48129aac8fbb4c062564da8f1f34cb86477d756c2ece19c2309e96540b9252f7ec95e6821138d2858816dbf4f922764ff8827dbb9e5d6080b83ab0a09e282557e6db43b0ee9d4b8258dff8e205e720dcf42e848ba4f007b474429ff118aff83d66705287522675ae42e3f1419c47904e168ea4394e906015d582c86ce854f326e4bc0eae4003c5f7f5e54a7fbcbb3a06e080f5a310cd75ccca16eb1512e7335722e217a4b106a986ef60323a757e75c816b4f31ab1a4e2af356b719c0be72c930c9cea52a409b018eedca933f3d85888e71860e0416e910580e72d18e39a26531c4ee03c864b19df1da41f88a6bcb45ca6d82ca3c393a0edc7de9d0000d7ba82f6b6bcea2d930d89eb574bb5a49211597ed75dd297ae5b9dd60df90365b3ac95a21d50631367b603f3d43e661ce70e6b179afec0d9db337e10b5b9dfd2892b1230750f86a651a7113aed7d5ac02e1fe459345c2cc74d4bfafef3322564fe51133862b573f8ac91162ed8806bd79f9eb9557d8db5f41195f522f5ad119b08a809db818506c152eedcb8e0b0867016a9d407f8e44ed80fcb34303ec674743e1672706d567c78694cff382f7105f0b426f8d0954f5a19eaa8fb58c6188eb082ba57f7ca14e089c02a1c8564e40e7d17a0e0de61cbccee634cb87209879e60c69149a981e8aff1d6a133d240592cb34e520b78118a419a21da26c45db52729848457b094dff8c0fc469761fdf6cc1e9033afb6b45600622a098d727bc9f8b049d2bfac2baa5de7e9e1feb25f4dd434c6938b7b10b13fa4e9ddb42ba973ca142e0b0ae9f1799740f376b042764e96856106bbd02c88cf89af086e175dacc3a49cec6c95b1e53d94f2f6314b26dba2ab6baabf6d682086da7ab69001cc7f82f2dcf169e3e8e2b90c28b40f44cdd7b9e9af68d7b4cfdaa08950b51795cd969434eb24a6ba33fa3696da01631214aacf2edc8587174a9d424fac8d012965a16a954f31ada5161449c153a8f127c4ae59fb2b94ead26fef6a66a9122b22f1c863e6296667e34d33c4ef1022ce34e054ca9cd00a423d95911afe57b01848a1287eecdb0766b32e695ef765940ad7ed6358bf7a279beae4e0dd155ad9a31009219dcc56001f677686ca934f518d04a2b7735f69ea13d22eeab33789b17ce940476bb047afacd8e9ca31ad30c4ded917912860cca9eaa8442632fa29e3c7248135d43efa62b3602e0b8be6478e01c9a64536dc19acaaa1b4a19bfd0c3875e2cecd4dcc5b7c7c60e71e11b34c2582ac7f51387d02ebe27751819a2156f48925dd4bd7fc5d7446812cf849a3d6c72df7fd6c3457f4d95ae456d4915b9ed9c787a280df9b1a5aa7d8d72a7665e2ec6d3262dc40976ff9adca0c01c33ee03be17377e8027491f54e4f1a76d1be54771056443e40b2829818267d1189f854375ea4ace01645bbf656a454589bab0c009f7f3e4c6a6f0d30d4e3c32b1eda056564822419fe4613a946840e70c4b323fea4f1a023fccf925207810acf6dddb538e15c7cdf66490b0440c198494fcd2c31aa23f8a889c0190c0b3f233369d720d24a13564803e6778cf010549bbe64c55af8af681e6ad7ebebea7506dd418bc0f5181abeebf08cce0b707d129919993f0d11d26b0a1eebec7edafc4169f9260941c995fb5e4f5c38bc5d48590d5b49507708c34f02f554189647655bc49f53adb3617dc355526a35bf1da04f1a457e8da2e5310d98c20cde6fa03b880a263630f2d3ce5dc57dccc2ab0108815f0b6cae2f4fcad3b062ed18bff3cf0172764e6c784fb707f7e4107f77b40af63cd1eb875ed255bc9e46f2cee1b6c64f33809b1efaa4d7e63b22f01d078a3c2f7f120de7e1564c22962cb0b697392c7bf0e06fc5bbfbaa4bc4d7e693b9a2d34955e9306c1167dcf9aa665338e88103d8385462fb0a6fb4605813a4bac8c09f1a8e0ae02df114a7eeed067c91b98a39dde01e455bee3e555b3a806df3688a79c41c18ee5af6baa0b618413e39049bb187df22e325af4e63a90152c01cb230dfec0a1708ca06a50cdeadbb5975a8a6ad910823d19e8a0f7bfb575a82ebb902b72a6532c4a50c5a2e0029e8cb8874c3601f7dd56d1bf8f3ef10cca1934ea511c962da3d34dfc6694a1aea41d8fad33ec0ebf79c6d63ca422b99d19c0354218fb8395ef27e9e0e140804ffb2ea063440cc4b4b8a85b676118f8f43d938c596b900d850175b16ef88128baa42f73819a42cb9d8e83fe949874a877c42dba057b3a25e7ab8cde89304e406c5048de87422f035cf826398c823cb18ddcaf9dfb85dca5d6396dec66c6408782b779f0a64dc02cc550d5d5d9d026978a9b716c95337486c7f4dea2cc7182841a2dee157026192a00386082eef03abd90c0825c7a7932a9286e8e1678d1b063390a0adfc2dbf4e8f3b4d70d5aadedb63a9a613ee770e66bc4f67f13a934354d2708fc3b8798eee3c4373911c03b285b20512c31f3d419ef7172fba6bd7c768dd8383edd7d3b3dc43e8ccc1bae058306d745eefa2bf69cc2176137af14b5b4d5f061f2c2f62852e8e93769995895165636f2f722ba1f71fe502dab212c8edde39420250c9bdbb5443c3158965e7ebe3cf7580160c00439d9a830a2e1d67f39a6a42b7854d8e84d5dcdc4d279c5929f6ee7544a13ab33ffe6c2ac0d0cd352f2fa86e4037b992d3d58f07692c3c10b4c29035221f07bd7ca6572c05dee1236c781289d2938a912b43f6f6d5be3bec92f6d6a1bcab87ad3565d5fc7e680edc6a82b5cfae1b99b3fe6575bac9f25e8e7866e77cf01d69c4bd77f5c2fd7216165f7653f98c724e414ae426751a98bb48625631835a8301f979661921b69748e8cae68a428150abd40b056003863d094423e15024b899e1928653c7a5546372b5206114affeda26fa77ae2c1e15adcb0c71339ae4687aef44df6451f67a1a0de3c88880ef24888a9c800848c4f8b25130fbab756472cbf479bb65190c5cc25243f0e301a2c860d3edbdd94f0c08ae10e00a8120baaefda936092622e06252112b8c69e953cfd55671ab4b64c2bb2a9de49e391aabf0f39607efa908ccf46ee812794948f1e4412d83a5b73218ab82d21c75a9e1f2c4347ace7e6a1fa0ca8ecea6c8858d5a8f73e3bc2ed93127189f2b791c41d0ddcd6574102d4077bd5030804bf7cd9ae5e4fc8943f980c3f3567d14ed7ca1b64c1282bc378f21610a4644ecefae6014e0680b2fc030c37489e3282ea92e38d678c229d00dc1fb604b0b1e7d66e37b7d120efec03298bd636611b6a537a902a7ede8a476689c886d619eda3d2975b934cb73442791251fa608d688eb7f72d4b38fc2d9129411a20f519c8ff9a31b39b20e42892f2ac7e9c560213745a1d1e430da85490df21661e6a4ecbeda3c8aee19068c4d40b56d5180465cdb6a09372d24ac927d5d5d8e54301c650035e4a1b967e114b63e87d51b107049eb8e39dd53ece45df43b2de07e66a676786fbe61c2038d2fbfd29e3ff7532898200ac2eaec1279e2e5b570aa9fafed8cf1f8884fa1752e06bd90f016c2cfaa0c59f4471e9786758a0192492004e279630c71e747913e1eb1f8878e014fc2bb372b31bb4d57eb2548bd49077a98eeb386eb731d130bd93fc4bc5987369b2ea977c821bcf4a3a842d62c634fac5b972903140f738304504db08c3ae63a3b5f896c125c6e2184f4c6beaf32dd6378370d86950d40fc7dd7ce70c9dac8f020b207299ea000ce96a140be61c30c222b330a08307aee9a8b52be06964b0e8bba7db255b02073809530b3ce794ca81bb5a12f06bcf0a86d703a0f31576982164c6195e3ef7d3877c849d0200c0144167471a961fcd026a0d2f452d658652b278ed075c9328d323580ba8450786f5de46e61553bbf2744639805c0a12956242d6cc6e8193aebbae909c606ce56ae95113d0bad62f91a4d8a3b153a97df6ad45c3651c8c0014cf7680e9e3e32776fe7691ae5cc5891391f087d96f461405b1a00c29ed35d4b4f4f69ab860005dcc30da6a232aebd49650bdfc30a0819daf4f29eaa628183a75531c9ef2d5d04ca40cf523fad41adae707b0cf2d31e1c9dfd5cec2032d13dbabd825847c0ed523f2159a30761bf0a6e6aff37f9e92679e81ef2ed92bc2e98e0acf8bcf854989f2eefdeacde24c3cb7aad51898dd128c5390fbfd18d3e9dc8ba045cfed9a4760334aa7189be034a5f68c8a44676299ab651afb16b82c5722f7dba422779f5ab136451985013426cac99b97c706417b9aa12541a6081ff82da21835fe5b3e36f2c3bcf950140705e4b45b4e27dd7b400d9407d8ea85229cdca8f5a3d62817ff7845c3e2567f1433f775af25fdb61b13ee59cf7a4f91cdab714789f2269357f4a6e05698c40633168fed1e67e04566dce7a8b542652b23844894072c694a8ec8714bdd1c7fcbf87c557cc82bbab75ba539677688c8931e8c3d9b6b28c623fe80f4b2736168fd1c7837e8b188eb190cad146e1a9f31ece216865251a7ee3c5103596ae60062f3263ef2960f83544df2541e1b240a7ce37ef79feabcbbac5b2ef9928a96f5c8728edf091b2916842025956580118f9433234ed8e47c9d7f06f88f577c23a4b57d05bf4d6619d12ecd1ee50842fa3001722c4fd7014620963d33ed2afce9c0037ea6ae2285326a41d36d576d0da686dfedb440f0baa59bee77e68253a89394bb5ceb0fc2c0ce9e6d748f86c68aa2573a2fa0b9a1aed482f91d704c543d2469522671354a5d47454971c9f04317971c521b29e5503af09bcf3e68c4e6894b2d3ea4c695e7536cb18d9ff74ef6139366ecb0fc2e07056ffdf316c112f921419bcb5aeb3907b6ef887614244cb53653911454e83d17a982b5e57a511ebe86810dad389d8cb42c56dc6e098105455d7fcd528d047688f6cc88b0b9d151285b0fd75e274553101c05cc828ce2698944b5d7daa851d0c5c326c4920f90c7ba623ae58fa8bcecd44ad39082c81e8b957596c4df198e277086fc7cd37df8d8530618947b085595e9186fd407024fee4d06b73365e40fd18d46fe76b630241c4c7325b0fceff7fb09fdb90e624c803c0b95b0825e12f172f48e03b81c20becf9b28c07d22cc050f7fcaf377f8a7899dc7c126e4a96311027cec45b5ec9363414a53c71e34e9f09a0c18bd3c93a221afb1a51a993bf804180c04d340dc17c0b97b1ced318d1c88c74cbe4f8a2f610c9b55477da3c945f312a35230acf42f7c245c861302c2e94945f31ee21706c27fb457ba243cf5389dbc338307931b370849ba9a85aadc7d8c5c19038a6b667e5d3012bad21c8a66ec0b81e119bc54f957e3e5351d340db2b7d4d8f32b4cdabba71112624ad4a3db47c2db73a41b227bb7904b10711d1707c78dd7f53e6960661bcc3f8aedfc9a0ca8bb29b0c91acbb569f3a7595a2f2d2dd9524a99920cfb078e07b4073f56cc9ffaeebd7f5f317fbef76f1bcc1ff0fd1b8bf9b37afff6327f6adebf6f307f6cdebf71307f58efdf39983f37ef1e88a550f6e03b2c6980e806543920efbf10e87b0f087ca0d5d78440350f64f3ac108885816e643c0867be43d54d3867ae93d55f59fd21f53499d6a47d52300e58a669665454d49ab4580706cbfcf6a5167b61cef2d6e584410488c203114b04a48c179b37711ecd3b824b2da659719e7bbfe9778b251559fdfe5a960ae4d09e7e0e2a9655ac537c745ff291e94b82abd8b3261fbd9b82b82c56c0a468727142caf8ed09dc7dd9495a3e2236daa13771d80b94827a7682274d64cecb088376964819efad60f812ad99f1fedeb4f325f67cc0a4eb3c095fe2fa3a7fc33d9d9045df849f240921095fc22740e74d106998f13a5f8248c3921e335ee769d879127e925e6a864df8127e4baa91b5186df997309b3f5b7613c45942e84cb405dbf919afc5684f09efdbca134b5c42d8c41de6535c36231c610398457fe785481615817accf81d31688638690b0193dc29ff27a9f3e5ce0c7109fd4992104e724638495a9fbe50d6e2e32467cc101db6610c6beaa7ddce44430438a9c97c74ad498bf9586ab13c448238694b472c3dc6da318139cbcee4a3f370584c14b87458d84e10a122075246d89282dc9270e9b0aec1657f2fa26b40cd1fb267dcf83a60c364091f7ac04111224fa60d88d012648a0fb88089974eea21baa79b282d227ba7a0bb895837e919fe5cd84c4b301f957ccc09fba5e5888b94117ed003e93588c3738b5aac677827f9e84ebad5569a8041764dcac7f42d8d10e6a02dff56c8438b316c010021034280fb4508430861481de184338010b66b08ee17c33054e980fbdf3f46d82ffc04f76b31e7b1a2c3fcbf12e0f7d7669a152da635c1b0f741b84921222489f498c33e08b71f984849223de6ef4dfe0e7326cd85d08dcfa002850a578652252937142e5f727f0fa592e0896fc033ef498159f427b9a2ad26b8518081e0b2edbb0883bbbfe1948a1a4729a594a67e4bcdba0804b86c97ebc79d1d955bf12c2ad19d1ce64e303ad3e84c869a3929b4e419cbf38914af3cc5519e4fa63022e6f9448aa8dcae1e527d7f0b711efa4d8b52b8591367cee3e4b515da43f3ad825289656b3172830497b38b8eed55623bf9d8331ffb35968efbaab7797a8e8fa30fcd1f11cc1fefe9186db517e7f1be595a97a9d3efd9bf67db86f16ad53deb2ef3c7734f524b6e281c2b25b88c0186cc892ea452a50facda365518c30c1326cc1659f55b58d264aa1265a8198b250cd905edb71286bc893e6a8e91d144fcd37954aa52fb944c586231466ca294d8aeb0878860167d8db5fddcc41d1a4876b05872c51256a27a88820208061d00034411251ed081013e36907039b3943eb9e95f25b8a3dc3b0ae61d05eb281ffb5d445ccfccf7b39ec0e50b36721bf1b167c266f2563fa789b0a82f378418992139cadd4ab969d497fbdd2ea216d2433e76ff0bdcd5ef44515fa8f831c19ebba8bf8d8854ce32c29b38a547650084004218c303560d0c17333231a0c7f508d2debe1029448e22e088ffc18dcdea058d0aa7beeeafc8ea717fbb2f44cef746008c00b051332644084308b71de07e9c108310aeb06031fc301c9d70f931429f25f007e10d0fc21c56f04dc88315b6b005db84356104668057210cb4d50f239c010a2e6754ee7f11b68bb65a0aa6e99fe94f2981cb2eea176df5cb849d4405c661c362c266b25ae0b261293005f616a526ca86348b8150e0b26379465bfd5cd8315c70d9336dd3b27bcc161831a4986f89a1c2ddbddf95ba86d31e579aadfe6d23dac2955cc9979c67e629ce63c579e693245d723f916b517299cfdc29776fc9fd953a91f394ee2a729eb2b770f990f3dca63285a8b7f0251f1be64a3ef610465c09ca7184aadf9c60b5aaf9e6b072b9b25f2b3ac8fe32b62889edca0dd91f5b570c341eb27f4c7db95c2e233164f025c9fea9caa54341f60771d0b0f090c8fe9f1697cbe503b744f6f72810ed4a97ecdf45b95cae20ac21d586eccf6971b95c436e8a6843b2ffe603e7830c92ecaf79cce5721119a23341f6bf3097cbf5c3922a4af6b73a44d9b464ff3a2409d08aec4f8b5858382640e0726d89c921fbf7f7c26a88f87de44d142c4ad93fd615cf46cd5e4cb83c6045d3c5df6c4c62e35c5aa4dca0c805419e62308058acca8d8dd2e10aa74f06348ac8175ae4489d1581157171e95cbcfc95022958785fac0a25b12aa63c6dc1dda2a8572ec729580044eb5c0e7392cb514b0d5586549dd951941ab2b045515c4e516a502aa27db024a25a042529881b830204122849453ec8d459510c62332b86e89c60591a692ca96a626394cda390502d2dd06452442193729404a872bda80bca6c051606854a1328b3242c1c8c588d4282c196987a532bfd1bef8931c3d9b1912329f7e7b157141a2fd88b9ed295d2642c72e77edc3e56f19e9a9e92788ac19c519e64f0c4a5a33c61519fb6d080d421532bd2323e74434cd5526f51c0242687c53d26cf2856b6f8778d8957592c5779fecf58cc141c50cad3294f199195ecc55817343f73f838b707bb4dd3340d86acfdc8e0d7f9636aa61d175ae0550b949464444db995a78ce803d9f3fc6b1087ccb758dac8dd6fbbfb2d39a70e938d89a182bbefbd57a5badd54262c5f70114edab24f136e45e89cb95c244b1379748cb6eca7a2c0404a918b4c3df8f63f20989cc0e988082d92d881100980f4e0054989206400454a136d3e0002c750c1f7634296ccc77ce71ab264be3e7e6fe7be37354ff5f6739c677bcf7b954ad5e9f02d42592bb5f7669e0a65cd87e7199116e919dedfa00d53fad1790245b31911d7ce0c70b95add3c0e8bc7f633f770a28dbcc187dc9e682339cefb2decfe06a519066efb0b701f004eac5abc6bb8773b45dade9b9c165c6e194a0898ec4f43c11eee22376fa28ddc7ece1fa4ed4bfadafc09a6309f9be50cb9de7b5df0e1b98adbcff40ddfe6f53a445614955bc60496b3ace89509e0cdc9532bf55cc3bf9c734ea0f6e1f3eba473ce0aead0d1b13c29909d0a7e60f53c20ef0bbd756cdaf785a048815c6bc83a40eac16f21057e9b87e910487a5e0af6b5b2ee08409d735e70051aa67ee1922da767f86f604e4ed7c0195bdf8e368229dec45962dfa3df174ed2ced166c0e6fad910a8c77b5707d7b375df7ee2bd329f0d590700fffb1640f1fb16e68f50038e617ee17e4265215288f4f7cadebe70e8fb495b5d6b073886f925fba07996a0fd56bb30dff247a65f430bee08404f30c58932dcdabedd5b3f2f5ca25d5b29b52ff0bc7089a6bdb7a3766215f10ebac30225a7639b59286f5ed9dd7be190a7fd9c2594f66f47c11484b20571689c66c115689830d90b97749bbb73de57c192f39e5c4d61dbc40e1eb7e47e0d81eedbec3d9a137bb6ac3f97bd05a22531f86417c62c4472efc5d061f210f7372c89c127fba0255cdad94eb2ee98c9da9c6e539bb842ad1a0a4b64f0ac6995fa8f16d2bc1a511a43dfb355878ba5509ef52bb8a3ea787f176cd879feccea3cd87b2f8eb59abd2d7e3dc3b7b7360492c173376705d7cc59c1fe76fb92b3dbbd7f2d0e23a87f4582c91724524094ad6fbe89738e2c04d9aca36edb3b9036450bb240c38eeaaebd8f2d851536eddaa7e00a56ac9b8f5ad3204dbce93c544775b97a8ed68259a1c300ddf7b1e59e3af6db817454a0eb6395ad7feba8323875fb9656cf53c6446976ce0d155a7077aab4dca66c4851f67fbff1a62ad869723191ac25fa08081545903f3a0726a01c216a1ca8d0402c6a072c2f2aa467b17c808ad9bd028a115a484ffb6818e44a4f45e840fef89e000184a4ef3f66ac9054ac2e807e6ffb68e84c440029223d24a10be9713fb860c98bf4100709d2eb3e1a3610319845793650f264f47d349c4e36d04411e921ab09d2033f1afa912aae48223dc46182f4521f0dbb0812a49817f37938697044d230a8ab12841222c81f1d86f40e40bf873f1a4e2b504610457af839417a324970f9a2fa6838a1d860035a480f271727a43743b3e33444d30f6f1af326eda9d4b3833162690828928620900038451123690d83b422124841fe00395067af17f9c346112f4890f56918349bc81f330a59c525ac7ea1dc6e0511f963860ea458726b1834a348d242fed8d980510abc8a2ba820022369f85d417ada57c3a0ae018827f2077591d305507edabdcd4daf8bd31144240d3d2aa4879d7fb850bb179167dd4262da53c95947ead4a97b9dd4e7d439b2b6d2b7d6d2b754dc5cacd7524ac559eb9734557451e545bb76e254bac5f6943e67ffde3b7f7cf3717bca3d0deddbdf7eb3bfd97761b3e13ca58f0f0b61b6e28ae26d85ffb3b6b5a669da9d5387be75c7deb66de5c6820b9bddac77adbd7765e3dacdf1bd9b063077ef739c887dbcdbb64deb3570dbb6d19e95b7aeca0beed5f6ad6e9c67ebc4cb6d9b666f374513152848acc09b0aa8ef53b27f97257fbe3f7f3a71ebbe7bef5d2c71807c34176af43e7deb02a591edec922dc842687fd28dc67c1923d3982f61641af3654ca64f27cf7d8a1f3f7d6d52b1e438ee2d4743eca3a5e23775ece316e0fa9d4b1a997ef9d91bf387befd084860fed4b7aa4ffd7d964adc3166d5abc41c1fc19779fb36debe4cb8f9083e783b46be5fc6c8cd6d3404ea8c43a0cee55733287e9e7b9be7d6b2505d70df3a80adb55fce184ff97dce13d6497bd294bb7bd775e2e6a3a6559aa76dda73c35bf6adca8b2a0a7ff86f9c30c5b266197abaa74aa17cc55483548234a9f56bad150730f9731e190e5e32fdad0031d645ec3c4eddada8a36617bac8ca82c88220c95e4f2a96cb9dbc5d9c430fc7e7ee6bde31fa48c5af829b0c31c4bc87e5cecc3b3672f460c00b330c1521495a8a79f65438b4c4f31dddbb588af93e48e3650ae26c8170ccac3d8ecee027ce39a3dce13b68c8f7739cc75bc17774e2e8635fdf76b4ffb57785512c6f725631c0258ee7886329c3559a0bc05316444876afb5863976565007deb6af200a9c1843e7943075686deaf56f4863ee4c1dfa2a2b70b913c589604c420a21aaa89cf3396250ce08a30838e2cb10ff6588413f6a502922462eb1ffc1cb781962908c160042d0f9181f430c02e17544568f18e1c7f8ee4fcd3bbbc9b375dfef93e77e7b1d02288278b196888102bf306719646102dccf4ae9d7eca55e46cc09593d72fc75831cc4c89c17226584ac1e325ee775c4a0108a2065bc10e9d9c8c41ce1982db87fbecc3bcc79c0aff99af7d935bc4f2cfbc1f7c2ad67d055cd5310057005ae9e867eb4aaf95953c3fa1bfe35db7c8be5a7a4e463ddbaaeb17aea47d993ba46f9fdd6336abe06fcea634df670d49442ae7414d6ad67f4afc2ae67d0ecb1de2664dd848e247423f724e751729e49c37e4f7ce1db31ab7c50e052b322fbfbabf3d635e8b71bf9e8af46e2423fe972e2ca2dfa115512d8c8794a3fa24df1a297398f0bd19a9ce748d798d9bbfd5d87bbfa8bca065e7615d9dfcb1768b28bffe23caaf76f30ce43f3feee729e9987c1f3e2fd7bd655cc566c461916eae35edec5a17a4a5393a5b689433994433994435926876aa71ab067312f1932747440143ea7cfca8c8498d7fc01bfd9e6b465d9b6c862166ab531753a168a0b3ba95f1bd306357f9a784fcc91b7fcb364d9b26565c598d70a0688d4b01b1280747659919d7b7d33cf2361c60ed7428888ba881a0db5ab4bf84cb02f4400ca070c413ece604200be9b9585eac163c6801b10a905564f1d6b59267c1d0d0b65eb37ebc21d4dee75c28d9857cc2ce6c87b3c2fdceb9b71af6fc6bdbe19f7fa66dc8be545bd2e0b44e18adec5a31cea9bcd3e27efe18eb8970adc2b47130d10856a0560ebd664936d532c9485b250166a63e28eb824142cb521804f00200a570096a75b71fe35b45eec170b268518d98a360a2aefd48048e3c8e33d3e34b8c76700dfe6f37147dccb4271afec6fa13ea76ff6cdbc01605701448005cbaaa394d27a350d0b495d881ba8d5aea6615162edaa4a21436badb552eaf7de4a2be782d95aa9922e499e94d05a6bad945aab5d4db3f689d6ef45439f94c028add6d65a6ba5f4d65b5fd495e0c05e4dc3422ba548b7b15015203d3d3d3d3d3dd97a9f2ec5922b45aa97e36a05539fae7691923cd92f97d2aa21fdbd286c9a76933c49c1b2022c9ee4a9933ccd244ffe510f582c1d12dcbf715db8ae4bd7811f7c30eaa13bd2f1d019c1fddd0e345d91171d91aea8d3a11bd2e5d009e982e0fe0e871894c6f8c01e6db4677ea52d6f97c0aa73f2d1a995ecf9e616e96cea7cf1d12998fc25fb6dca9489f6c45090cb9af97446da7692c272a9b9e81724cdd5c1aa70ed395541814b0acb76eb582c16a3949b5514c870b3dd2bc64b3058d47d592fdec30d5d187d5d187d5d187d5d187d5d187d5d187d71ae0b7b71306b446cfbb6afd65a2bc55b752b57c95b494649d99f1a5124de7399eed25d32daa08c6252bc89537eb0175eb29489c2e875516a2497379373a2dc8c7245ce13e38c78cf77c4cd6e8c9bdd1837bb316e7663dc2cfb5bf17b7133da18c6157db31a30f539bfbbbbbb67759a2a5dc8aec5c7220b758d6c17efb1168c97210b46c8509ed788903c2f926b841ad9a8d7ac52d2d72adba89925af0a05d6bd7039669f77bcfec57992e60f18efb943143652d83852d83856f1ba60428a6eb29729905c72b7c8954b5a5494dd6a71114451d105b8ef1590a69706b9acc133cc9e1e6a2175a83615fb8b37714a6e7a1d15827f12e592265d99b7da07fc6df68bf368ef7f93e60f18efe18c78cb3f4b962d5bba74f172633796fd35912bba4cb7c95da249d9bfbc4bd99f265125efb9b21bbbb12a988b21e14d9c92525a8b7c8bf7b883e94287bc74c9e58329ddcb043373915bf4a820fe6edfb31282be3ae0dd7477cfd9b3678ff9d69b4c1fd31eeee7ca3e87b48db445c3c7460a3132153b103081635403de375984172e71bce7c65bbe126295032b4f225a9337dab342c2b40504f7cff7aef642dbbef9238e8f50b869f2ec6ec0fddd0670ffec86707f6703eeef3490faba1a707fe7c2fd5c18dccf6da1e2b4c0fdf3277355300706f77359e07e6e07b89fd341cdc77db1f9b81cb03e0e07b89fbb01ee9f3f3779bee7c1cd7bc582f613e0de107bcbdf6b676a55d3b4726127a0f9bca9a3042e57de4d17cbf5fb67562326539724285916a6d89499152d5ba2606192949692609306d38733379ad58f393b3e4c4d9c8929fb3331d5adce14c0e64f98bde932da973689dbde6631d2383105cf9cb56297a6d824efe9204516d6c4c66c93f3ccac93f34c991121d9df6af11e27fdb94d7b394f69ad3042628f9cc74bd043e0293382246f6f13e6ca3475fce726b3f2f14b8de666f3d1ed3401cb50d6b5e13c3439473dcd81d7cd0e29e81779ca764092fd6a54c9797c05d9dfa394527adb2679cbdf7649d9ffebe9be71f879cb351d1a7625644c90e49a3c6542c0eafd1a3651cfb82c1ee08de374b58a3dbeefc42056fdf1fb205615811030c9a7adbbf2a1adfb42a4f7db23e0fe26066de5cac79293c4f55ba0ad5b4520044c92c77d2172041729743b91d5e363f19079568f263b71c90490b6474afd245955e431d43dc80314839aecde7ba16b5dd6f6c42032e23cb3051ef072bfdc47f95edb9526c1ae12f9520167ad32e2d00c4d729e29638292ef5325e789c1b2de17bac097a99f7ad57b5ce89af96de637911ad1d6fd99ed9bdc84c82f14226bd8233565467c51894bec8321ab87cc832f23d257029ae4be49ee3385813fbf97f904a41e1469003ff532220d4b7a809f12599586d47fdf4f5226ac61047cbcf7372770d9ae54e802ff868362bbbcefbe1722633ed6628feffbf7fe5412ab6120f8df778b3dbe366a22dabaa038690b6c176d5d4dc4f53db1e3aa8bc6dcbd2842fff6b736b2a537684ff7f679506b6753b6960b82bf075ffbedb9f74416ed4416a51f647b782dd0967def8548fff6c22e9cb1a6f72971a42ddb89b6b3ffd40b2765f5f8defb4ff4c48dd543ec4a9f39edea2dcc02e169ef4d55d6bef4dc33e6e47c34dad23891c38175b7ac3d177483b6342edc615d1fd6fdfb9b18b4a4db444c5bda078d9a86a78e7d99a9f3330a03cad9ab558d66e55ebe1965c4a1c1df97cbcf264f199392bcca5346a42966858603ba40e001f367042816ad248a4a08b0a036682f8d944b9f19832b666c06a36f70ad6d2db304a659ddd8a0e1e3014cf3941181d2ef3420f224c772bcb59a37b827d4ce118d43f67f27d97fc3ab1f02df3c65445490cb7116f5d43f372201b9ba108e8f951e510e805aeb737a980c444517ad13de9cb557f7eaaec2d8d34516dcdffa71fed479c4003ccff9d353a77bbc5bf3fbc5e611dbc6a654e52ebbdcf34b1172bb3b9db32cbde3a3fb78cbbffa84377cf4faa54ffde6a9a2d32a96e10d1f1b5b077c401d0cc8f47d6cdc68eae33d4dd2f0d13fc0824b9fec3e369c877ed94f9bbe8b3eb33557322098f8d8a4f9e16d1b77c6189a0a5acd474e7fb8c54ddbb452285b91a5bd50d6f00af0f735f41e0cbb2ad284a07804d3ead35dab623665aecd1372c8844092e994d939411628a6e0198c7918bc674645055104c6542a4b82caf44e77ede70c4f50c147fb79ddd3a736bf19b91ee36306187cc426c0e5967f3a885980b7916e38e54d0c0c3ebc454b9f0f09ab10239e00854171de0ba210011fe97b3954104f78fb7809cb1af042962d3233305663eae9fb9821c8795e3c4de11c2a991918ab91f6b4eb8311ce3c43c7940a583c214b0e1971fcd4e3255ccea85478820a3e4e197e2af3aa9fd9688bbaf8172fc21a4e1f277d06380fcdd31cf3a91c1a2ca39a99212a26a4386627258ee574026badb3897e13d18781fe0cf4a74dc8ea61f34bfc6d5e8864852c1e355f23063559f342a48f19c29ab05db445df266ca222a32cc2e0e3128c3727f07ce12e9c48ed80b5bfef69e30d59f377682b644d31859a574b3a27b0774de0fa134c219ccf52a1a775789c275380e9b96f43886aca6e90657f1eb64e2053b22b413494fd697c4e1d340c985c75d8aabdf46a3b3ed96768086c32a2ce28bc705a68d756da2b26be3bc4003570028bacca0e2da001066040e440041a988204125415fe040a3b45f6ffdc89152b03db8155135988891055a44c4992c3152eb21c9db893237ee4c08503469c804a530894586dafcae41df1c4a3495ac0d9a0b4d1f071177112362c91fdbf95e747ec7849e2a8091d38500325bed8b00445033c9cc045963b3fb0207bf61f64d9bf3c653f94e087a4ee89c9d5ca756f9a58e22bdad087cdf36fb8510e9c524aa9572aa5e99ce2f412cf57d988d9bfa7ebe662e830b6767b7b7b23ddf7dea9513c201c1308c7e3a0d9d66a6bcde95208e97615cb0de38d9b3afd339cd99dba53778e9bfd33776898f9d569adb4d25aedd7adb97c3218402c56e573c6af070f159cd028d90f45b97f4e81752664094204566421d301291404074d52dc100229432090197dc9536614cb25e42933d221c7d4f76e0ded1b674d04f281b346c5ee426ecee8d744af3b18e02a941dc78bcc8026b8c4b9968de78ffd179a58dedfb654f7ee6127964240bde55bbf6e76cb2c1d4eb7f933b7a9d3ee7ac28452a1ae95a987e6cf14b8dce2868286390ea80d95492f817323878ffd5a1461390f18d7a00aae5c643d389163377aa882038c1b394ec841450f52fc0338264f590f2ae881881c685c0084113d1ce92b30ce53d6c30d38c030ace82a300805ade188129e04fef2941d794287602f4fd91126208e28c9e58cad64475e413a9c28e203c1aa3c654786f8103c93050eb8060a17de98702f98260a069904c1a0527de11779ca783892cb15dd01bbc853c6430db9af60304f99112f3976420e15563f7a5c5633af8a50911a58398ab2258a23b2f0a102c5061b84a882042a7685b3974ead7e610b9660594c896aa24357e1c2c20b182194a4c4b6408107ce02cd48135ee72933d224c7e42933a244a679ca8c4ca13e9bd239e79c5303b5a9134ecc39e746bbdaeaa2b53ed5a76aeb537db2b73ed5a7fba46db32a55aa54a952a5bbab501078a5b1b363c3c7c729c5f9cf196d6da7d5ca66cec9d223ae37d7e8ad76ce72ce39e79cbed56b8472d9ab5abed9d36af6380e9c5deeb2e77db4e7be48e33435a09ff7ec6a97674e9c7c819ab456992ed8411c3699da6aada5b5d6f9345fadb5d65a6b9d552c1d90a958997873ce39b9279e9813d76c755561cc7951983f5f68d385463327f744d3984c1678e6669af1c036c33d61ef55799dcce77df562f0c33129106f3729cfa415a5945e23a33c6788c00d724f187dd645d2e6cdc9d18d724e7cf5561817b85decc27e524a29b5afb72e92703f2dc2f5050aabdd1196969b373d9b1a67d9f4cd37c1299641b33c6199fe8c07282ccf67ddd81667e0d4526b2333c5689369a594d2d77ce26a53bbe08e0cd8baadb470a42d1a36b98a3b5c469b7c2dd7e171cc49c2f29a6470db7a7b58615c840b17239aa820054cec10bc6d5ad4a00628b87265d5ddedf692f70a3984337145e94a131925273025272f27a55aab5d72aadf8bc649e986dbed94524a6bad6ee9cb4949ca95a32b448e9c9c9c9c9c9c76a8cdc1c1198213aef015bc0355b7cded923b69573b7a3925d17e8fb49fe2ad3e5e71be9c5e575870e5e5f4727a399980527a658952292895a20445900036022296744a9d926e084aa953274497d42175417422e88eba241d10b585be789e4fa66177da9dd25a6d3ba5b55a5bedbd9ab66d9cb6715cd779ded779df0782a9540c988a89c1584646a59aa1a171e1e2c50b18abba5a51ef958c6a668686c6858b17302a0c18d41b868b235ecfb1feac1fe41903b3f2cac72868bdc93923f6a14163db9c87b5394f8c1acb0176d96a9b3b72ee884be25ed58b72a7b55a5aadbdf5757de3aec781293025a39aa171f1c2c6674d4d5adc8a17c363cee44cffe1ca971c168a20bc504a4ac271d1c5fa91bffc8513027714029794fd4308817b714ddc14aee9331261e32c14f76a920bdd08c98b8a36d88b1c1386200a9f93080208200a9f130e0054dceb4b146d3111f541d4f79a3f5eb264d9b2c50327f2e006447297bb401865ba4041dddcb04268e12d5a2c14cba6d51a41868c9c113a8b959c64a874ac5746412b95a11100000000b315000020100a87c462a13894a6b1347d14000d7896426a54988a644990c3308a8118c6186308308018828c21c628433536006cc2b3e3b141b12a1d4f8797fb6aab617846eecc6d28193c60c203843906d2d1a5b10187d5baf848afbfe5cde06b474f24139841fc203477cdde173d0b3979d1529c6517db8d4e5d0d1eaf49b7d361eec6c14c112a1f9a82b68b4db6be9c4f9fc3a01753e8852965fac68873aa80a9db0cb456f1e63765bd6d02b54f295b2a4e60ddf5ae0bd966db91b7cd853844f1778e73672a1471429fefa8cb74709e20718bd6e9b17fe724bbef9a1f00ea7c9d51ea8306b5564799c261ca6aadc5449631d829e0559db800b333ce3c2e801bfea12f07364f8cc0465d358f719129421d732ae15fc2140486fcec91d89541edf44c9af35d269baddad04d4c705b3870b627de7150f5930c5faa03f4e497d20b5f439909a664c7980c3a70669fa6a746ba0b8b7236975b3beac94316061be741e925c1c6f071d1ad2b19160132a82206b4e463840914d49250b6c18a6e22ea1fb81f5d9804666c422977f48407494b240d9a06273e8052af125aa01919ba6c76a2043ae3501a42539973688127358254e2de9db0439d624448a7c12bf7f029a06787afa6bb1758f893d4652cd2c2464ba5daa6d826b0dca206246f64ba34f85ee25e7e5445e66e705f640c8ec069d9aa692a6df3165ad35282e2986b6ebc78c63400d9f31e4b62e334553e6ad114bbf9f68aa1d700c9c67f8783af506491acb4e1e16d0c7c7ca3dc20c34b67f8071edaf683a1fbef16588a1c664a684abc7e04afe6ab72f2776b20ebca0f34fc3fd19dc02f7e6432a73885e1ddb74233f1cd54465bbc9f73b8dbc4e575df14641ef3e6ba66e558b0873630c519bbee2706d741e0e602a884b3348b380b51c4348079c14bc1b953412c5b1280b6824c44ac5257faf9dc3c3a1dd141acdf25012fa5a8e52013802c08556cb55063ae34c030fc376b1bd52f441c69d837e4c691938110a193f18ab57bba625af702275bb47147ee0f7bbe3f04936dbfdd16073264584b624c3b9efb93434b01f2660d7535918906dc1b4624396684314d943755dcbec549dbef4354323a3afa8dd105d40335e3c6f0d07293f4156aacb2ec8ecfc0560cbd76e46535caa847b593634a968261efdfc514707be2b136cfe0e08a513f3ce0caea52c726ceb989914e3853d12ff591b8812ad1bf9bf1239595a3deae4fbdef870fa2ffe1c4b387a1cf024f5e1084895cccd90e2aa8bd09a36814d568b27a62abc7ccf6df9b23bd5d5d380803a315b335c08aa88894c4260fcffc5355f1d3a8e5f0c5b380d05856457d44d76063b7719f88f399a2c37a15f9ecf54972f5518c5fbb3d96e8238be8d86bf529eab07fa0adf80703e1f30f4a1b02f0c5b0ddff0a6593523c8239879a2a154938ac62f4b7369eb4584f1813dfd86ac3821343919d2c6efd58a1ce0aaada33b4be6e81f0fac74f435b75c6a48625b2fb46917481c339aaad531a21cce00354fedbdf2fbdc5e4f5226dedbc5a577542f325e122fb845f2e3262c635f69b37de81d62b5ccdb472c988a2ca2031ccc139f4aae08a4546e75c345d4e1d14d1389a74f3ab4055784e4779abbfa51126a232e4bfab17870030dc0e8b14f7eef8032b5dd401e1353fb2707c4ce6a9a05f989f739e6c897eec5f1918e1eb81c41827e67d15db105cfb3b2b1ddcce88398b0b96fec4d1fcbcab97c62f321e0acf1b6c1a6140dc670399f166ccb70a61012036187fbb2aba189bebbd2b7412a7342dfec15a92d14e9c33e2b1b6de4f0f153dc288da840f7a9546b04b809f4660a11838c809011dd301ffa1075015b41b17095a053cf13d3c119c30a84cde4aebd37ad3bda694b20d4dd26e71c5b8b104d0d932311a753b9ab37fcce7d4ceb84546d8d493025d0f3c96316852f9070dec75b89ccbc60d0d25f35122818b641ed6dc41f3c0ec12f674344304d94981ae7c67c6af047dc5c316bac577668cec383b9c665148688de5a400d5c6eacdf4b6c4be59feffdb624dd726b4b4b2b8c0fc0844df44c474d244889af7f0da885faa123e54fd46adcbf40099c032d8a7fa7eca4277fa6c313baf761901a49de6157b722be7d256b5c8dfd9b060b960fd6c0025b9521eb9a8f4a3a5142162460d105f841454b638f0c93f0210e9910eb69160d40cb51821b3de3a537e90ba2b659e7cfb72aec57dbb4aded899a34c3a94f0249f20a339e82393883272e2d1d7d81c219604d3d13c4469508bdb0ef6cf0029440acf43b1163be709d7ffe1bce353df6a68350b8b8a8330b8c948bb0d337ac7a37b234c0db58287f21b7568116004a603436c5049365592d50c4ac3d8b5c8779a426809d64a69a188234e4921a6592573566121df3477d5bd48c225f4430925d9a87de15f43894756fb222b50fc51e555e4a1434219bb079b096fd290797b8dea8165a701f76ac821371999c15aa021ec886eb7ea3571b89e0f755ad3aa36df9f5aafb4d217e591965698854967dc0744ce200d44721c752c8f2acfa05a921f4efd3a5538200edb82b7c21d486684ebca71f400a5fd791fe85cd25e6232fd830b9f7224091298befba8c69827aca409a875db606f8b69d0a2c9b142fdcd40f64a5572f2226208c740188abb410fea9edffe845024d20c523ca9665c464dde366b63656bc327bf09c4a66f7c1dc624a5da6bd54938af4f0783ff8e0b432e869599ced5e7c61d4355095631c014e5f13e59e99a319bebc8d3a728513587c73b14a91296734ea3b76f86e64c8506e6971425f5ecb7c4623c67308d769784e42599e8b0608ea131f557afeca6749fc0ab429398805efb437b2dedc183188ba13261f0f78b5c8b06fcc29abcb473d2bda7c5ff6606cd88db5e878352bc5b670580d899aad01e2dde00a547fb7dc4ed0c9de7fb05f55e3e50bc0c65f81a87d615f1090782af231ed8eddfba1a16952edc75e7cd70ee633f906ebec76957459d45ea6dee0933bc83cdc6784f70b580bc56b193bce9d1f6d0009ad3e88ebf42234af9c10102904c15f4f512f61255702f1d4112ed1868540142dc9f5977c130781d55665fff0936a5b92dcfc752fb8dd9c75730b70a0f3f6a40acf0de987da8939aeff56ae8a1feebd70ce2d6f9b726977ec5187f09dde1b104f8c40aa85f7284eaf7f457fb451270ecb0bb9cac5d952f19fe58cb7e452b22ff8d36d6a284328d5330200aa8ffbb994c3a5801f55de1a908af68b407f182bb03cb40350f48c0d2fc8b797c825d0012c6b54194ee6ac6f32036e4024871d7713d1709ac550fb0bcf63a7949ff21865836c71e5dc1847bb3bc614313dfa391e06e47f5752f032c32506ed532f681f5b9d7ba52f39224a6a783b0d1934a6080e2fa069538b09031ccb6b3d4ee1598f5dbf394f649c0c9bd5b0ee67beebdc6bf68205655f5e052dc8cae815d9c5a1b6a469aaca7084a5b4c686c2ecc1939bb0a552cb48b553018ed8750fedf3056197cabde8438d55be98140544e3164b244643aaabdee4207819ace4205db2cab9a4d6ee6fc4a4b8a6ff09332af51832e0ddf11235677e7ed52db00e0f5511f26c5b16307e1fe99c64723e584444c5a12e83fc685e1fb536a677abbbe73d3b3349a3a3934a85864404fad58e33e5c24ee5501b20e66bf1958ffd0ab84511cdf9ff1eb3ba684ad83063d09de22eabfbc5799ca793cec55fa7c910a429a4382e119540dc5a0813a48cbf8059c4801a4e82ed3b012be1cd39a232f11e63fc3af8a3e563e37ac5a5790d3c66a04ede72bd14580eb569f24675bd3c99270605bdb305df92f83283ff78b079afb8a1da4e9a180acb1cfd267501947bf0bd8f6ffcf7c104d2f9f65cb6c2fbe2e07152ef3c15f2535d7a8ac51a539ff3632f2925bc81298813270a87a73abfea574919e030096b8c5fcb57278a31e54d5ccac7f27058bf8c722ef035626315d186d2bc853ff3fd56ccbe805d7a2218049a1b10000fc6161365014c4072272f8682e554f9b88fec03981481c6543f582e363c1f8665496573e118e0ba1c5c6ff4d7de902542d8fce6df6dca45ac9e2110e887dce6fa662741b650467051490fc9e38154363d0dec90e4b949e9b93c446f02ba00cc923f22b900c6523f8ab8c27e14a5c5dd1cd2c489ca24e1ec32b9a520d879783b1d144857059ba45cbbfc6ce361fce721fcccd6d156e0091bae97dd6ac27a23e789be9d43f69070fbb125328fefb562ef8adcc0a11e97c1e87d8b8f255d6f79a30bbdf86a0a2954d46d0014ca5d68b1a5ef2a6cf6715830ff17f4d7cd753ccfb959eda38cb807f90baf886b4af1e970459897bae9af624b4545c7f2204fedfaeaae9ad9e8eeae9bfea3fbfa92236b16023d78ef19167e06097d68d5ebf47b5fcdd29a266001f8d8ac3545829e8ac3a52ecf9a24eabea03897a363cbab38b4fd43d5790a5d85826c3bdaf742563c05d34aade2e2849a50b3673136e9ecb3a2020da62e2d1a8ecb8f80e48acec489cc4408eb3686ee016b90a365166794ca2820592c8e3039b64d23b09b2a2b044ef81acf026b4f455ef29baaad22499d865cdfcbf723608a0a2aa9490c07602501d2d53942e980d96303419f0c8ee962cbc07cc5b74b9ff86fd18849e91a0ce9c6d81f2c5343a3fd241546bcff21f4f85ef343a93f9d8d446bac07a187c66c6c72762d89a5c66132426ecab90ccbfcd9efad456c7967350f26f8922b62865ddfe5def572f5fb1eddd9606975f51ebf7ba606b41550be411f54ef1d9740506b9d8b2fcf2a6d502a56f7fed3a01b942642609a15237b00326ccc5e491505a057e066bd00822b5836af55ae66b4fe5402671ead042bdbd1492ce9a23996ac062d8ec34cecbc0d5e0ef547a75f45f120cf005ff05926c271222d31e83d54688770d40dd0cc63d90c826f6299f0ef957452fb30a2bf74e47719c5adf7af00203dbb407c517c87f3395cb55ae80ae02735f7ebbc44c9bfbfcfc3c4d405e0952f55dc7f42a617b6be0f0d06aa222a03e3628e15dcf9bb54c71a7f12d2174882f8b07d4cda475ba3095e318f0e04da2b1c8bacc3d75be256f0aa6458809c31640655d7ecb1c57928b916d8a1272ef975b15a5d1b1f316a6ab74059131e29c7cfe8bd2431412301dc2486b0b4c27bb8baa579acac162046442fb800515a6ed2817da78ba79e3ced4fc2ceac8759040214bc0ff80b6b860aacfc7e16b6ef267594de1a993c61165cf38159679947fb9931a71957c3821c911a328e282b4b511a1a4e36ce38b4aa16b12acec7eebc69f2ae10907855a0042607ff48791736dc737ec9029d4db864f8533c5258f626601da1953f6fd47549d11cbe23af6440f806f3ae0a94ee7f8d403accfa7874a9d056ff5b331b91501b5966bd3052994412d53de04d9a43b6463cd6cccb38130a4abd3f3f1789d18537fec547540b14605fa35af6e39bb27e8c54141259369d3d589dd06f0d8149baa5fca6550d3162bb2c3d1807c89f39c44bceccab585ee5c660cbad2e9995a7df9f87c8ee2fb38dd3fbb8bbbedb1dab4226d3f21f94790a39e525bc8df89405686c7213d1ef04464e173c99fd8e4f01cf134547df72b21f2af6cb87512d36b291f6601a1c298686c99371407e89fc2d9a1c9bdc8e902292dc26681adbe9961db4a79adcc78c8f761e9e68fa455857a735d57a6d35224f77f0e8bdf48415e48e507c1350bf8fad01ce8b940c9ecc5575a35a002754e57afa82a90cddb07264b43460b0076684c561ae5cfa8c493f56a0c6129aa0e4cfd15dbd7eb56934eae84d6824331058c4be9f876013f888be68cfa678a9aca454920bf37ac176cbcbd6e76efb0a2b0f5fa92abd67dc764e971652ee35f48b3cc1000487dde0e8456a95e31bbf9b04dcd69fe135cea9ecc4e7f2d9de7430f031e91201f30bf02740b5b799c903695ce427c78c01de6546055dad07893cea37626e9ad52e54118dcef605756c27781db723067bf66aaf1c11b5d40999b98be8aaad0b7c480ce845897e724f750fbccfc341c84d26b0c618ce73c1571e4cd8d82d41e7f13acba7e43a1f9d2c3d26f7eac01b7ba27bd961ca3b7d6b1bd651db38659b14b2af889f49ba65b9564c4730d13e991b17d6df50a97f4a378a30b72ce12177441f360eae8df938633833c350a09732c2aa4aedb8fd11f393b2f7073e967ad43f257a15297f4bdafb7343fb0e2694e81b27609d03c9e3b2adeae813bfceea76a438a5594f8e84cf400f479b4512e2c3cd1eb87c883f423e13e31520ca7fe8a323ba5fd76e887129874e2713cda8a0d201b29213bba84e199ce8c0798ad7b4648ccf47f0789f4b23b6a6ab16bc276c19dd0f674328d801ad80e96ac1a5ba1c1d4a86bfbdb623087e320736b359765f423d8b60f3a8e801c63aecc770ee7e196dc2a70d402590d7a99209974d51dd22b4a4c0ac94bac9a3cd67d5f502cf4d6be97a94a23c0839e5f1f74fc2a0c4b150e4f846686f4007e2ca10a7818ce2111c1290ec8672821404a5cd8881eb06cec8629fa8083865eb15ff02382b061c1527f3ff35f85dadcd4004316cc5d5deb92fa567649a8a8b43d7c30523eb86f3c09cd7fcfeaed0e0ff12a0418ee502467ba5c64d9210c3821ef45a1cd73b33689175faf5e14304b88e7b94475c901d5c4c67abc59f524ee8422629411836bb026abcfc7090101a5275caac71aa0bcf64e280a4e9a709c8e61580d3f9e50a81071cea07bcc94c68858910489422b25514b02bc11e68ad1101322e8475bc7dc997340e4f54842be253950d382a8ba04b52ae3f745d11bb84ec06347e5c8ade7e4eab2ced5aac26e34a17b473bae4c72b9f4860d5976479bb2f73b84dfc3115c28ca0799716bdc38718d32f7161ada45db10ad6f529da7f5695b8acfe847c8022eeae972282255f55920d4f120774b558c25111a0d9fbd21f4933365afb793f6c062a4bbe6337288dc51c51916b031212f35c78ee40287563388bb91126ef66a2dc1a99e0bd7bd2df3d5ed87e905489bd6fa58868ca6a8d935222f9b63a09e34ba6976a18d9789b17757def25061eb9447f47d27d7bf029e2ad51488ff085e4b81d4a5a4a3ae5da36ed5d6b988980eaeee1c5a9abd6609e6a9422c84850eda96d948b8430634b0c2b774221c8ba1dc9be00b874f6dcf5a89e44a9f84cd438005800437e4418531bc8a92c60178bf3ddf1df1bf83ae1497db29f7c246474b6196ecc1b7bb640f1b5e5cfc3d489dfcdd5506770559d96e7c362caee2dec38bf52a4848ad3f7b5535d1ee466a4d0075f57ff79cd27d8d1602843a6b4e2cacdeb84b842607687e8bb5537d5a6a935b59c2b54f4adb14d49ca82a2d814d3860384fbeca6f70542725c0995365dc000060a8097c2262dca47f547a590171da85e72394267b2c4acf93f9647e323273bb59553159c88c9cf8c1e2f0e0278464da19a264365b9f4b7e62c96a73767ff52b9cacfe060ca61f338253292db2799f6b80e2554023d198cb0a7a48d686f4db5db7e764c1bd5de0218a3f700b83dbc82ca0a9fc23f68ec12fd118f0c1e9f897cda8c9bbe05d1b3bed2f4d24f850be8b3f2b0539535f3ee100ae85dbc395dd38093066499fb6c189c30078d48efa7014231968ab57e086a02b39edc470aa7a656ff6609152dda3ad023ab5737939353b4afd4503312601a4a89b33ad53d18d154a4e5f1d73f37e5db89f5860f2f9aec82f5d0dbe08e4c3f572fe26ce4ba98580b0a74cc36d3c114e111ccb05f473af307722522c7d2fa70e362c18e8b10b71b12374b949f17f4bfa05672081a8f1e4b8b7a2c2b6274dcf82bc7504f2fff1bfeddf2658cf3fe27e1c95d82affbcf75191ff538d4c822304a005ca3a27f247d5b0bf21f96bbf1a6511123289a0a712c143dd28a6f17871eac30bc1f27324251d48bce56e4d4a99651eb06b8d08f717eb0ed193853590538121036d2f942adf71217bf0f4929547c6b15e18a513ca171cab3399773ec3ca7dcba9d019032f9f6c76922b073337395619e0b34bf590cc08f887829e0fd5ab90e365e9ca1a43607f3d0090d5a134d7e6c81f828994e37997da468449202821c5592583cc8f939a18763d772898ff0767b3fa880019569d9fc82d044716d6db8221be3e9f63fed67d7e5c5b90e7582b0f6cb6e98f20ed8a6a115f0b1504b69afc2ca900cc7315c892d4b59197b2836585de290caf052d62e7f56d4c56ef97b6c083613e2a68d3efb8077169b4c554822e8b998cc77c01fe1eb8d30bf45809118305a6002d414cf36944200721330243e52eda4a0206ad54da16010c00b66222ff17facda7c819b5030a7d79059b14b91968062e6e66e184c0998337448d861747f10074988838f94b4e7d298f8688c7c177be7a0c7398bc25250c9a1f7f7157a96e08197ed372b406dc47bd3694fc3664dae4828bfc5589c6f44fffc23fd3198314bbf04221f75a0b026a9881219d1bb4a612c2d6d090a6619ec20a339207e35b2217291404af136cc1f64e15411fcec319d831b5a3003743506abddfecd8458134461a4ed6b55e06ed05e978627447cc8e674560fe785e0f55d9be77ea110df2b68817b31447ea5978135bbfd676ae6677d956e05864406b425ca66d1a2e4a9ff12518d1d6673fa9bc16445021bb0cea2e94c84c482f325419ab2157ee78378ce659128bb5c49c5ad6e4617c7ea4e0c81eb1f8092af6a5114e63e894b45aab9f0afbc27d1297c48dea0536de5eef03171b93b9e7e165d46416ac35642ec6ff8154468f154fc0ed7a0a408fc3132bea6e4ea78c1ae36c57fe990be2c6b6046ea30ee758ed536c2a69d3eb166419b8e9ca06d2eebd7aa31c9de2b2f17fbf4ef17f2b73c54ec97a5bc011218f8b3c4891a0a86946aba4a0d8ccfc1fa177955697137e07b9e88a9bb8b9f963e63d82e3d8c5334ac76f66047b2451d848faae97170a3f38e27b674194b84e28f7af59f6656219675632ade868a9e9fa83c47f220018461bcf929dfb237e309a4d2a6b8f2814336dffe5ba13e9eba2944d91d356442c686a064f8a5f34ab39aa2268cfa1329e7c0c0bfd97beff217c543d02a3d84ec3df1cc42d027fb00275fb6e4f4e79a1c2776c738b3ee74321f0448d2554551391e3e5b3938e8ab1f6817339ae786f221e73ec3bdc5bae87a22474281d880b181905a64a639367e250febbe4b0c5a4245be1f2a75b93316103cdfd2639ea4aaa8047c4bc417aefa80c1207d718aa1165a4bdf11e9579efe1c5dae31ad801281a60c53991fd0640b3a8b7f657cb0383b3686413e7c7a7d898ce1cda5f1c93a0ab5fe581f83d196895133ec340e79b1b6b1af60fc4ca55ce27bc07c5f2e45a4dc18d171ed3d6516ae5cc200bbf6540a465130e199168c8148f0578a1598ff07828220b336138c168d5e0fd14d6fd49e0be71c3860ad6770dd7fa5310f845114f0f465f482939dcecc745c92aa776e1b2f8d3de5a98670c1514da3901cb37dee01ac9d653855dcf1b9cbe5c2087b7e468573c792cb1a8a8f0bcbd05823f5547ed94dc7eeca6402358d6414399af5ce3841950048d6e35c82559e570d5ca14030f25a2d8e1d6d0ad63345fb7673433d350d9ffd079370c1941f5cb39364ce396b603cdb65248d7eccbf22b153fad7a709345ead35f730b0da8f7f86724ea4218c703c106e306cff5e284a44c66a999f2360aca70a42c8dc168ab89118a2d06b6ca438a8f7e0cf7d802048eb6c2b2e496c8cfde4ac1bd91395788648036474a2613788ad5b3a380646e6443a460d0b312079303e8092a4cc435e1264fdf13b1f9fcdc9c9f94bc3e98b1eae3e1779f83b9ca1b3b08054fa0ad13ffa683cdf22bdb92d959230ea630d5e2632905609daac486d7b8a311bab00d34bbe55d7c49e0faf8e72fd3a867738ce0b57f69580c2a20fd43e06682843660ea2019b29385935dd1e5e6f9866b7751e147c37b5b36f0509cf3e9e88ad987e34d9f487efb7469428a474f23d3275a82df4f74b1ab19e27c0a1696cdf04f8c7c87eab9b7d1fbe94a8015a3a6d447ad10e907d90c7943360272a90a3f643f1b11a927b860d03fa7c67e010b1d8467e232d0dea847235ae04a9fc654b0e4a0fbfc10c22a390e70bfd00bfc7081c818a67d28f639932260850fbf321c11157306c867c0027bfd35056aae44093088c0eb55f7f0b1f7a6f0f003984d0e4753f2280607fc3ea45225e03625aa9358420a8743c0cbb1539ec907b98af00f255581de5d5931d9a5771dc2969abb08223a03a6688404bc5f7bb7228adde046e017812d53d70ebe8058aba3bfc6cec3c108c278be98108055dea2f7adeed5d83548fbfeb8c1b8050baa36f2727448a85c3185fde0df72ec19393be390059039d2e2e4630b1363bdf392700e738c07223a1b9a18fcca631798056b9227653a6fa1b267ce5678fe15b649ecaaeafa866995ea7760a3a8d5e89bfafa149ae541cd399fd765d1471bfbd0453d9b195a40f919703fdd976ee6e1d39f17c1754ceaca44fccfab44e9468fb7ad282ec0016fcb6b91290c3d31cf7a680381d16c65242451c44a429b8d889f3381f12152d14aa2ce191ccb5e2a20605367d450f6ff0e700dc789a4b9ea5a185b79e309db748536c1559339ce8de800a10a040cfd16c726b3c45b9823252254e35118d9dd0f083275f13476df3ffa9d7143aa80483fe50dae0c371b931ae12dfc3e397d7cf89c3a461bfb9a97911da8121e8d0377b79e0a88d4889c54df89063fca91a6ec30b83a9e8f4b5b9d78fb4450b86f81d129687ce0c083dcc05b083641bb2177fd4bec2f04d1ff0640b28fe0d0d62917649c992a56d55a97755616697407cdf11877c494416f70484cd196b32befbb06ac4bfaaf4a1d05245663d5619b0592de753bd88e9d2158d89c3f1bcbd61796a42186603fcf88c9e3b038a9000162a47486c1f35ba0052bc455542dfcc12f86692fd29a4b7371b432899812be838e63dacd2ab8b051de00cf4c408af924cf7b7414fddf07da81509e2b6cb4d6f79611f066b0947a79f50bf483274c3c2d6fd591584dba213c1e73be786459d2f35a22380fa1c0902e9d5c4534445248aa93f142b98805dacf41bf4ed097d008b89c991b6b7929a5d64532d0b9ff715638807ceca328d66c5ca7d13cc9b0cff79d69f10c57407ef326434ee18e5bbbbd013789d6c4900a3bf7e8b0cc03d7cdc6b3c28c2c65bca5cb472f768f8280852a0c5bd5d1f17184f3a6687b9053868fef547e845152f0d6b34ddef37dbc5b62ef0de2e5fd6473dc6eeb8916d61b6afad46581b6fed7d24b06f802bb03921a69d66169238ef966f1d27f1dce5bcea0ae6747183a20d48724a4ee16a9f3c8b04abeee883123570cd81b80ba79f955cea0461dfc149b8b255ee1a88632d41e3661e80aaea5f04a3b000839b72dfc6dd804511edb10b7c956e75b7ad811201e6903073c92b81695032dc76f348be54062a05e8a0f3b2a18ec35484fe41931e654350a7dba8752dd7903434cc3dc10447a6976b010c146a4fd3e1c1b11edf78aaa41c5d6c19a865c5d8e71ece8832f1664010be4905b8c5e316fd094ac9ab455490e8de5b43ed2147e00e9114c66b8c60a6b39ac58881883a77f15d694244cf24fa2b652efb546ad42afc38878bff7df5ba3b21dcaf4f8ba7821674f82697017a9dd6c6949ef7a12297955027f0fbce127ff00b54d9c5d4b29d426c98f120504d46cf482f6e96cc5a397237f688e6780f8d1a6cbf102ba5d2d96769ca3d9b229f94d363b2670a1b5069c469f8141114ce986288217fb2751259b2c45898d9e5bfb8c429be4545abf7510ccf2f5eea2d46da9693f0c54b8990aca4890f3f5ee41c570cba29199dac505d0265142e0f09845249547a1c8c648a09092945d0d7be3d80393c0b3c1d1b887253462bdfe688d0fee253012ef90d5f90e032774f8d1a2c8c9615331dd26af58b319630bb2da780405bae992b54816e4841a96e0e20cb863bd5931ee396e44e248027188b5c7e4f0ec6bfc7fe23c267762eb776eb022e2d54b5bd52ee146d4aa4d6e85edd15dcc60aaedd2ba79578d8e180eb6f23e8d4d91238081e5e021bdfe347ca7861ad91c7ac3011ab6c385854a75817f5d9eed393df17a9c60b698a96a753fd646748771bdef013e2683e280cac400aec9701291f5d4887ea647b66f0f4d4f48350dc661663e74fd26832356dc0cf25a1503de505724d2d854a9249b19153c883900a9924ff8bc33e263f3aa80cee8041ad10c8cb484aba1703ee3862dbd8487d1286fb130924c48dbfd45b21fd2d35f8468762a8459e9d95255f2411871d7d1bf883983f59cdbf74f6c60d1c2e95f9884a95d400ee5b3c4906a18b82fe34bf3a061136d136a8fa1a849feef6e8fc6bcd769dd50c0a298f5e10c188a493318ea0b6bd902f6984f0b1f9f531e55db2ef4283be2c4db97773dca09b22cda063c831f629a3466a46d2fc5306e1cd5c8587b953bd3d06eb69b5771c10bf07f611617da11760602654205ae02df1a31a601d1cdcc567f0e6cb0ad55543bcb9f0a040ada43df559b2b91664ffc6f8a2703f33ed33b4fd4ee7d3e72ef90189e5ae34ad9be44d12ac1cc0c26352e45f7f243d64c8262a237813a7e4f59e09299d9f717634a91e1a94a1e5c94f589234229c1e7d50c325851f1025fe3272d46fec667d5e5c241c88ac7e6d97403e4d30664953511202029d99487281a947b1bf9d38a3110a87a1f78d0b126691788dabd0d84c446cdbdf94558269c50dbed53842ba6612999b845587eb03a2280f784c386fb8a4d6d0624b11cda9eb04caa2ff0059fecf266986c89fccb760804833783d0539ed62e0df7aa40dec0d9207a78d2c091c07df06e845283736c05aaf9facb58622ade3a5323a2275354825be7d11bfcbab7baebcb3602189f15b07392874e231f3331cd2bc10e1ce4593c06598eafa8f0f15069f649f357a1ad588559f5945a5096f57bc035727e8e03f6c6c554a46ed5768072378cdd2db2b295f952a68c769f1281e2e32e638c5dff966f6aad7e7703259cb96f95185ed2f902f2408abfdf88f075c460bd216096861c8562f04f1c15b20e69dd66192d2261d5192ea5570277e2ff34f6ab8f1a676ef59d3d94b30b0095b4b6f350ec09078edd5e61e009cd15aa574b6aab77f004fe19a28baf191475b4f0b307a56b44555fc975e9e5eec1d2550da68cdef5236774b9e84035d4bcf5b6530bf7181654649aaa96fcab654c15826ccedb35611689dbe48f87f43383cf030c35a93356aebd0acec106d5d50fdb0073176f5dae9d30a667f6749a3458a7758e12a34186aac1109abca0a0ad685157819373d17626aa5c62edfa9cc330d4357af625b7e41c0313be2070d25293f618b034d459d99771c417c2e04824dd83bfba0c0cd1865055a1e89bedefae7901da2dcbbe5c1ec2676214be1005e9c55bd42d65f82fde5acd1eb959ef7905bc576660f8548a4e7c2f6453a524b9966190a3754b31f9fed7164210b2c92b69ff9daa0202e706f9115ab49b1442422161da6001b1f92f33fb85affeaf41598d25e5f6053dd47f8d7e5ffa15be2c319423f1030bc1bec0064909d95735eb116424a6c77044e56e9a6c1f783e7468b8a9612258b6ff6a5a4c02ebac808844aa1c7b086249cfcc0815f4892fcdb806df84f23559fb7d6d7341af0bfeedd25d161231365fd5963593d8e49f00a8a4d1e3c3641f5ee5d19b8c1f3bcd69301b0f9f2168ca0654a248e9f5ea8e7618e7dc69942129e16a0e1bd2eb5c8db31de92c7fd3c83662fa5107274bfe823b2893306402e6a71f77d28cbd3a2622d73526657c755e79e69862fb4c753a337a130fca5759e7f28e0b7db1ce9493efa7af401f5dc70ed29125c4aee35225aa396b304035687578ed98142b01c2bbce3e9485bb521f17c630a97cf302ee8ade1b525041a87259b4505b6a2e8c78ef79db5a5c7ec53533fc2e3fbeb054e98480e27f5ac87f72e4e24a2c638fb912318db7fd6140e8f400542f4c4340747a177e73d899fe43ccf44ef738857f9d1e2746b39f0245d526162abae1b7fc9c9e70d6e61cca3de89f174602e9847410044c8f80d8fe5217bf5492000509831f3e69a43044163dec479518999e19fdafe0871f88c21f7c052d8adafe6c19ee67d63457d3c4c4e4437f9d396e51931d85558a942d69f348cce2e70149b4a124ea380414c7363199fa4b475fdc2e3c55075004cb538d34e291f629fce7a5d5d10634131cc57816b490da900048f489b9198624246556e42a858d415a1c79c1dfec37697772c1ca2f19af2e1ac2f228ef9f78138669fb9cf05c7baa1780cbccc64cc9fa14e04312a25a764eca76ef3d7d8ee8cd9b2f80a6b73ac890fe71a4afa9d95d2b6a1707d3a405b65a4ba38cd81211847192e9baa21385eda1acd13d293ab0498c3e8e8e856d74f64d1d4a50577532490147ad1f742d744d87834acdbd6c78f9faf2e402faef625e37a4448cfdcdfa1504c818e68d231832c1237dde651a78865a571d4a50cfd070fd024ccdd93b5fe20ae1e4e41a89974488866c4928037c52bd3bd83a2d8ad676f7ad507fb18852f23bcea5dcf092c3f4a7ab8e59b4859c67826776668f208c112d6832f70474155ef5ee80b6c69af29c54bf9071596df1eaf53463367a34130799430c9ad3f3665f3eda858be0b9a5e6935d01275708141434b6f2c16e725e2cb9a100d47d09a2841982c8d851369a8279dc9c41d00a5c5fa70dadc64773daa8a3831347d50466401d688f9a4ab8c557666503b492386e9c08a27dbecc44fe2018a0aeb9d7f345c6870bec8429c09cad2d3b0d382f87ebf0efe9fe35c20777aad3fe5988e3be161e53df688e3e4156c1057e5af888996e9d3926884da021e851c1459510f89734a15244ffd9b3a265bf6c8dcf9f0f7e702504c296910b13d1774de0b3e51a693fd582e6f8d110ab7f302140015c208587bddc2007345b6436ff5d23db0ec2639f0c69aaab3b515d71a9f73b0db2f791d1b421e4940a23fbe7473c3a66a3ea87a0d95213ee40061116d0603f0e6dd734e05868dfaaf678ec0ce29cb690b33d885b3324b29b58ddfb93b97f30e379beb8c5da9c2d7367cae4ffeb9724d9f74c6c45a26cd189aa171389840a8ce73a1987a9255f0c6808b16f33d48c5557e07825ec04e3b053308b743c889c1058ff36e62a0cb9f6330e9bc2cf81fcc59368b373e6119b872e74ac1d92944d139177fc8e2aebba40db30cdd41c55b666bf40158b67b48a08822e3f6e2a0c4ae77cc11c1be29dea65003ae09185db3647d5a3a70d71f0f40dd5f237538ece20a110a62cf6a2b907a0d8b90f9a7cf4f669ab175b3dd04f31ad83e8fd60f4023d78f503b9d24614fdfe5c223a72f24cbe5f223dc7737d3aa05210894d78b1dd92f3d6cc1a8030b3aef13a63d4cc9e2d1ffba6d06397b562cf9a34a453b88859f10c2847831c83b10cc126c3a4bc151d68807e05eeb071e21cf408921a80c1f5ee10b26a75cdf90adeaa67410e0a54948efbc35628ebd9cfa24eb0a4c5b3456ff597fe1dfeef069b37f5ba96858b1a8c5a29fc7bbb72f302ed98b00a6cd5a7ae860625e2f0806a8ae75578e80086f17104da75c84a7181577edd6e29989dcb35a5f87202d24516b29a31903b16cb20e5f15a8d4414150ef52079471359a28fffb033b3d4edb036b08593f3204ae3c7a5d712ebcd573d6d948952e7d4ccd43a5aa885ff7f1025b519555d08120d7f2184d5372d6170d906d48000a72946cbc854d67d67a1d6885b1be507ac92a23343d4cb998e23790029f65ccb584254cc1d0605097d231855a3ea72cdfed19f7b01573263f26990eb9b7f2c94576ca0797f2fc95dd4c8ba954525856272192ff70c9a90b4936f8db86165a81e0cd1678e9f2bcc1597e62e8b2f34cd39b3dcd248626d7a98944fb96d5c2ddc779a8529e230fd96f09c2deea76b1af3e7c77f081b126a7b55868414220e935e6a63d7a291a8aafb75213de360d946571a95f062699730d9a5ef51669e23af014edc613426a26ed182980b7d94a61fd2104c1cc34b0a4b92b55455661e07afb5f5b51f99ecdec56c444bad73eb4592f8534ad3bd44f98352c2ad411aa228056000a2c687d4c8d3053d06f4017195efb4c987f8b88e0690e077f588f549d97468a506728b7e85c3a382600e6064627aead5c503d7f4a03667b9971874099a1a369a556643959e2ec799ff7dc7e5385830ad4756fe8a24ae075d8762bf5dc13bf076c96a73c4266390f07eb2039c21df9171eb0649b0449e88cfe426b12301fb0a7021d83f9c3fdbe42f67bb7c664613e9450d0a5caff4f5eadcf8ac641f9760b37ee609b32ecd92a8dbab4180ed25991b9eb849dfbaf638a8eee0e30e5de337482294c2e095dbb0cb1b6bfc43a991c7bbc226cd695508f89ba73296526cb34cfee1e98fc7f60d4bfb87e2d8f00a6c049e22c458c12e08c705158bd6e078cf1f8e6282ddadac0bc79f2a8340f4310004411204320702e68667d7a0bf32f86601fb14916c261c1d4b439a91108afe5c2206b0e2028228305426e8f4120aeaa0b8e2e02ea707386d6ac03bb2cf292037e4aa8844c355183db89ca0b2fde1f4a34ec7e0f1d78405fb82482b6b5c264ebf0c704381019a7cb336325b0ecdbbd4b75eeded28a2bd1393b634a010997807056f1b30ebc368b411b7cfd3c0eb039f8523e0cdcd14620ddc15248dc2b7f3d4750b0944adf3f3800ec442c8ed6690f4ae274e616de5ecca3a908d99f6613d1cd3e89c54e1d9aaadf388ef31c26987fad6ae855d4f3f513fe89bb858aa08581737ba96307fabf75e95705a30986c8df4ba6b4400085a8838451866af91580f960e45f516a3c2e9f4a077a0ba5d8e82282f99c9068917b17e6dbe7d78cf24d53394fc56320a8a29e67549c6f31f71dc1210c3f69017fcb06bb835d95a7fefded8f2268fc18bf13047bb01990b5ec560ab4a1a28423288dc43495542ba9d5676622e4ef7f836b0d5d15b97916838ca94e4c000387c870edbe8ba9459a960e49589178fdb806d1b15ff9087a5aed37621d4fab69bc5d8a32d020ab131c7464974c04d606a7950d1329bcd600fec317a13c6a9f04ccd3de5f3178f5ef24e1c68cd1d659a8b0b53d57fb71c7c793d7ec5eb1d799b6d2533723efb3d7a8aa91f310960c257d070a8465b65e6c267491a94dc55967ae6074f5a361f609ec91c077ab0571b6c14c434ce6623dfb00fa8879bf2143ad52aad5afd3be189ccd9dd507736a8b5e7565bf620477dc96979f9cdffbd6c265a5ab3f33db5676ee2a1eef60de5464f8ae9d61fc258d62e22f98e9d07c485f6834d2a8e1ede6a52710252aac887fb0530450e28057470e50a274b8dea05fe8cb0ebfbfe915231c9360111149d056ed1e52d1fcd27872c04be447bb3d846863657dea9cbd72aa285ec73459b254bda9590a58903a6496c10bda4fff88ab4828fcdc368872bfe7779b9debe48faad77428a5b2df9d042b4322e162664c9269d232cf738fd2145e0b605fe7bd07f5e9f0d6ba69f001059e550b79096ce116e07fe9966f80f3278ee439228666cb657ee5a72795b3ce972e216e1c15f5010c773b7ebfe220303a53084dee460c94fb796701268057e4bb5326a9084f30e3b026dc8e8445f20e9421a16737d6a524e4090fb91cfd14e8bc82a18eb84572302c153a6fe0027d6662dc296c3814c54cf98ad7c80687248821035bfdaef3df6ee6865cd72bd8ed28a8ace9b98d9874c66c9dd2d57195abf9121697ff91bf41edca4066ad58d02ae8acbe475a37dc850e7dc09173fe465f2fd6c0f5a04181191d7c483477447686ced22d0d05ea0ab69fecabf863cf4243b607b8b0bb38b96dbac9fc2d0b8e998d1851455e5f295fc5922f8e9ea80733391c8f75689aafee0bfb18df9c14671063e7ad2cc57093ba1b87ef04ff8554e355701e78bbe7352e553d240de10093f990d2b386f1aabe205dd64916c75d1155934a768d5a419df275e274ee208264bc51e64a1b2ebdecb4eeb444db5de490bd7bb52bb6ee3f64cb143fd64849664059bd1006b8bee221941de8f333990c1df5aed0f738c66424feb76a8d98153375c4e28c9d7620d67d7e650d39bd1ceeb2b98cc68edff8c6becfd4dd116db00ef8d473c5a45955505036805632f2f6c78e97c0534516db299b1a7de81e5fb6a12435a897ae20e0417004416d9811a173aaf145c766e1a2985fc06e26894b586a26060927db1f58f43c613cb4c4df91658a2d75c6cc36c5a329bfca2eb3b125841ceabd83ccde598911658dae9956a51e3c70ed8217f73e501e04e25282a5c59f78df0881d72bf153f9cfbb7ba70a12cc01105097e488573923b18733fe6cdf719c4af9cdfc3b86df2caf56ea17c632f2def79aa82210141db3ed2923308f2d8242e3f482a63637bdc6ce1e5c0153f951e27bb4bc077b0e3a8455a043a88a1c2d55c26f98dda7241dab9237923198cff1c6d079951b2033c4eae8bfdf3ef4ee7d20605790008540f3c4366bf098a88d33af0fe2c4872d39e4cc2b9beaef8efaa05f131a79059ec05e8919999a96cf5596e54297e9b1309971e0ccf79c32585a25cf1d5bdb54052bdebd2603247cdb65ee2fbb5cb354b87bbff3fd8ae2e7e8b559aa887d31ecb28d7f7729f4f9005e5d30aa2ffa3178d283c1426048b1905ff39018529d645106de0fc17b4a4b625170c50daea23deee0de8b7f6050f086139158c747cd7d648a5da9ab05b0c7ef26ba95942b33fdd74e7ca808e1921df58218410d295c497901d830d0e2799e04a38dd21be5ca212db177c5d34229cf6e2da59ab07213c2df89ca9095abd0f52dfb950d52cd19f03e2c6f8cc0b53c68e069528169382325df36016c3e2535bc0869d360ab37c40f80e195c1013b4f912e657af5b8dbec3025f0c80a2079073b786cad4d5124ce8fb63c9d8be59d9d2e1f1d3cfbd0fc7b704803b486701114be09bf19ba48a16d47af3c25498c8d7ca702653bfb31aacfc196fed116c598c9b195c6f37b6d644f07d0beeb0e36f7fde0926a9627f5ccb881ccab8d84e0d183c2833d6200764b3dd624cc55325e018ba61b56658afbd7c814072ac08ba88b4bd13d394b0dff569b4b68be80bb166a38d6944905d579f24734938a704fb27bc3955942402f082adbcfeb59e1401cf2b5ab5d31933eb1b20f97aa360dcf8e43841608d22dbf8165e5710523740c973f55d43cb759abbb279fdab78de709a5c71b2d87c6f13e7182e56ed6c8b748461f4e275c956f1f73ac4d7995b1754baddf538f0b23214815a0b1514af2c1f0c6bf015b65dca0a220f8f0356dcc8249487be7541ab3bb1667b44e01bec553ceed133fa29a8202a20d2f0dac8679ba00a00dfcf836216b75b4c6dd1f6abc718ef382947f939022aa70c436a451098f3268b31147e24e294b207d813845282eebd31968ce3c8be43a15461c9c7773e3421913abba79e96d14b992e0452f0d1f1595dc15b2785a2856028a7d2f43f84216a48e5167656a3635ab4bc46b1321a1218358a79c436b4f6cf97b5b6aa4c8d015dc76a650fa774f20a56f5b0abaf590182cf03642b0c2e512c7077f2a046b28537090b688688a00a5cccba355d18b969d6becf20c787d09cb908f5a492f3e46d61249899745b740a0a92131b654950cac71f5820b698400fe85b59c8c3150d4f3f9e875e758ea08b55c287a177a0e08f3f4a4fbdfb0a5a8b3480d0eefb16eb582b0af2a14a83a73887abc54b2273529d388bc8e1de1160fc42ad74edab7b0ab40a1fa8e6f67538d15eac7a385393102fba0aebeb689d311e43809aa7c1dad8739811b675d156173819ffe24d873efd4352fe7d48bdc9486a8df48c09c792643c6d530cfd76a0585d1db5921a6873c507d689e4d3a649f29fa99f1e8c7b7abbf17d05d1a0bdaccd4e49c544fef73715f008bcbb8f3fd20180348999466353d745342167828c7c042898aba0436d08b2c61e4a1d3f5894005b6af420ff13368b05a478a5c29821f1699a6f6dfe12972ed50ba9b15460c4db42546246eeb00d29930fd7dbc7fc5a40d9c27ce6dbfa477f1b62c4656be9b9b75321a65e3a27bfd81dc0a59be4f4fa3a21cb9cd246c9b7a41523781d15f206035ef87e71b4d8e15f501552f7cfe775c6e89494606d25c5967a3c1150805a6c29d272fb97a89db2ea47a3265b80566176b919381c16739cddceab3d177eeb90d8bbcb0461aee024c000a40ae444f173d9bf046921924e44dfe80216e75c00522f2109a686942afb3c86335d21fbc11e97a801772963255239ac132fdec5c8e7bc76287e007ff056bb9402ce1cd3cacb0acd866c3a26c6f3357e14be0aa38342dedf09ff25bfb0406daa077651960a00077b93479f176e49952aa2e579d2dbf807167a793e2263fbe9c3f14b03008baf20f48aa32e0072f4953b17c357cc5e34f4c0cd2bc19e00bfe318d8fe2255ff3336f48710da006114b102f48489e60c74ea502604043eb4f8c0239bd01fba9da2352479308d88df3afa48b3588dc61322368b83c0ba9f574f64c5cb18144cc4cc75c90f41ac1cb7ef532ef44044763f84c04f20a07e246ff2033655b52d33184a043558fd8f0a6d55b08f1b294f0ab868c81a3ea71758f129c25739a9b03270b412bd312b0262d1209ec3bc3ac8c9feccd4a247216ee2e6157a41f5057dac2115806b869a19a4094201a74baeb8b51904e034fffec37db30eeefcefc762e350af764e50afff0ec8ccf531c9219c26174020f751890679b25f86a53b47c2b5094da7c932914af50bb08e2f65253c5f4e35b47b574b16d5aa586413a7a7cf721fa5f08c16fca4210559ebb694a19d20842fbbe08ee3e49ee1b8792850cf824099fe531d382780fecd6dc54e441a6fadb23c3eb4b132fa3d045ab306d0e25255dc5803a1a3616ad835a155983dd55c062babdc9f35d3a6975b398df7e252b4291665dc3a77e112c8c64de32ddf5eaefe8ec77f4a688da6124de7738f32e62c73cad1e0275a7747d4b8fad380bb6112ccc1c98284055dabe58d8ba246b047215559f40020057e0f3562b3b537b095288470437faddf034e7c7090bbf2ef0e00778a19a56fc869a485c93ac03e60da46117270faf7d9092d10486fc8b6c316365121d887118d7e2c229a14076eda3a2ff1784ef5af1fde5f289a229a87fa4b18126db9e6c303bde7ad1b5c6a5c558755c310861993f02c631ba3858b82074a1e6209afda7908b4dd8a3f82f5d8dfc0022986aaae46d21a7d20b60e4f4976063c5dc0d0387aa79dd74879c4d8913de26ca2b31c9bee3e257be18cd1962ede3a2e4021a7d7596ab852d6865ad9c75a05ca28ab7e591fa6ec180cd1ceefc03e3619a49af0efdf3bb841953bba28aa774ff4b45de330dc5023f25219aec8462be6c15f46278795a7a612e3bf555addd197fe4dd81a014a7f2ffbc307de24f0748bb9368f09c1146c903f7cd3bbcefd321d0d3aff996ac0e74b904d31f0e57646c6cc2d084f320bf0b8cb0223797c82d984921188aa19822a38ebf41bcb0acad54d6c8a42a75895418b607c821bfff7e0ad978d7db16ebb6c6728a80d25bc893f14743d25dbdeacd50fe7e89032cd095c9af2d3d516c7ff7037d05959f289a5fafa803f7ac8f2ed7e7236f351749150a8e1761c63cbd4e822c1723fbd17a421810bd90b8b852d19e242ecc726a0d8100d649b376ef881bae0d008e0ba0def21296ed6fe5bc340514fb2116935c0350dbc450413dc3983d595c9c366b132f55951c7ad934aa7d8f457785e29abf8f9ae245247cdf5ddca10dd59098eecdee9a0deb6267577cf687c136c492fc9a880a333e62ce93f4dff249bc69a634e6bf8eb9fa7bdf4c69fbccf4c106a866b4fabdbd26fc5b3b030c3eda01bf432dc228f853ee57b86233e651b7ed50c3a46af16e5bfc0118533cbb1d73762071f5e332d31e23a51f6ee7ea27340e1168ab2018584f74168fca27c9b6b75cb96424fed60c53b7d0b6b6e5c9d3ede313004a05aad1fb06dff0c016e759cd7e8e97653290fa18cd9dfcc88d2fedbf80bdcbd1529521f1f622ccf1fbc51712071d42daab8255d2cef9cc1a4cd97ba6c0c93b1ca108bd2f2ca6deda97c9d6fe5f7228e12fedff33ea0bf2a30f4170bca7f5d8d93ff3c63fd338f519499c7ac5016ba9fb7610f07eb8d4339c201ca91f3de4d97787df367c7d8848f03effb989cb86dddd2e3f7f0e4f8e8e8e874681ce02c0d57b8df8e8103c21667f1f4f0f070f47ce27ce8747676344e1cf856b06feb076d37128b0776f3c251013bb8fb09d13565630803d5492ddbca8a010467a74cff46b88cd7d30f4fd992f3292088681506217de5d3f2ce98b3b62312667e2d7e51423effb73461ea329c2a9093a96eb7a42aa5893a60d00c155bcb78830d88006da07d97d18a7372708c179747d0214b40ab14dd74b352aff8deaabfe0893a21f8a33d649fd01e18508241621a94c4f434de37b7d4209dbc951b766443409cb3eaa1715c4c3ab827ef522bf4a42a104a70150ae8da96016229de5985997e9f6372bc93f8b83193643ce80a2d6e6b7260c8ef542a33146fff3117dcd46a16b09dea14132c9b3fcedb8fa45c7f18b65aa73df27d1f081dffc59836909fe55da6ebff9ac830bae9d3702dee48ba551a34c2165008704219b8b423c9bc8b18ae5388f185e49e87f25590ee0a94d489fa4a586561b838d54e1d8cbd80b2c1f8c0555a9aa4003278ea5b9a4c9f48e142f7a2e4f5b5ba172e377a0234206f5714df481eff8b0312f3fa3b2f911aec1752c640f02fcb3dbbce9442ab47df9cdf9a63eb23a3a5ab934e768c6216f54a4bb1e22ee7ef5f64d2bce5a6337077d8b8e9e31c1d0b9f267bfcb4420af61f77158c7c5ccfb6cb897067270ed54ce5eef40877149ad49a92a8118de6843f752f0782f767970de630e11b0d731a6bd793cb4cbc98a535c1fd520128b59c67081efbdaeb8a41313027c92ce1c6d55829ee896bb7b500676832c4d06b8390c87911ddd08e13b089682c4f791a1a66f1f45cb78ad997720dbc27d22cf9e3ba79ebded890abb199bdd286ef23ec2af0d4a76ad8d5480a5fe72dcb8d8ca56b15cf42ca0ef373155dd488276cec980d74587d372ede4e53883a544b8649d1849bd348e5a265583cc18f36095791af22891889460fc8ecf7a74309adead520f680589f3e4efd27a778e1931a7ccdea6d05a37497695a84206d115499dd629d16e44590f4d608321b6f313040ba07e7d363fdd04a608fdb0b8d26904fcf5b1222d5e0dd5e199173b1f6caf692b88c6ad29cb5cca81888e22296951f6f41e25e983f89d0fc3a98c4229573b47bd0674de31fae8cd9a912c969bf2d50f40f736f7c4e8b42203402c03d5ac11311fa4c4460f70f02785446b8e275018ad082b74828f3f71e8bc4a2d4204c5a0f717763fb00cf50888b9c191e0365985500893bc28d0dd93fbc805236cb517db2838876f4565cac7e2b761fa6314d10aa048b15dcb389982126fd511b7c11609961ff287239a844f1ab4e695056112636f68f2289771f308b340648ec205c940bfb3dd3afb5b6250a22c56d05a0e360611adcb8fbed316463822c95141360e85597cd9d717f20f5aaaf0ec100179e10752fa2471e1b0d18f2a4abdb7bfb4af72b2b8c6e40d718de1f0d3307a1bba5f6996d12c7baee2288a55fbe69cb21867e1a187cfd0fb58a471f554a48e997c5815402745191bfd1894855def027cd18c44decad90cbad09e43f2d173067592a10ce12255b205e580541ac580a2050feb05c40ce625778e2c7a6edfe92a56adc10675750d1e389a3d86443f2f37d96158a6505e42591a78669d2bc4cd78ebb9e177fb4a0d65c6c4b903f5af88c760887893b94baa949bbb19d0646c0fc349e03fa20c9673f5c7fd56b566c72e9e848cdc9167c1cfc8b83644f31da12042f488374afbb157281e39504ba8de2b8d1a583bdaf6355a806bf79861ef7cd853601fee296bb258b23b45c0170f75d390f8e9a31b7f566b4f1e68be6bd7ae55229f6d808196ed22320fc1e6c9d91c78a4d43887823e4144da617110ab8863e69618da374512a3d71eaae931a66eba757da03b6d5eb4e8a3b775838f0d30a252d9cc4828d02ffa096667e21d0984f4b44bb8daf4bb3497c2281e25e6a107bc081f7f890385af90ff059a30910693f77c09f94bd1972553ad3280e13984ffbb483e513fbaa8d203f806e42f05ba268ace9b2f7f46657bfb1abe6d6fa823d9ee116520c5c3813c68c464d9e923f1a0ef3e094dc38188e537e5c869a2c2033ec8bda9eb2d908fea3263350b6a0b448a29d02ecb15a8262b584082e66c71ce51681b9262b003539eb6e111eb377039ac8c16b82f83ff11ebe0b2103d4bc033acbe2925a2090329b0ea5089256debb18600017deff55c1888e5196e41dcac7d878d58bd68b2e348cc3a9341360a1cb2dfecbc243285049079773b2aa2487165250a1afec1dff8059221c33f5dc8703f5db979915b1ed6e7b4b29654a5206e008380926094b4fb4408d21a4196b9cc1c40b3658d04316361c914349d485a289d5cabbb1810aecd97082171b6ee058d8bbe1e18887252f57504a29754a29a592868ebd825d482b6432a3245c7c5029916195040b4ee2858c5225b97d5d949577a3a4058f2533e27ec0430c2f6ac4e890440ada04050d5d2c8993df0f461116cbfdcf3939438715c49882861b5451e44314143664b045962b3a50b981cb978406e344450627a921471228b8f3e9010ad29165cd8916170b5b2c065606dd0d77b9a7ee46124128a1f1029311153a300551012b9ece60c2a40627e8524555c3fd44772dd2e203ee723bacdc3527463cc93259d69cd4c082ee1304f16468808a8d29431a4390f9de94734e0e63168b2ec993e65da0c6994d14092152820a3b24e529bbe439ffda2a6bad95aabaf973ce39e79c33d69cb3f0b6ed02343a0a8e73c3167d23b36bc3167d39d1954911e2befb2129f785baf7c2a18e35c4855323d7b16cd8aaaffa51bc12b8dfbd9015ba5cd8a22190109914e8ed34608009fe5c78bb3a81f37caf95254b46a50696f38912fa8bcc9e5d4408f7add690fa5c8882bf841c29725f8a507dee85ea73d26bfd1b0e71a16847aee2fe98b08744bc2378fa18f21920656882bf0d67189cfdd60780b8bff786a34f50e67b825b6686d1249798a8b4d84f03be3f4e2ab97f04975ec47160be8b8c5eded697739c446188bff04531f708d306e550a3983f5bee32692ac7913e8284921fccef4099ed6a84279f0341662b3b28f499330e8bd3681215cdcc256107108df1451ea516364c417e05a114619186410ec4dddddd33151bd556b574ad52de1aba4be9524ae95d0f35ef08cbf760545660f99ef4f979864b59ba3765f9632d5a891f764a6b9dd5bdbbbb5d2b105c7486a32bff7c8bd2dd35d3a2abc57be7b546542778da6aebca08bb5ac2f34f209f8894f547978cc9ac0547569ef6fe075900f7fbc93fba4608d2d2b73294524a29ad952e84322c1cf02893655c322c9a2be89252887d1eb9cb3dcdc91da43c810b03bfa4f204994fc6be4b9998b103b9c6c4642c29476b2bb7808c35d33147a6e10932cec59333de0d9e0c404d428b52551c7198f592d603e31c73d8c6d98a60eee3b80f737822994547fc294729a5b488e3709c35c2fd46964bd995e6cb5f4ef0257b49780e2171a4ac37999380ec3f95640ecd3ea1984bcc23b283347bfddd7b9bca94edb64f705269d1fd69c045a61de56d75dfaf4ad5fd546a9c39c4d3524dd69a1ccda6713265afb41d94c8e374da41298f334b7618b7cd5bf65775d39babf932bfa76b3722454868d17d52366c1337ef4d26a60c2c7fca55bb5c3f58866bcc20a63ba5328fc3fbeed6181d9fe3f18e5007285bcc11ca80b2451c614c88c371d26042d9e24b18865c287e17f7396992a73cbb5944601a347d5aec69713ecdbca5f0973c679409a5451af0c8cd1987318bf55d9e83c12cecf45d4656cd40fe6a1aca7cd405380753193a32366b4f54904516590c993a4f787832430c93f797c97c502293d950a760b27b5f76c19efba3ec7ebdf6de7bc3b147b62393edf4dcdab5ca48a8f5fb0044f6a6444c6badb5d65a6bada5feededeedddedd3259d743cd0727b2b7cd8a57450ea699a58ca1d168b4a6997c0abe9d754e7777ffffda1330fee9912736b04e18e3f125d3b712c7ca179d3e2626a28c27110693c1645206bb81c16e6430d98c155c1957c993bf607e37ac5fed3077ffe9dedee1685fd6b01b99c36433304a61b0ae3d8982c1d2689bbdfa4fd9fc51467b66d397f5c87ac29b169d46e62f8382b2994a9d7efaeed46b3e0c21b3893167682ccd0d8dbd696be50d0d8cc6ded0dc882f69ba768546be9ae6e546439f34c160b5c8713ac7b91da3df212c5729c56fb17e37d3a264ad90b6781a7eadb625d812ec56451adbb2b62e355d8192aa3d45d96e5b727d3a9d3aa5b4fe4c7705c2051d6b913271420bd551aefd795722c7e99ea34d32a7bfd221e81079c44a79ac4660eeb073c251b9af1ae55a8d1a074fd5a3a55a8b922b945c95d6a092c7ca946bfdb13ae5b15ec9b5eb73d7ca27d488ca76ce1e51c85875b4832711e0b5695d32c7573f7552598bd6c3bed9b9a9d2b5edfbcbc29e1681de07e8e5778f048164937c79a59417a8c7a7c5f6f7e9019299f6008d361469770927d4dc5df4016a7f8e73eff6eef67eba3580f39c60b528d322d79ab37f504aa9ad284b1ebf16944129a5f467bbebe9cfb4e04aa62e7716fd19b25aa473be8c0c225b9cdf9986d239a369913e16dc851fa5c9942aedec744cbce9d9f1d991d2a7070604dbd9d9d1626767ba4ba6c5c744e0fe5186c3371de4fdfe39b99f99e35eda0abba1ac47fe2893bf1393b21e59d7b9cbf797f54829eb9137e2d3cc1f699ca66787c68746ca645226b381cb7626ab09fcd157894131d0f2551f0b70bf10912c231011229285088bc56211111a21cb084244848808f1d9011a01ed08922115b2c870c93b79d2b814e1c245852c2ccacac2a259bec8da408b1d590f8e2c6b4b466d5419f509a24142578408d92021fb3e8530c6582848c81521414233480868e7a667073c7e57829a84b608691a61cb9613aee0c6b236c58cdc4a70b0bbe353af4f50ad55a020ce27a81664837c806a403e4134bb4f500d7b38190932197d4a29a595da5ab3ac4d1943d62c17594f0f900f500f101050934f0f50a53d403bb6224d2474ac8413bc49daa8a43d4d4db52939c834944d9788a93ac256e07e4ff6146f76b89fdce47e9a30bfc7e7de9dbb93fdfa748c86eeeed3b333bfc787ce6eeef84c2e94dd9066a6caa481fb4758dee958145054943fb7b3b393eb9586edf874bf4f8341fd4041d3278806758cba7b904f8340b2d61f44bb7d82bae7c6bb84a99a0a2318ea199e40428b1e4081472a6b520a48884b91e0236bb2d68213d01f28cdcdb47f637bbeb5efb6ad6dbb7323bb997f236bebd5bb61373d1d9545a86f6e565cdc502b6eeb4dbffc527a9ffe7873df04fab29d9b09eecc9d8e71efceed8037f46615c4834c5f76b3e393bbe91d7a43223051a8c59b9b3064635418d93f0353a7df1f005cd0d16be3cddf38f51b96ac4d7122d370c43772aaa6619fde4fc187b7648df390a2d7fb1ead4f2b0f59e3efab054cca92356eebd75cfd5953060cdc55346d4a9bd2a63af22b05c3e9c369f7171efd68a8b20277efd1af7eab6945341aad05d70bd961376b74d96b50a4b2e3f4ccdf53555ac7c6e97d257b4b91fde78dbb8979058397474155e425145ba4451c87a65158855ea80adf3e0c78c42afda082dc81a3e8df170cb9f0a38eb1972985ca3c1207463ef10298d07f42a9619449253b4de388266620f560850653b3afe86bcee6c91160fa2e6b556859564b01955c93658d072cf928298fb0dad1d1dcc9352c6b29b065580aa072ff8d6ca73d9810fc019486ad07c819ed00533389040c49d4a085162b667d80a9999cd1afe1fbabc5dcfe82c2925c8a908a010a22a32448b3970c4d5e2841b2822b3998f5c3fa65fd474e59d68e96b20759d67850ca3004c81c8be4c29c49f1f7a8d0945198ab07fc593ec14485d2c0e2a4ac22055bfd3f9b60ab652d7a908faa0bee9b54a866e061a774d2a9c5f3a49c53ce091a991305afdd9d52a74e4731cbb9c59333a3bc626a093c6777af7ef07bee3f2fefd1283f20d04c6b5dbd67ed0ff7debd2c2c6ba6ea3d8e9b764a2359d37545aaeffb91df8fd784bd979717181898d5cfea076318fcb2c2313118638c5f66f63e8f7bf960561ef094b0b59155903ff3cb2a082b4ba72c5d29cbf724c7e99bef68bd7c37729c1e2fdf8f1c87871abe24cbf72432d6f2088917e1d07a4be338cd5fad7af90d15157b79f97d3b22a5d291261f91d23d0c3fd381539ffcd58e5fb2febb9ff7719eb4b693397478ab0f06945e7da12c5696543489d0e4928aa6dae1f8f09579e529d1fdcb0a040af33978c32b42926fb52a2eea251c5b4a6a1586aaf047930f4dd61cc963a5529364cdfc4e29cfaf51561044072e7c63e5604c3e00940dd4cbe900502fa7dcefe1f87270154472d9788cfc75e4afe6a1f110497dc114953b7081b22db29b0a0b3cea4ce94c71d1e9d231bbba3f6a512e25bb87f7a369a6e982af26591315f585634b4979e1d85bb6a8420f94aa9d69ca3d7aa0f43a563f3b92c00ec2e41e60ece6afb625b5188b12538a51892d71b12f624c2aef4737f21fbd686563b3b1f9eb36d562db70b15943672a05dd11a298528c8a8dcdc616360f6de4a1f9111eda6863cb4f3d8e2a3efa58ab9fef59ffe33db7e37bda2a15c1a98eb083ab0e44eac0d501541e13609400a222d94d36d585d545d6d47e38fdb8f2e3698d34b270c9ad33e5ae33b563945ba78beb4cf96b87b643b48364670917fe68ba5cd562f3540e7e2a3006153b8a418925c5a2c4a272c7b4c892dbc66663b3b9b5d8365b6cb85c1e5a0fd0872611d01ce8e5289b54114fd28e22a3265038b1083c4a289a685034cdccac7e54efa0ec3a0f069c597584471e5a770f3077a1f92b4f2d3135d59c5ad4e17906574637ca8279683c477890d8d83a76a53a76ebd8d38c2673b8efcf593a2a4aea25b4b1d9d8624a1e1126926d91dd2af776be47c4d5017c0358fd549da90e446a395dc902d562d41749f1d078888a5aec254d9076589a479335fd3a5c1a6747c7ec0e4bcb0e7a0722b59e05f2d076b4902cf1207f210f11016080c76a6b817887cd716a92bfe6377559caf3c73a95ab52b57505f81c6cd9671d9c52823377d3e76ae61770660cce0ce3a0cd15446a7d0722ed780c4e59335b2c50f607e36756cde7a0b491ea57bf8d4d4b55cadddbda5fbd037c0eb6b2e7e373b047ae2012eb3b56a803c4b0cf411eb982483b9e8608f81cdc91bd7f0f3cf060f56323d562775d17dadc6c00b0fad199c282471e1a4f51c7ec775667cad6eee67350267b1f7c0ee2c81544eae03b10c9f53dc059d1ccc85fddc1eb00897ee69a1d31913423ca4f394b86ca51d976673164a91f7f0d169725b6d40e401e1a8f114f131e281eac1cd0b31dcf439339ddf7e72e1db38ee3fafebc46e3c86eceba49268e60c90412d6926fd23728093062228ace148f91e388e0e4af9808502db68c89c085149b8705da9c215ffd2ed0460cf9ea17810b19b301438a0dd5311b5b8b67c89c1cdf6f6386ccc1f1fd3665c81c99efb7d922736e7cbf8d0c644eccf7db90217360bedf660c9983bfdfe666238693e38860b30943e6bc7cbf4d0c648e8d940d18365a6cbe9039aaefb7b1c91cce692a80e8b9ec10c166ac1f6d6e90fb6da6fa07ebfbbd0eacdc07a3acf590fb45b0394e526e25ca24f7631d723fcc0eb93fa649eebfe124f7cbf090fb71a420f7e738cafd7e83ace95f813e38f8e400d68c3ab03307fa2451813e4a401f1b644dff07fa2c91351d38730e50ca1a2422d8acc7e300918864b31e7f23a6c7d310e010b0720d917a8452da8cb2f643ee870136931201a3ac21e57e9f1ee407fd5eb52ff7b30bf43102f30d7d8a6437c78951e9573fd3ad76bb42cb42148b8a49e5fe310146363600c49498e09a8dad2e497f29d58a1c7cc91544723d758522d8fc558023b86f797c590b4c000dfc3105fee872731cd9edf6e306fe50fa41e5c712f883490df9634beeff3186749ae3fca8fd90fa285616fce34aee6e16a833a5d3e5c97174d638a263b50056bee18f261089e8673b7e003ed8d854dff7f320e17141e65c11456e41ea88e0347968224835ce8eef17c1b603d4e1d23f5ca8338505d699cafd3c349e233c441debbe5f6749e7a6c3d438390b0f4d76e3a1c96e3c34d98d8726bbf1d07868b7dc9f63b5801c7995c3a887116b05a455f53e8773182555b01a5d2879fea76ae2c46407a71cab1fee73ac40a0d995237b61168bc023e6a1e5289235fd9e8c056feeeaf7e08a72579a8dad16c99af99533ca75499e2f826dea24c9f3fb6dc8535e6173b04bc0e720e7e558f0dbf9e4eaf30c30138010ac74878e1def09934306c78d9897f93a403993574cc974095a7852cab7aaae7bd2e943ce316804bcd68e4d1c550e02827c81dc202cd84c7b7612327b3052f6a4b9e4f7e012a3d4698cf32aa814bffc752abab2ab504af1fb86545d48c9c05294a1d47b7ed3cbc0369cd5bb2fa53f0da51455217d0f544d1ffd3694dc163c43f028192c6941a41de5fc76a5942ecdaebbae8ecdb71dbd54aac0cf05e08dd5023adb104f1ffd3dc6c0b35dc0eade5eae01feb4abd5ebbaf7e7ee77e0f8dd776f95e5cf550837c80ad50209b242a6340c4f4a1d973fa60ff93d70c0f3e54e4eebf4944d1f370dabdff4bb5290c65d6c513e0dc39bedb4dacb75df8fe9ad6037b2a9a37af9393b53c79f0503e2ee5cb91e62e039237d0c4002d943e9a3fe5d01e9973eeadb15900e29d0d5159096a28b044ebeea7ba0b34a008267774d6b60c30f324a9e3e2229255cd00213962d567ed8804ad3549515b4918612307028b9414c14320c24162005e20a79662a6b7a3af72a9082dc03f9c89cfefb3f12a788bfeedfa6c8f7e58846bedfe03c226bee17b97f9ba07143bef7deff90f0bd61ff91a372bb49c01c87318bf53f8af7efcb2eadc57befabeebd5ed414b8e7644d81670addabc0e5fa3d9376711cec2f1725b53d2d3ea68229c8813b2db6867cbffa55c88214eebf10af4296bf5cccb415849ad00a926dc80199e70a48976dd80a624d6805b9f46f38634351059c6d8f8facf1e7c02021fd81ff057fe413f536059e593e0d9fcafc390e636fc29f7b5af49d9b2619e67fcd53c6eadf6bdd8e2ce40b845b7449c1415ed4f2e78445ccea21aa1f01fb6b7e6765c6859cbf561290516829729a714e933cd577efa940efdcf577fedd4f2445987bac1a721fe6afee67917984c8a72d20a1e026b9e3baee811aa7ebba19e42e0d0c85d3752f657d415ad1cc25753f89565fd71a72df7b2f64c1fbee2d2073ec773f8dbebbc0c4e1e4ab7b4c86a441ee5e4e1ce4ee2d089335dd5f905b8172f5803a5b85a2bf3a30f0d7a79fbd2267bad0843a55050499ef4b0ee62f0bd4e2bcadf9aef74c02c7f57b9112941a05535152ce2c37da3ed76e34dd6bb04759d65400254ffb4548a8214129f5377eed6e75c03dde7bcf5720dcf8af45ee73c24dce712fef5ccc120f1815fe9a78803d1e078eb9c48463464da82cf3890ad704e3b9c80dc78b3f1dbd78f8e1b7dce3781bf35173c9718a4cd09fc78c9a503138c0f9047a2ee2a424559e4bb03cbe70261d4928a1162d0fd89b2b106e3c91162deb8610a1c699f9c62f77a0b800cf1e7e8b560261abdf48ee709cb9c7355dae1a8a5c530552dddd1c08faac7e5a415ccfbd2b046ad1ba5ce1545af55fb0052434922d949723442fe1a47153c995d46211d6cf09de0960cb0333bbde487685e39ceefee272852dcfaed003b38ddc31dde2e1fa29c5834767cbe468a966edcf2cf62754c76e64fb33cafebc354ecfecf370d99f50665253b63fa3d86ed7632bd8f59e0b9c59c682941e0f86b221e0871f864346666008f397f52800be41cf5e95b32b7de1f19e79000094b9489116ed8feb79b86e3cfe171eb10ede3e8fb71fd4b177bd557900a503703e6569d13e0f7042d1eccbe3971df3fec6cbdc72ad56ae702685f30800a077c3b5c7f378155ab4cffd8e67bdab036bbf83702abdeb31112efb2d7012b5d80394e11162263c4a5b088221e72fac2446135d666028fdc502592ddadf01ba5a3ca1c5221deb97d7118e5cfe728423cedf8d970947569eb4699568ddb372f737c291158343989770e4f287846752388f6e98fb790486435813fcb9120ac087e110dda1ca2c0021e7ad0578165bb4efd9fa9d48bca99402cde38fd4c3a11f27889a9a85a15c35800b7b7c5ab42f37e0d1952dabdb4e250eb3fef377ccb3ed98ccd696d0a27d6b59d98601780fc0cbd510ec2fff3000e1908c428b191872e087e1d0e4d28515a219d8b1d934011471749b746c5e28d9ff897bd5fdee87bc1121efbba7341c1a323202a7114ae802057473ba4928a1ee5531df3d0d8586605ed525fb77608b767fd8cf428bca565dcc170eadc28eeb68d8724007f3aa6721e6bb1fc202ccab5ece46990456ffb5688b862a707e07e2bff146662f730907914c3834938c9692888c6e4cfca2227af9711ae5480a5b35147244c78e9733fce35c9a313f24a57ba7ec42307f231c9a377ff9c3c08443134a150edda6d39c509d5307a59a30fe18bc4960dfef1ff22d1b1751b8dce088469bbdfc8d972284a50879f58788b2bf9724e4fd8d70a8bdb0938cbc1f9bc8fbb18d929ce8d6c5896edd8505ee00af0ed076379513c97c43f9cbff87a408798f7f488a108ef77e6829fb13f1cc6e08bdbc3c0e5fc22199b09da0867a098639bbd14e312f67313ff6524cd8aa6f647695b23b4cd8aa466637a9cdf07ecebc7f79988781f9981782f998252118237f49a249fef2efc02437eabaa4ec2ca8c296d0eafb2e6c0d59fdd712d2854ed97f88ed82830c9c3871a2d166ee44d433d597a0fb2f6cd1d197b2ff2b0d2c6750504e4c321041051552aa90851274210d0825a244374ca32179a1dffcf5a34365ff3f821570a4294b29f24893a851f6a7469448a87befbb9b94fde56ca446ad2173a6fa3953bd77f1dbd7a2a1906b74e4ff31718db2ffa55d223b65bba87eb4b6dca221b5d9db5476ea94fd39902e81ef029633d56ce6912ee527da3b9a5845d1d44052d4c6155aa416505ea853a68e80a2a5861d98cc28a594d6262e274d2a426829e2861b6a30c56c64c9868a95c1694007275d6268620d25b6c899b125a22e8759b0f624acd90518aa18b54dcc982d576831831867dc3084bfe0491405c4683af24593194a58d080478c7ba0b07b1217c4061304e11484910aa4989d4515decb0f5996b47c6134c415ca7937133306366e70e249176870a183ec1734164f987278c08723564491c2052631c8995d8349239e7820820a23aafcd0b4e2d2031a388882140519947a962b9ea8407606961948614697267228810e554e70f3828c1f6c68dc2309ee6ac68c7ac203a594a2e1aa82f6cd93196262545ea085259858c152832c8aacc8018c0c3dd07040861033407aa21a65fade0d256c85c076e61b8e17c8e30a4fb429539713afa2c527ba8872a246132c5255dc80638244050c3960c122860d36c0422b7086143348b1c4195856209fb2a80590724617215e8032430c334a29a535a4b9f16c13cc2d9c88a12f7af71530640142882d32e032a5b1483086e8c209195ba2a88822c48cbea8048f323255065349ea1086961a4cc0061a33d8d25400712f184b7cb8a2c68464ca091e59b93db3648d91cc71d517f44676d1434729a55445abcaaaaeaae3e40d47ce8623ae3399c699341491393353265a5841152ac058524416d47b0a1d6e3612030c9ad8486a811456b4604629a5b40b27bdfb69d222429226d57c50c2d11769f712d6d2fa92da90c326c810cfc020e381163cca78a592d6ce72e62e3adad5ea5eb91a7ad005a597d65ec3cba9b53ecdf62f956fa5b554d65c5b2badadc609793ec9cc607a28ebac734a39d7e456c173665993c713e45396491465153a6349a50c1e69f6e73eea5efd29d741f660fa18c2d391e54f00645b6bf794c958e51eaabad166f9501ed731ea738a9efdd246ec9c31d802e32c6b3da031440f3148ea01ea4972b887a43cb2680f383e98bb0326e3d3c3512bc121d32c6b47502e8577a9e0f9922543d17bd2ee6e0864ba64c50a049bbb4cc3510459aaacc023cd6ea594e142d2bf321c0783c964ac2a9870dc0b18b358ffa24c96853890bf3c2ab5885a6badb5d67e9f8c9c9919a513104602d335eebdf7de7b67ecf784826f109915f2056ba625c817ecec150c95eda94555967ff1a62748266bf24942a99a9e9c3a36aabcc8355593ea4995434e61ae8ac0a36a29fb2f75ac86324cf7baca0644944fcea1ec8121161633957d52a42f5f142603922f6444e1cbd6dad003f74a2ad65a6badb5de77b9ce0b816296c0e34c06b114ebcb9c90e9a3803b9053c1b9ef3be3de7befbd37e85624f84ae5fbdebd773a1da275057aef9d5d027b912c9152a41c67836d6ba79d6dadb5ed817b635082ed4822456eb6041496b8fbeca8c982ec576494b5d65eee41ce5feecf42c2a3acf9dc00ddbf0f93ff4ad4092b1c3e40f6a7993efce50706f58ba2816d10cf8221b0610af7460e13748c99be7db74fed0d5983630399be8db9d1852307327d97914760c03d727d96b4a94f73df7fc67b72219e89e94006552056a9ae19b6ad9d76b6b5d6722f3b152800f9a23fd6c4643b236be813198732e57298aeec7fefbdf7de0bd42f7f595580ef8f33b3d219c20da714eb0a35049aef7dbf610a77e68e539ce9580d91b877592f28679c6d6b6dbbb5d6ce3a9003a5acd49d8894fa68f7e86ccdd9eeeeeeeeeeeeb3a9bfbbf7cf15a594ca10e9babf5f0abef033650da53f44de44c2f3633c7f1f94d5e28e9412c79b92284d8928ac4d093294a882e5f49d9b98d372b7a0ae81b3ac29b194c71d777724cbf21c78b4d6a75f80d9b1ce93334d2447a68f650ecdf44790395fa62f57344907fc79c4466a68f2440d0a0545507164365f741c1e183891318314254b40cde60bd69842a4650a1b5a6618005314288c28220a32c498c1e67b2190bf62ca32e4648dd7ab103a8f2b64ee8736a594ce96ddb4db5f8cf44c6166f72a08c3cfa4483a5b3e97a76bf694e9906210cb1c9f3c78e532e76ce69e5f573f5e5741463a1ab6808cd443d6095f962d1ac9465640da41cfdd861439e2ec5fe6642f2f704e39b9448eb1c111187e56dfbe9c197105bf6a032c75d480c79ff41b29082f1986d26e90f6a57235658da44c5985ce9e427697e168fe57ab5294e77320885966c053617ef5d084509a309f8626ac1a6082875f8bb706782a999ab2d98e689a4a70932c3f4a96a1e8044b28624014035a9eff29c15d965048a191e78772024a0f68ea9f0fc4ca8d006330c1128aa9a9ac9425145347f96609c51493cc6509c55418f921e0c9bfc1c6c465b45bb1a9bca02a2c2a6d924314d60abd3908c15969b24265c90a94a31c7ce858dc8783ca637284779cf15d255668c02b1a25898503961737b272843271e170680206479635261b66e2626693a31b1dd3e7048e2ecb1a1313c5a186cca7f4e12c6b4c471c531113110f2d39bad000ebc8b2864454d3e4b6d0a1246b4b8ca0515ec6053b90c8b2864411b659d6904041ce013482479a5559d690a0e12a53b71260593b22cb5a155be672c0ab283c74e0d0a3f54970b6807b4c1a96b52a4f72fe484399203c39f3d643213cb4c05124d1f22897f4286b525443eeef9f35c89c99fbef0fad50c55312a799fd0b0e9120074ab3eb843393981242a40415646be46e69db32d585ca76fa7b8872df21b5052cb32b040bcc589ad9ef69fb309ce309d3278fe37c565186e5f36bffacb09b16e5abc8c023b56e6df5e06816c164abd7cf831ab07c99bb852c6b4520c9348f34e37723caa791ca52d6c1511ba1d4d3cb520a004d33d0a47497f8c9c7f2658ad892251879fcba02c1dd7d4e2b93fded4b4f9461b9fdd91e98b962a96fbd002ee0d148a674f543a9c704b66da891b96f09036f9ce6c382b950464546387efe9eccf190ca1aebd6561b952c597e361a2a621ec5a8b6d567be3c5bcb37e7ec615b2a9ade94d2d456adada39b9ea0113a266b4648551a952d79946549c4924cc4960ea454965fbdfb65f00ff8f38ec4b928b8ddd0a57644152a33f93e12e79ff002ca852a5b2445cde4946b8626490697272172f004c40c6b787f91299567bc60065150aab480893472709fa25b4120e10213313859624412417c914653104250a1c1d65e4cc941a7ba49652175ab8571b484098824b2143106922442425a2c6a33e416336a6dd100420553b088e281872e6ea6688173a2b4c0640b173a38f961ead0177a3e4d410286292ba033e0a323538a5c58b01e7b1c96e1f0d7a2103670b3244a2a18f744868ba45f7249938e05840c99e338ee599ccd1cc7715c0d4cfd9235ee4abf640d59fa25fbd35032c3540d6098d190044651d1d1163114416581618684b602286450b2a45f32734a9af44be691c33e5388f0c771b521bc88212222c90af2086bc25d96b52447472e2c6cf0c69abbeccd4fd2b428ff956b720b48677f9b3b7b734e98ac45297ee720954daeaa1374d97c80cf60ad2033cf5419a84b7186524a296d77984c75a3474f77da7dd1e4eeee3d612dca27eaee1e1add17f80b8ebadc599352b74fd23d07ce53d1f608d0cb09c151039eef7f1b50bf9bee7d35046f8ba56bce564b5f528bc553554adbb097738ef321da7476aef59dbe676add86b27299edf4be4bd51c95a2b5be02e24dbb045fc7b26c4b82e9f9ad25a0b467dfa64ba2ec39ce23215ee06e68fa6568ecc8272212052b72c32073dd42427071032d56da30326256060da0dae09246134ab371d25ce3d7e35e5701f23893efcb36f2bdcf61c9f73e4b698c7cff8748caf7ebeae732c1aecc8932c7131eb93cad9c21dba7f68590ca96054bd9b2b89f4ab370e5712673f4f1925c8b4246a65296c8f49dc854a4345a76ef798de827187c944e144c5083c28b5b1b5c6081640a065048a0c48821ca1022849862835a932e8e90b62646c020858818b02c79141753c6a83d7c90f985ec5fbb16c413fa7d363300c3e33e9f3e80e092c320aa42c905509214311b613f9ed490840f2ec020098728679860871fa2f8e18b2c9aa64d6f71c49729a6f23019104bd8e4e6b82a3c39737ffab997b7611159df8625f8ebbe91d988bb49739c27721c51d6e2bddef5c2499b9836a9802a2ff00b388dbcf842d6b1ee3b598bf7650da22cf9409b5590ee5065a63a037b4fbfee5feede2bceb7a1f88ed31a626466c3f7d77d39bb8f9b70771fbcd96b39bbe94bbfe75685de0d1b9919998dd236d6dc9fcd28384efdfb45eefd2941bafa7eae54298430c283e0ef3dcd357b21acc57185ac0aa5c42d64e9ab9f36a105a4fb169059f37c23b97b2399ae80cc50caa290a8c46c18753d0a15d508000001054315003020100807c482c158288c3361f70114800c76a04c6c58990ac44990e3304c21630c2180000000010364668666db004c017a926d11c4130752b15a602131317c6e6a4bdd6fedbdd5d5e16cb797238b705e4ec4bf2adcdc0e0eae7e5e7490ca1e4c3dc2fda0b0a28590e934039af11f15557dab4adc8b9b003345e28d8e3af03ee2d02dde013db3a84b0ec17709c022b6e773912f663de7ccd62ecf8a48e21c907ee84d69d7f3263bf103d40285a56643ce28ea2b488998947048e7732ea6a833c311084b4ee013ebbac7a78e23838c13952dea58611784430c8aa0834c51a9f7fddd673d9a64cff4319bbca0022503f0f72e7c75ac58e032a7897f8c3f98e51519c54cc83f9de7ff6c55fb8badb0329ea7820032f9407be11de6d0c92af4868ff8273a2321aa14c5aed3630fae9421f1d729d7675c21500fead410d52de529413584a3ec0bef92257cfa60d8dd07cc3d0f48bddd7cb5e47599d650d364f6177669d8d7639fb59de51bdb6cfcf47a5f3fa5daba38e9627ae8c75138f24f3d4cdabde6e8eb7b1f26802aa06224f90113b5faef365f48b2a1fddf47ad771bf959cf960c909f8a4c1a6afb0e267ea2893ae4df3ad5b43a0a2d3171b5e71576dbc23e791ee7ae884c7ab35bf2ee467cd9ed31a2212ad5aa6b57636bab883d53eac9d91dcf306907d3d6cd678c22549617c50af3be4b66a5c29cf15328250ad7037d3613a78a83f4de9796dd4b36b353302dbc1500c040e77ad9565601c31d0a071a449edc868e57293a8e8dd58ecf66706ac10c6394d424cada709f7582bd228b2f43e2264fa399b43c7d4f4aadd21e3f22ac07f53a923dd0e303ba9cea8b6817e7e73a14ca4a26c6a875850386e9c840969d532704dd7b0a91b57f7db8e458cb8917a983a4ba1a9090df24b5dee710432ffd46b8f5676148313e05710998a075840e4cdc84ab881ee440a11f1bf8f8dba67046c517f0c2d9a6d005e24c58ab6ba5e9ee505e00bf930079c159bd33a293e57e9ef2203be3a7a970ce0008565cb9ed2597a5b215fb090b898aad0020af86bb4252117072469bb2ea4c19867126264c4787b7192f5ef6f100b71f451d3d08a2f43dca4acbd29b2bd614d1319ebb84879c855d46050e2c1219af6f0a60ee564769d75a910dc0e19c343600091a02913df1994fc869c58af395ed4231381366d87717f9b5d94b6ea8667e3bbd64d2ee654e35be1746c384f638c1bf66b152007b68c2df56078912faac660bb42b38c45e0f2d93053b3453cf38c54b41577fad46014c2f2f3057256068158a832359c26d65d04231a166360ee69685a434657240cd7f0b8d7f0ad286c3aebc1ec117092b44e93104f087293944a306d0c0f7421a4674d503754b02dcfd4f087ebf71bb2088a23358655741758743d972f39223fff673f510d516a1835877b2b61992d6af8b6fb7ca2bd55655ab098d19cca6b154826e89791a7ed8e274fe48b72ccc05a397e22a4f812fee25252d323fc3ed277932ec1cca62c2c1a4459498495217297183712ef00474981891374f0815fa8121afa132c745352e9952bd50495d334fed7a63745e2fb0337c4742146594cd34a77cb18603aee20502a81521fbb294d2f959881695ade4b3f414f99620e2c9473ec8da7bde60177c80aacf30699188ad9538d01aeb277e58eaa68ad0444257803313284b861bc6445c5cca2fe8553c85edd080677c6cc114af3011736bec038dfdfddac6bf5d6fb9132473818a6e29aa5749a8cd9d5932614ee8df262b091c226c7413d1cac9dd047499af9a478e6595522c8b060051bbc47f83d7adee0297f9a0de60e0b1de2b9d514eef9439bba12ebb5e704674ffabedbf20b14e6362755e693e586a2ca23a7f575c44ecacf5cd223e172536de5929f1206d38824743f5ffc1c3d5c776600c436d9ac9554de13d3be8e5d76b250616fc39bc71ab038503cfe5cf2890a72f1657717b04f41a390f6b9d204174762b546bf76a86c22b180deccc53e9a06bab72b474738b97685559b0ba0f2ef220f7a62ad65899a15fb0088cf0b8700674d40fe9b90298a0f07280a5f4aa2197b2c8ea1d33cfad4b1d3149f04740a9700aecb4298f5cb9fd80909fa2d78651d76bc083f97034d553d160c483f4b764b91500be3ce82b55265aeb202964cc3d6c6db02665c8d7164a2e78359a86dabcfce052448001b226979e0f19cd0c0eba44a73dce23ad60dff14ca36595dc08be939d67e90447beda2bd67098c4cd67e79a0e3c7080016800f14d38282d43e0c52587ebdc49cf86c3418efa90828df29b5be027de6949b63e3e7861465281c913539106a84b4d1ad2ab1168620a61693b38728dd6d8e0ca658a503b07054b1505f80ef73233528db049b149b8144336f2d8ff7a4c1b4739cdaef3eb0e4fa0005373620e21c209a49666f8bd516aa59f99f1369d8afe861212420ee38976b7c02af828851c3b07f15bb958a2ea513b0363785950fec0f4d2fa11de5726875cd9667b462dca1259f9938b18a081001852a1bab7266eeb1dcafe876220cdb089597606e299e28b2207151a2a46103f26727379f8c3e04ee65e4c03fda202d1432f0433ac62bba56b43c8c204bcb59cf38d94bd5d06bea452472cce743b002415c7e307453c671d5abca31f16b5f49c7b2276718bb969d5c8ea3d9652097fc53a48e56515ddca49ca0859e6de18fa96d5809896d7801f276336ccceca9406e949a83881a0522a6553b670982b629fe092ed4a78020bba267f3d1976d8332a6a34a711c618e9150d8e2503ff4793104245c6d83a694b7fa8cdd3677eb3f875bd93af00071c25e5d199c1d063a0c959a802b07eeca10ea1b9ade305f6662729faca631c883054a90cd28d88ede198bee83f7b094213015848894f395a8d77245faab9a6bb64b2944f92e5e711647adebbcc3188324480918b0a894f5fea43ee3cdbbd2edff521edb4a76c1c5ed2134c2e9d0891eb8e130d4a26729c2de5cf78c6bf9e3ebd8dd7b9bde8d333e772c264fd67a1ea8c044a80e021b2935af19f187b1a742b2ede3a46cd291e637fb75afc134c29e601b46feee37d71805d1c1f114a185ecc516ce52c15a6092294938354562558024350e93974693353788d82b114c9a40ffc49c54bd8d9f3be76f88fd22d86d3620f2468bdc4c1acfdecf0c5d7cd55358850d763c71b5f69e6f1041b1f93719e51e63b377fb80d6903491c0cb1c15e533578c39c8308a4e563c4ce89083fcac71ebeb16f6425f7dcc13d46a6aa425aaf0166eb0cb7ceabb482003ab5f5c7fdddf2cc2245b8882250ef40b554969da5eef77b35d06a72d7dc3d7bbe30fe0d21413979bfb24d6ab82eb578823c5cd35dcd6089fbfd20c71572d08b7e18f25b7fce1799ed72719e100e6c7efeff3a84edfbaa8782cfb2e7323d723aff626c518a1b57e08d2a06a72ac6c023be3473bcbd352a39e5329403ab710e917f84f2a6902530e002fac490be22e9c306873bc1a27d836d3685c8e6aab6acb39278789aae14c8cfc55232b00637c97c1b7be935af4eea83c318e7c24ffd5d0c5aad86943d393728fbe8c950320609110298e5e824bb697d49a8ecd67f2514f0644a9c83e619a8b0be15e01f0d2225386aafcaa6107bc8bd18a4c50248d1d93fd9803ee4ff6d83d8301be4b075e06c147dfc0397494e036e578be8f87e2560063978edc0bd0d10d4c45706e5d23388d3e34509b34bca40b3fa5b46402be816b8f51e0b08c94b3a903c6c8e6003e84cc41f43013f4cf1ee8cdf1d0fbc02d4035e3c23b207a4c05e1e384a719cf86328acf0a20f62cd001d94ca07ac1347884f6d969b0a033b3a54dc6f34f75871764f0066385ce2f876fab885800cf7cb5322ea5600341c142ba9080319bc4e5f6b29f789ebc4ce8b5b430a90e13c94c8c6a1b3ed14057bf9089475e539cb2b3aa925b7b8c341140a4c5b0ed350335a51db5d1262d59f9e928a8be51edafb581bc3e19a9ccb3f9ef0ed1cc78f2f5a6b4f2b385e0de044b106d1168e37190e2f6fddf75ed2cf8df1ad069195e9fdd41b7bbb40075185e892bc5a9f8e184a0275a14700de0acadf243116d8bdd5939a6855ed12f4ac4bfffdc71846e14159ff336a882f1e0ce1eb09135c2286191ab442e72f1350cca2d46b25df49d1c0f0d89759378cef2d2c99fba722b93c91a1f668a875de987fdfc2b6c0191091d30627281f3ab6c5d4def14eb50d49bd9e7ef7c5a45166810f3f166557195326008ce61d20bdafd271149cdd6d57ccb60cd6d7d533904c31e77d093f5ef31775fccb9945cebcce8b30dda364bdbe53c8befa3890365ced336991e817f8cc0d126a8aa869b936325f674313b7d4d1be411e13587dbcd3a54c23f09db4a6aac38fc5c9f022e67e7a6b49854a5f9c8bee1d178f6a9b2343d4edec70f245b98fbd30492c6d73054b119ae38ce26cf797806331f9c66ae6535117457e795ffba70ba4d78453bd7ea1bf9e7c02edd7fe6d170f94f77fcaee72d58c6ca1cdbba3e1938f745a402aee7c3be888ac871d91d90a324e9ff02087f53a34306065f8268792141b40ea7ff6c704080c8d444ba7ea4e00a1d908477902220e53980b2b5f1cb91849e63de2040e98f9c0f575146f0c5831c0fc5810c58d1c72034e2f975fb3c2f38ad01354c451cc46349e2157f80b1556533f441b2bb4777a00f5a8996924786f983d9d4b86a55c31a759080523907512a08071c645543093a21c1620d7fb0f0b2a1dd32db76e030cda55df62f92d40435890c7d95b4580baea0a73fd271d29bc9fe1e2c202fa0bbfdb29a3d643075108e5a7ca8fbd83968d7381b71e81aefc06852d3eb0dc777649974b6cce1fcdf1c87596f07f53f1cfa059ad22244b50594373331c4e16edb347d216f91212ea80a85213b5453adfde0b3c7c558e397416900e1e34954a69c1457db9fd841bf9b423f90f51c535d09eefb488938220897b6ef09b53b24ac6371a90d6666a9bf554ca7b104a354a6d92d40cf78175ef369df4f59b55711ad8da75cf388bf3a18332070d06f13009d016748f0f10de5aa10cb5bf3a28b897712995d9d24c4bcf8f8e1e8b79843aba9e5b35856194ccbf5bb1c2e1df20642b26b10f86daa5042493564ba741ed55d2910f58014cd33f7e39536c6f30f53c991503eb4e6e6656c05d25bd63dbcc112fc01e37db561b2d48136bf69ce350888513588dd10bc66e1f350f05082ad1b9bfccb57ea44fe0abb73932533d10b12e868e1353895114e25a5589c9d21ffa4547d62aa41053cce8b520af91188c1f86c11ddd952c6b4950d3d4366301177d2874a7be89032363151670d5e32be5d7f0c216dcd4b945793d579db606315aa73e8a2b40e888407677930f5cbaf20620f6db8bd23dbae14b5395922be99cd78d3c165772fa97dd79132b12fe97fd3c122b13be94edbc186a25543871cc3dc04d326168ae1eabe77dd6bfd5aeb429c189fb7267e0963a8876ad3967c42a99d850f4012a13a9b3767fcd8cff02bafa02ac5fbebcd17fde948abd72714d14d417271103577f5e17a92919ed54d847697fa429cbf75bf362746054cc11bdb12c99e0e30c9d642c165e1c4c7cd50fabe53349a76cd801d4b47a7b4e880b4135fde32db5a2af80f9275b688828d31218924a1130899d3df1d1a019cc617be01f138384e3f53077f9b2dd349236550228e196bb32f09fa2bb62971c86108a8f03d88e6218a32045de85c295d2eca570ba5889c24e58e0069f61fe7c71df9cb84f20cfe4490f55c23f7b9b5be86b14bb4e9fead0d7fd6aa5913f299bd0aff7d1162232222100156d4bef93eb971fce8da9c86e85403018ca543470ce1070c0f1903a4b4f81e3a4832eec2ca35e24d0af5ebe2c44445e67c9fb3376e57be0b019bbf932c7d1f807a98cccc7ea40799157291ad3adf5614fb3241fcbf774df85266827242395e5c6c7db16e43fe3ed1f0c8f45c4c9638a05dac9fc592f75b0b6616967b08e7ae212f5ed86e30eb165fe46106da8e89ca9b6735e87c0b56d0ddf3472360e6979f4efdad4c825a83e42b51cc102af56ae669461bbbe8b8ce62b7025120a5cb89ec00968da1ee3b33795bfd3b62331675de40d647969f70c20be0cb8ef85a6e395b46da489e2925a0048a90931c944425fba9f5ba5b0553cdae47c347cb6ff9f70c84a139a250b20dd13896fa3ab0acfa4a63620dce2038ef53b7588789e437af9334ce62318f71bdb3e5320cf1fdaeb1b4409209a94f5e845e6e2466e787a43653a45e699da00b24270d41c9443053a7ece2c445e806b734a508a9ef9830773401f87466925e23d4c8f8a426206b12b79e9d168a9feb96ca9eb2008cd67151eb7829a300a35eca2e457ebe5310f0099df39f5af407a1c728e5de5fb5d8aa1e39e1017225f1fb354f3283539a7637bb33f1b86242f26ba14f466dc3e958bc5eba056b83862d7320ea8b8f8275d3ffda1c2f393e63f51cab4403248b429984f3ce9242fb063ba9abe9a537481762ad615d85a916a7424bcf2144050bbec91a74010a490a0efc936508ecd3879409fa895e1c9a74ac04f92c5880622282594d5dbe3785adcb9d5aae6ccd484b05968b09be03f194270b6ec1ad91383150c3b32012d671f45eeee6cf5159cdd75b31566988ea1dcf71ff5899a463e18a491101d0583d9e0c8cf25ccba4a870c3986e40f3c5a6a9db7a4e0bdb606afe3b54278040bf64eff4df3bcffddb87c8e96913529a90928db9500f2078ef239c0784d8b098a4c107e512755aad461fa556f9479f6858ec1e5c3b543fb2ed84e7d3166f267897585f49a82d1ceee6a0098e1b641bd41696ec4769d54b20ec66946e3422b4cc84164ed17d80c2b1eff88c2e6c9119c5e21c79bf44d10a10c41f087b334b1cd184b98803b46118046dd78e73249215983105474b3dc4808ae4664ea041ccefea314ba107e256605c3251cf006943dbf3cd83d74ab670d73f682f3d06d29258d1191f8e27c904689c3e03598c205d2cf4e629c6833cb235d38b69c6638e43a5b23369ddbc179d99257127c096e2d0e40befd408fcc9e3ef4f59b1d0d8d304c91bd646c83645cb80638cfb133dbcd30457ca4a20794e0e760f0a90742324bd972d91d3d010920d4ed481f89fa033cee09f2cca7b121794e9683662c25910e517ce90488731969ce3039a41d5b3da47e89b8c3fbb675e263befda9874caf3e889128cedfa97f869f6520c36c533cb88928084452ecb5c9d6d96ea5a19539757ab7b6e4d6a2625a7fb33eeccee8ef73352984b58df8de3097082b3b3d6c17ad3a880928b12d579e9dd84b6ca7a3698784d2b97bcac69df9af3f10d9c0313658530f734f944befc9c36270f3118dac9d5c58345120bef2f4555e63ccee7cc2649b5009a4545197a357ae0d676b5866a17cb9deb94ddafa9da32ffc806d85a23478a39744bdf654d5d1f22bbc89e39cb1782ad4b7228d34dc3ea823f2f0c8348f7e69d1498071936121ef6b5c8fa5c65d36aa31800cb83cbdb90f361bff8f2c2f77723d5da47eadb10c94e495277f6b1ba6e7543a89b1eb92cce5cf205e16cab4d2ca73a2ca06b9f57d3f01ba3148373f2ac061d7436e536fbdd67760f8543c511a5a20c87a920e491205c80f51b9828bcc11b404b12f3a1c2ab6dfb7bdcb12e2afd9f7fc7bc34c1b4c6a4e6a2a0f0c214921e296273ab2bc0fab0c1f2a927b543599d9fc09f2d5eb966df51d3de3daf8db64c652e7323d8256e801f42b835347751c98bf84c0b5081d06d49756c5243a5a0f70c9b78d7e987a5e4953646c66198d82a77b5c056ab3fc5c8bfe0f36560fc09415c87ec2aaa6367d44bb15e0d1729a0eef1849e498a34d633a48ca79d0aad828ced8dc1272a4a26bb595166bee70787aa1683a8596f947d6ce811719c964f5a1d7d3d501879e606c64de0027af56ed5b280422ce1b7c3caba3606ff7da694858a767c166d41a090d808c6c4403ca4e684269decf29fd835f4ef1a2b3fb68f7392c9e34463d458b26ae4a499a5305ae680245dd638d9463b440f84cf8027d0d21642ceeb8a5ea662c406e2a1abd725f9fd7d666270b266b2f52babd7f71502a331f4541d7f146f5ecb63132261e912fc5d8a6c81054d8d1871ffe0fe9e42c0c749adbc8ecd0c852d749731bfdd2d454508bf17f54eeb41491e70320ba5c5ecc46270f469e39c1e6aef61eaa31ad082e570d9cc926c154bca27e101c8ed568ef07901fec56adb450252acbd1e42b81131e17698e88ae4f917b490f70326df19f170ea0381f4784b939961a67f7f7e88eb995b6651912d08857d964ea12f3c2ef5be033ccb2c4bf219225f7ee86ba128891ed51608996a512e45f6e8ed51d05294d5cb6e4b425620e3a76a90e550a086d762f8bd2f625682e777843c857b7f0328f800d320a24b7d3cd3f5cb24e0be57398484e445ab736c8e77433f5a6708226e4571903d1577139fb30ae5be3c717a1803a97f0455f1c0560eda7586e64fbf1522e15b4914ccb7fa9a8292894a9dba89fc72cbd88e19c0e39e425773d523f9062f9299ac7ab0f8ad6fe80805ceeebf15fd98a724f856ff0e7c0df547dd6b1c254f08adea0770665237d623af3a4bcacb08ff41756cca398cf7099cd512a7911db71788c3bcd1065bab756215bf19b16cf6d12d1de55a041a82d85b41afbd71f984f6cde1251b3683df9abc3a0f8bb925e6779b1190779c0568578c7f42cf1ecd889137369df11693abb9f8479c98645a7156db65b8594ed4d5aacbaf5a7ce113b97818e89e3b07dc2e3b89a25406eac687c7ac6766b9fca2bb191bd8e94887d72f0f2401b0834d01439d535828ea8562f98158e8ab14d8ec7e8c46f149d311c8077ccb7ecfbb994f797bf6158c6aa6dcb89e79b15bc0f102b18ab8424f56517615047594fcdb0740d50e18c398091a71f0cfab3a82d2e927554d75cc6e8e1c659543cca5d97908c08f517da2ea209497db7ec357e4ce51fd4c5896bcb1a1e3b6c4e1c053033aad76f8839541d9dc63cf2c4488c59f519f74745e5c7ac9b4452bbda09d5d8feb2d9fdb47461e5418086dc782e249d6f2a0678ebc05760d85dda602299e861afaf85063503a1447ffaa7d95be2ab3ccdc4510be89694ec662826f9cbc0740824d01c172188cface96c3d3539ff1f68e23145e00d53419f28fe041d15f76439bd0189f905b74b13179eceaa38cd88ae9af053d3619408677580e76bbc1bdf3bc7f50d64b7986bbbb1693b06ed7ca912b26d7767ef29e7e6640eb4aea4dc52576b6ac35da3601e7d91a0802428c1dff964cfd7ec4dea86bb76246c68e4ebe88cb621bc276c222034f47ea94c9f034abda214e223f2b14761d360b9bfc2d601ba2b79afc2b8a4a5bc4a38ca3b43a7f2bd65c5acec466491d7a6e48cd4bc968c118933aef80cf91a494df6a63c18f9391b182795c1e63486699943d26208898cbcfa3319b3839dad941d74e65b486f6ca87e46340b8068a994a6eafc66ff4038eff76c455523790438fc9cd904dc2a98d4423c375ee3ca35f0cdad3061af257fad6e753822e574ff35d70e315d9c9c1907dedcb1a582afb03d7c047b610f286a53972e5491904c096534645375343a0389d1c269e581dd1b9e62e2f4b36ef6c11d2be23b2262687252e0a02cb82ced1b483e3f729de7a6945b82a4db702bd8c865bf32c79f62cb40f67b2ec79e56904c02a7701320dda59794f164560b9a88dd8cebb55ec930e83b918d0886450c2d713ca464aba4495feeca9fa12b06967512dfabaa03549e0d42acfa4b36e5e12a888552a0bdad5de48feafe3790dc898745b67e88239983892881b711d5a0ae2a35c3b7748087d670e187c0c3b4de8691d82560a11d3ea96a4512352fafccf99e4df9885efd888719b8dfcd3111cc41dad3328eac01e1d9510457e6e1c8354e57c70f68024b30e4201be2aae72c3a7cc5f589bf80f269e32555db36e2d5170e10559f33a4779b7e67ea4f3dcb5a5b743921878d6118324ea090295b22d151d9ea4215dc55eeb0855186b7667c8b9326c842b6986a88d868f89052d84666386810829456e3a4b2e54e79c17ac5e1e3a46eef7748c97bcd44a224f6b2ff5cc9734ebb59f18f03ed87eb7942dfdc78570f9c6d030033c194e35ca2360614d624348999e252882db7744d8e89a98243838249d8fe00f31359cb2abf863c9eae258629744c1ee4ecf991ac82f8525d702ecb8d848558dc87f9f1bb85e2250d72f7fd72cfe5d6da74ba9aac906eb921b2c539158db0a0fa25805421cf4d99bdca1f4908f694dfe0253a76e94b0ebbea1a6bc5e44e2afb5e13c6566e19781d91ac399bc8ad63d2dd7798e6103a13c8742660698206e5547eebabc9b4dce8abd711be7b70f1b84611f984de9532aa0a9a6c4c1946f47c03da52ab633ceefcf07647f5afd6302b58bc5868aa3dfde13029b5dbcec2afdf06c29331da27032b7fdeaa106ae2887c70bd1b4290118e03f3efcc413e1453d5df5c1b84107b4d72038089e5794b5221f621b578b0b8e7f01601ac6ca28f9b5a0fee3c6e1304a0433bccb8ef1efe16609cbf93d8bd40a488e47defdc8eaba21a35ee0bc5a899d9d912a8345d27277b418b6dcf8a1741bac48643295c6391598ad564c59119a4b1f80c3d467b2613cb7e86d784999fc0da153ffbb4831f17ef4162baa108c144d5a01b2aabc67aec461e5b5cfdc56fec1b99f43ba332aa8fbf8fafb4eb0e3898d675409863dceac1d5f33bc1a83be68e6c192d634095c99e8606c643c620e391d7c196e1761b6be9ccdb4e47215ba8239254f65d298bce19d43306fa3d272f185bb6e364cabb81ed3be7b64fef67ef2a0ea9758e234c4e81ef7aea7c6fee2a30f6f1816b097c715f0db730c56bdf15f97caad7982c78190c2552137613b9432f38507993cf7d3a5bc0a9c41219222beaf63dd5f7da41cf6f0bc73b4757273e8b211972df4aa4c5203632b7e69b695d2faf7982d7f6f8a5845741c1c6ca70785f894c6e8a949897b1b1a762513881b50c2678fcf15e0beb343c3495627adffd1eba117babe4c7d63825510684f9d79c635c336ac42f76e936ba6263f32283fc2fbf0f504544d92d7b1b6a427d19f519478bf2c401a874bb7961b07c75f928d5aaf75476404d5da4165c3cd0c9be231399b44cd98f88a7d2ab508a4666df9a0a53ec189603a7f03da9b142194317ffeebca9e906aa393c2d60c1cdab03e964d5eace908905a7401d42ecc7c45ff3b2679e852226a5d7b6ee28b194f1a14c2cb893a4afdf44b500aaea1ddfffe18be797da81152306a74d94653428393cb9ce5934e492c7c75ea67435c358005d3ca93bface5466aff3c644504912da5c0772d03699ec4b557f1e0ad508caa74a6b83c940fb7af867d6a5ec86b7a4a86c0f4a22a389b931d704374dd6097cb4019ef28c40b10f2bca6067ab4def0450392794d6a8009dd75dc430ddf88e73720cf200d45f94a16faa6fbf9cdaec792111d09e85a65ed648eccf6592c33b421117e975764cd777704c37ee3caab3059ed1832bbd74d809d100c7e34eba8435831203b5c67373c19c60e33d05cb31942aa3aa69fff807a0d84c628a8c563f5a8d063ea688b896e71e6d2167244f37f2f90242b73833a227499a2d6d48bbc24d422474e4ce59711b708f66ac7248a793ed7a6a8d0e066f2be894d3155bb842108789ce7c6f4de5804dda8636c8ee73e9dddadea52a638403fa188f602b8b49a4020c442822548ec40d1f24dce0084013843132483245c8ea1b8e1abea3acfdb4f596a230e0ba9bd61b106282c007c4121c1433d5c599540d7043a437dc23807ce743a6a10e53e2416a1d49dc3c12b46e78c0356a84925f2f0a1f31a67bbb8cbfe6383c24472c0a95ca959ff3452f400792d954fd963ef994a8e5edf3a19e6c725cc5671ed411462befc9df16d61360a847791b60b0f9c5fa631fa14e83145d048aa3651ea9fdd463480204837ba53afc8bb628eb5bfa084021a12e6272087560d860f68c2ba3cc560644a407f25ee5cec10f7a4b96110e5a6d9b5d209995113a6a207f74b71ae442e413b8553372aeb9bc2d2212d2bd430b6cd61fcdb6f6af1086e3c5d9740524d216f0e772dd8eec979c1908b60d8d89319436b3871bf4741e231972ebe415ec1b0c06a916f8a1ac5c12169013bbc211afb833c18d72da16e8024880f96814252323439fcf10c749c0bd8f0f6cddfc8c95c554ff5889e33d480a54348f342c9f3bd646af3b94fb4c6a940be80a6b6ed8e53c11c3fe47cfa266b4d94e4a593614224936cea2b44dd5c08fd856db953e76a160392063b3dd7f53ddc9a92dcf4cd524bbb76ebc33a1b49c915532e630f949779d6b9c64c2f24e3c4f391d144f0f862c6e019e1bd72c843913631d022c3017f24c3dd9590ce7b3f7bc80ce1a9542efa4177493aeca3dd4baaa715c93f9db44ca5485c0b4945f461610d05d71ed998cde88ec42c4acb29529a90147a94d29450cd13ac8118db6038cf787bd1f40f50930d0268b0b6e7b48eb64719c9f6e426e2c5181891a6dc9fee4f2695a5a1813d17b713f32844b881355a24cc6793b018c5af6761540c175cbf0d514645283c6084de7055297fde1d3c0714b945f310b5d807e8b4ad3c901b4c19aa611438653a13b213152311129cbbae62b5e475f6856411b1a25cd9d20490bcc4c3da902e7fb163c0888a03d20eef58729c1d83091d2fc474ffcb8cdc32a85e32c22b20f7cd4496f109957b86d90aa6e3f5c104542223d17c24ece85ba36737dfdcf174342c1c857f5d2b69049abc20e1ca2f1a0529b119e7c5dab39fb55af2fdc41d1cc675707bb11870554051fd91b6119519988da4974a9b937109f0282ce2ecb8b8d12e8f90035bd4a428f511a85a3328baccc04a4dd78d13f27ac271f333786dbb83c4a2e480a5ae31d2141ca34905f7693eafbd601055642fbdbf3f8cf69895760db1ddf6e200571e38b0b5e8dce929eef322787033fa7b599a7fcf40c174d33c06042b141198406b88252188916cda6998573daad66dea074f9fc3443c659cad55d1f06323220462a1cc493b214a59cefdabb40e4bb7bbafa53636b82e6323d5956463f5d0c780dd19c86b1753435bf5deac40857f457ba0d713701596919aa32b5273bff40aaa89a72a9f192f588c1ae36e7f801ac3328bfaacd29f7c398a6eb9bef6a2b90dc6df9d51ed25b64d2cce305966a5027f1408c50538c6eab5329f0ee810a2ca86364ae694f01d1e950b4a340c33deda96b9d655c8c2ea8104a2a80c49d464a0d4c77231d0bdc4f875da6a64b3d6b1e92ce5de23d7247e55cbc18cfd73d0ce830d06b0cf8a851bb7dc4cc1a2124b79dcf9fa261c03fc41990de3964b96fad16452c29e3c86b29e8459f3acb1b4c0e4441f588e5470fada387439860661c977fa964ba9373ec8173f98f0eaa34e86f40d90574a009320caf809218de809f810232bd73de46e9b17b8670527629a49deaf5a70859ab525c7a99fa53880f45d5e0be5c8011cb001d6012e0de61c110d3462cb1955c0c906517f40c94cefa5518b4ee9b0c39dfb0ba9217f0b3882611644dbb1fb668fb5cbe7b1a97243da5c471f05ef44d1f17bc00c821391caa382f8ba839d3adb1042c69226425300eaa06236d29a2ffa5b314a995a747839279a2508f22a085fa6de4c9fe55e10f4a2e0af2b84ad6a39aa3d9e18bd4891c1ad123135b07a568ec84171f366875cfca3e2a76a48dc5c4953837bfb583eba42984cbe7108ca4c615701d6b8686732a2ed93188b4c78ae5e52af89c517a0ca58f2dad9551169917c4ecc6ba1237ec62065f1a11be2bf16963046f7bc7721f0ac1c6a3bf95c7e33cc7a3ed8c9a91fc54c957cf29333336e9bc5541f3fa4ecab2806ae5a9f2411d7a1f29acce32b80382606bc7b83aa9f2e114d162fa62b997cc150c938453b027872da5824d66c5d399c86b0576b2d2634740a8c83a48255e6307e2ee049d6621f98f809a5bbd3c5897106fa89236ad9b0909a07fc27592fdac3d437ba141d7694c60524ff51469d5b30ed45331dcc791a49cc8fb48590e3e64c88ac1d008586df68bba7eb5bfb23d4428ace187ffc9096b2ad89358e2d051cfbac4a04f9c1d31088dd4004d816601a0de651f85bd6c04d5947229d147707eefc0a6a5d7464497425696929a7aa509b0098884d45f6032c25cec2c8f69469f9cfc1d1206e39caa91d6409fa82a66cdec0de0745c83560a8db3b14c81ed05be2532b965b5b39a9ed917da9682b5563a79261f199425fdf392954c00bae73dea455576ceb5f0ea0ff9ee38e6617a11333e20617111bbc4b428d5373681362f5c7195a241a5f100147544ca3c5ea5e3bb0477e52a82c708f9cfc6d7ff8cff1c634254091b935c8617a80e28e686dfdaa7c0563aec8e8b1ba9c2c43820ef303372c745a19203fe44dbde40de7caf7fb598a332da1604dd35e9d75e7dbe9994f2d5296f6e3ed1505b5f21a79c6ded74461dfcd8a14c7076932d2aacac939436395a6f70627eef555c2fc87ed38135fea8beb35670a203e6a187354a7fe69fdc7f9a96bf4c49628fa0e15a9a1b0b0310f90a49b9faf3022b9c5d9c1dbdf88954a2161fbbb1238c51f765ddb0eba0d8cc56ae8ba26689f17a28f301acde996de70161bfd4ccc64a1f8b63adf66add88d6108822dcdab373eed0eccbe4d0082eb8d2e9cd9ba9d7a9fbfd56b69795257dc018965d1ab6ab03071dec5e1bbfb55500529ae3f87fe237e9dd2831c98aa4c595460437db5165066d6523e3375d8fca4ab9159e1eb003b665e6808ef863223cb168f706209c85ef362bef9000388bba851d48eaf6fb2bf5d7b2591214df8f7a0a10158c7925fb783df4a598d5ee7d146ffe9bd284aa433177776e0b759f005bc92a7b63771955ef5ab91363af29efbb7cbf3c642c12937b464e0898c1a64971d60aa17e714bd048094047c8d6ff4d939502ccfa4c51e875cc59ec35096d944d4233d3721b8318f96f14a72ff0612faa2d0c16e310b38e9abaffdb8fcdb17e75f28280f58ff92ea14e521e2f9d4e960a8617e38bd075a2c2899f3f088b255942b7a6190f602fa39333eedc702bb2403f930b21bcadd9b4f113248dddb63989634091df8852b2af73674c018819d333602617a34c33dcae5a28127e8881856f2606cc1fc3931e5cd83ca29b023226876736a5e065dda5ed12f7e7fc0cb2384414000c8699ff65f20781c65c44425318999c9a5a51662182e9bd74685e0fb28465059e0a1a8edf90c94f7bc42a1296a765bb0a4ec7e003ca59fc65a7f564615f920090e8aea3ed76cfca75994ab71505ad9b8e2891fbba6728c5c7618165941936f85ef0ca2989c5c632ff2d6154b4d142c3dc5241e7ebcf7fbe9567e008a8c0cd2bc99d1460d3985e853d31524c178df6faf452f7df013d87fc63b62f268af988c5db1e887141a081c5c66227c683d324acbc1809f221dc33aff29c8ccf94b6a20012378cc5e640260c4ad49ee6e8a0c5e152715a8148f790d8c5a10006eb8927257aa6c08d0b550524e0fd530a59cff0c37ee8054611cf6330a6381ac3d67a30943376e6c1eebf69b783d5636713bb02ce3975a63c253ee26669e139e9b76ef6765ebdebf1c84b32bbe6fd990f0318804993057d2a4ff62ba1f80ea0e1a4754b7182116f636d137b6de62378cbcece344f4442a2de3bb008229a9759c4ae789495a168df810d45ce598eae31d4012070779e9ad4707d3e283eb7678aa2b0e6d65500dfe197bb38bce87dd6e31c02d20e8952dfc92be73eb296b9dc6e5187ee617c337702660719f1fb7ecd693fba16271e854f8c216963baed9f395bea82ba2d47bf7b2c1cef3a8723a17569f79069f2dd9dcad80c436acdbcb8151238bdd5e1fa5274fb8c544ac14235c3d92447bc3937b81ca2943bef413eb152c08271646f3aeb82063e8edc3b3adf486af3edb670b0dfe8586d78a31c5d096a622adb483d6fb08f4d9233e8eab296d1048873ab60ba29368c82a9c461263132e260c1d99c6dcd95093dfe41f04bdd6966351dd7cb90c1d603bbc31a222d1de79bdd9012d2c64cb2d4625a565b4e42bc2f998890fda1cb60de84a774bf1b0644900e995fb96502c72f45d1a03cd732bbb3bb5722db6473168c30e4890d6c33e2a6895f5c07f9a1ead521b321f120518015c9da8962afff4b15cd4181e9af542997ac2609cdf1792b76ee2454b1abeb4941b409726a64535f828a450b221794aff72c5c18f593d5edad918c23c8717fcdd71704eee6f7e4cb47c7b5960af504258b0251ccec28a28895c4f974578cf713e288fcc3b9a8b004f38d5eabb16e11525218b784fe7ba96697896e1ef05a37b932b7105bda4016f18b43ff9bfa53893d37121ad7e4636a573cd1b8298e18dc8c50ed464f1358b9131fc0ab44edd2bcbece56435bbdb05f364375f03dc4109219ce78070babfc15e78a01f2d362235946868969f64dc5d0dd98a3c4694761ab86dbe0bf268fc23e520c8aa4a089f283387eabca356549b78d0ec89b0265bd8571adc27ca910e13d0bd2fcb465dc863e3ee892de4429e35bd6cebfcc25a8c0bf965b0af9bfa88d1d14b208990cc563ecdc1ad977dde2101ccaacb5767718171fe671a5692b848e0d0b5f459347301e4807d467606387f63b301715d18d959bc0132b0c580603daf1099e06f93a4bdeade87e5261e3345487b8d08765dcc7740ebd3880c377fc512552c930b5bb26015c7b3e59aa357385cde37120171ee7f7d427600139951114933222f29254d59ab03252e6e441a0133afe8c2416e705dcc59280f5470e22d72c695f803f52d789327867df1172e90509ad4a49095b3764a6f9f22051c32691cb0c4f9949df8f934bb67b63dac128f1cc1a098c376f1546aef74f30f66a7f1cc315acab3c2dcc74567f9362079f1ba2a4f3873a32b79e38abdc598a8cb2c3b83b01c73040ae125201205ccd972ce49421c6d97d73f13b0b2663c9c2b49660a5babbc2c786f36feaf8353b6d4a772ca1d2edc16a390be220f979b6503e3eb97fdbf58f231cb9003937ee18d67def3c8516b6b236a9b2f3f73756cb3c9eca592c064faea9369f27a5f6f37ca7393dc426caf85978cce3f4e240e160c77919573297a43230c206993f1632d4902490b16232ed840deef7a7b244fa6f8c1d20be77430cad060876a40d0b512c7d6c4f872d0398699504af026c5630d9da5e4b465c51aa42d27127e9a372259822948a1f8ba000ef0f414d83ecf287d6b6080bb4023cc53c83af0873239f25c16d66c61cc0d57a40f03897a00cf664434a3af9f9b686afb162907704438b591046bc8b58fdd0baea396cb7a67641cb41381f9687a3d2b26352e2df11fd7f156271fe34ecd6e28375064d8dd22ff8a49c9baf976df35e1be92eb7e8da67809b0f08713dfbfe0fc005189c00361a595659b768e40dc0729d29a5600be2dac5d1c5696c612d684e900c2228dc8f118ffec61fb389e0e7384cad7e6fb18ae059185bd66f9b550f931a03e136541c0e6fd00c26c7d355bf88b3e002ff5f37bbad73eb6fa078850f1c0bf3838651eca9e29818ce84d071df2f2e373fdc5249cc6a61b0b2203cb2503018bf584e8e95c3dd907f585de072dfe00bc1e62d053c68d60fb2ad97a42fc723105ce3a2c59a9e52b183edc2e18fe83a6ec1fb282f97b9ab3179ea730d3d618015b7930fc18638c2f0a3c4ec329486414d161bf055090eb28360f1ffcac4a20d20f11c74b35fd782e1a08bb07223a3d7a474360f5e7fb629873c2e08f95014a4f666937cf23d7a6dd24599b3b05c05f6b771ca16a4ac1d0e4726a0f4703e2a6dd7eaff350d078bdba3f616adec3dc111df654fcc9716873b3fab8990ff646617572eb93dfc67c742814138335bbc8c71e8e26b34dec2dc649a7a83ade8fe3fdca30d387b67964cd81d99f24842d660f6eebf6ac26716b3bb757f0d18e3245fa79d2bac8a5d17d6ba17d032ef9a3e605f5c0acb488187e1ca3726de24f657d90244a809fea7482b8e8c4398cb522c198140f6f0616fe335bfd19fac099f39fed9177b3360bfdef33891e374c078c246e0f899af60de0a9d9ab07225385f056b09a982953542fe9bd05d979c7e4ebc5d8da859b2eaf71262ac0e4f9239afd81ced68be0e52b35284fadb13f90d353b9383969dd34a211a8b1f22cc793084373206821c4073287fcebc3facac8cfec999641f8f810469105a310a73ce90b2535a4a0f7a5793f14d54004ed3de266dbb58c3c29cd7eb0447d7d01a742275332217227f40ef14636661851272e92c240247f67b61749a299722340e103442bdc42be055c08ce98b796afbc5a82e1150bfeba379d75c44ac7d9e176951c82fae8a482b5f3836c2047d9008ecab0695301e4ce20ccd15921f4e452b15eb53e5e25455c0081369ef0c15562b225424caa7d18c2c0bfcb28ca245fd5c86ff061965579ac1a01a888df2a425d7359bbab1e52b52905b17b1c699bef900a3306bd06b368ca15e7c5b9d0abf5c1e4ccf6a0b178366a5de706a723b9ac43f94a6bdb9ad40a30f5baf23f32b9ca64d827a88c3097d132cf3c7e7046f3567958e9ae462c5d0c2d3767f4528f8cb610acb28b2195315d887356297b0d13a982f5d6613426f2de094ce698f7b6c9ba0321cd813e8be095e800fe9afded06e162bd4177dad0466baaa9c18332a70b81ac2e69f61557a5c631e4e9c38fd2c7a82455116b21664170a9cea8806c6e8d5638a62e6c287fe1dcd97c113b58524f812bb65b34414b1ceeb1a33694cd781a6fa67855e60d53e230f74907e6db9978382123fc69aee6cb6630b527e53c1573a4b128dc3b718962acb148378bae6ba1185c952024203ee57cbcd3f962b61fe4573d05a33fc870ffd31e63a1e7e8e04ec2bda3634bc21de85460fb32474e6cd8e90c4e25c5beaa3eb4fd0d09f71cd13fd1b402cd92d1a6018f02fbf04542302e17607a36904849109773753aed2409e144ee991c09a15518446b0ee78dc4b0703a9077bf8f77e959baf07be9c58e4cf4b3518b27bdcc54facdfe787c3fb33ac5ce3b600a168c861708377180f8ffdecd4e025ca730c3eaa903869ff8a0e061aaadba996a481a6170ee6d86129d118959ebcc8971a3548d9c07a4c5163383993e173be7aa1d4b44f284e2ba73958df8fd22fad0be5e81f1080ba9f55785e789472484ddb05943dd95792acb6b6c55981fd5f87e3a4dfbfb1a87cd009d6d66d416b36988a9f9467f899366caf3c08dda1bf20ee79dd6456a22eb454a3970708e058203725ff901e5213e0b10dd2c75bc2fed86ffb33bb2f10a33de85bcefa819f12ec14c1ee8668c36d88052d150ae40b9c0e362dee63f26cde7dcb340e5e5f98637de3e0f9422300e1fca2b77cc38c073c0abb003e124b5ac4af0160d582658de0ecdb4375461923a279d59893c1b53fb08e725aa0cce559e441e9f6dcef0804d44ba81ca288691d11b5a1b9f39e66054e2b0da9032e7022376f4e8c5eb9aa3e849d4627cbb714af782181d0e281300fd2c80da4d57a9e7591e1046620971187b527f61b2a1ea54d4ee9c934b41803ff9f84b4b23fb8167e6afdc1b279bfd0266c0cef78acb5fee1ce13f62c5b600ee7c509e761eb635f25dfc0de14a3008596675a14fb7a2bd26bf8fa3c50a8252ef691077ccd4d10adc48f957a2ce70ceaab18828cf6e11b746b136f62bf7f1cc06f5d0f682035c4dec12fe20f5ee0820709dda6a4c6f1f0af3e333638b172be569b6fc2115891176c0c851bb10008bf0aeff12c20e3c2221c8a1a9efb9cd9a7fbad5bbdb718000222f47e5d913292fa83917d7401d1aa8689281711605b1e3b7cb38fb91c676a171a4ad42c02dc084a9a20ff81cc566e58e6f71b5a431db4a5c78860fba439b1d25ecca735db17e85a239e22be1405ad6120bb916bf9d7236e1a56235854fe1eb089f1675fc02f0963a4e5ec40997f5006d58178796d8fd2b4b833ea15485be3627be6c30bd0fa61e22c5abe2e2004da61c0b613ed135031d39924afbfafededf92b3516f28f86532c8e136e98a294279d344b27d0a9283a444e3e66d8e04d1714a49f593a439522cdbd4b430988dc05580110aec7b5fac42760e8dd2ff6292899c7a41de08fd8365666b2e33abfcc2a0004ca2bc10f861690a0563c60dc0c4159af5e1932b2672a84f7d3ee3feeca9f3ca8296ed04b515fb4e2c82b49989c4df38aff092422b96ef4237ccb2bf0957c3b5e93ff4b5f2847296594b23fbb87fd8f681472d3c346f313d03814963fcc237d7aea37eddfcbbe9efa0bf41465949e304c4a27fc26a693ea7ef2565e4a3d7a62209a514917dfa5d9c9ca08ce84f4af7264bd2b076e73e70afc13353c6245a5cdfb912e9d2884d8496c7f6787b2125f631b4b90090419ebdf746988c9612c598749b9d9ed52fe01acfe92051fe60048080b82d6748d0c6608c424563cd14a98d1c809559a476c5145a32aecd9937234716b42be075a706002db8dd75fea29a552e893689ebde4c01a2a12c2e9bedcfc8b6694d9c10dbcef35b9eda1d9d97d75f4a71386f08154f0e6dac171bab1f5e30e63cffb2f0424fd604d9cf676c980a54d881b8cf2a9068870e83c880ee5a00675266a7d547bdb1e8a11417306d08907c25e02a724dcca5850832a773202e5ac041e732d6ab01eafb4b7329782f5b5f68cf5607a6dfe32e64cf4f7bf119041174ee0c6fdc44a0cfd1430dbe97da11f439a6f584d15e34722faa7fa6e87dddbd08a279c32babc6c292e96c744df05d6300571727e9d664d2a9bb4502bbe6481074dd79259a018374a8225b3ade4b1c35d3f15d59167564fdb2506202088f6b1962cd12998bb82e6461396b8369520862000720b62b75835251a6241818efae61bed0c2c987df772022288816efe862628e5956213d816043dff88f4712da67437548599d8ec3ceb3dc1ba26d03cd31d71b9562f59c9a7e7d8d209103384d658206d983f1661ed213814ef5a9502d1a7b4f696fb764962b859d89e82a216c96b94088a73963c97a0102add0c5ce5f9ab3ab5c03ccb19c01ba28c31158bfbf4f7f90fc8f76c8da3795f03cd4a2ddb49e6738e6e65f4eeaf19bd3731e34c7a68c24e5a48c47daa135bc250b496cc8795ad494e614b15c06d1bb48905e70fdaacdf066e61c95b972188481aa943e45af90a89d34e0c8c4d2ab039159440a40924f087ec159061b663d2eeaa800d69c93b72f2a5cd4cb23a5ce403c24f8963e7cc2378240ac7cbc8183d56f52d63de183b80e5daaf7055edb14fb6e9ebebf29cc3c39ab3fa33188a254f4beb876893644cb9e11b28c49cd6159c91618f9dfd1b432f6d2d057bca22f731c04499b8c86ecf8936e09c4162144b82f2df57270ecdd577cb0a735317f6dd0413b68173f4bb7db773d1d9ecbb42c1e0d04a20a2fa8ee98e554709c1c5ad78f4b5795ad7d360b36b70897ee5ab4681d275393ef9c8cbc8b5d1f157280885ff00183fb286bb78df7b9ef551e25aaa50e423f94f507718792995e55100c0241580d62131ed0cc019a083cb40ff8508def866b560335a41891ba1a647cc500d1937afd17d51f357c421ccd6f23b21f94cb51572421dd6e959a77842d81d62ec3e401f66d36eb01ee2c2c16b95e2c96b1edfe93584a4ea03deb49b6a6e08a940f3b0b603e93a74106f25e8b7dc2f0690f1c7903f204e8098bd629d19893820769800280b8e58b8a21c9da7bd941bae8a173cd3226f511fc58b4f6b6a1014b262e10a90135df80ff981c4ce678b50ee6039be1b81b4b1c35b6dcf64b7a7f8649a0208a4e9c0d41673746469d5b5db3939e84d1601f554a02d487687dd364312c8f98a153e0b25c849d02631a4ef245adbc26f7ee225a824d9ba93d0330a32c9eec9c73417cc5a07e27e8bda06d6e4c830505fe6e67acf4aeb7bd5e3efb8f31d51a5c39c5671cb581204df0ac3570cdbfb5a119754bdfc62a8225f2c7798db5dd31d5d6a8e3032a34da3d1e5716ae9d059c35faa381852db7a683c068409c8edf9b984668fac2f079ff117a5b853bcf1c9b36ce52599ed84c8a3e8b888ef23b11584a83d707bb173703008a72f260ebb1bae69f8540873fb92eaeef55faf0b1cbb9209ed0a8d4f0e7d075bf7d4f57a886210e401182028ccf8bc3599800fdb56a94c47f40d75ef2216bc1848e83684a4735d70f980cdd7d496babec59fe381f53c24a942f36169a20212bdf883ec9b8a7b5beb95c6b20117f900c67d2d8437f62ca9482a68a9a1ea8ec880bfb3aed37afb573ad3c9b259354574f1a4fd654745e21e28c0c9fc26f9d9f0a38b8dbfd079f517a4815351efd96537be1a68cf0add5d638596ebdae729ba323a4b34aa59d11b0794a35980f5cc158838304e4a45603fb415bb0f2e9b0b7abb526d1e694c7ab10398d332e3e2c490f87ff9b4cf33365872f43f10d9e903f327b7065745562b1105e8931ecf2131923f1b857dc1b92a0c733d696430269cbe5c487a4219426e9516edc05abc96573144b51b6073a9e3345bf9e5b4acdd54b3e3d73804a2795234671fb8de12bb25cd40079ecc2a9d0b2505dc5536a0736e8d17392816eb090705d8c24cd47e0c154029533325e5e271b4959ea4b48b93a9e6133e0e85a36ab73046f8e2c7cdd8ab3018a9b47ababb269237e2633927f6403c422db9e9a67990294d24cb1ed8c9ec711e3f15cfa8caf14c8be59302c6b1497f4656ad76c04b431279927b9ca2c960b80cb038252cd70c115cc5564320312715c50b03da6dfb8a0cc49d3a5f88f4e95c652f3b399feaa56470b637598d9370c4bca8abed37712dc2e8ad4a215dffb3cceb47586d94bf6c14cf3358005d5fb0e0ad875e06809ae7aa02a8c385762200a640e823064e55ad39bdcb13c88a3a249c274013b2b62fa963da656bb2455316d8d8ed1e89068689a230bc6b9a507bc4e40b54e7a13dad93042cc31fcea3a2e584e1c9ccdd26cb4c18ba8fc7c4e1a3d07aaedce9956f0c740b21805ccaca5cf6dcaf57de3a0bb3a7f049d34b623b9c5210f69f0b2df56a559d4546898d01f1f70db35bfbb5eb6d3b20f32a3d48672f259fb92a9c00b699989190307021992f2c816edf2fd0053f30f01ae439caa9c90bba64b05772635ef2c172dd75c4ed823b3b53b27d63cf35895cd41b1b6d6dce54c566a78c52ab3400839548144b0496bcb6bf943f89d4a87fd47a0b60b08c90a10af9e1f880d43fda5d4c31515ba4792291171ff6a9f88706e0cc4be2402c50e0c2ac67ffff1445cf49990e392705175bd7e0a6fb62712b0ee49abb8b1b644c17e58950c360aa982b04cd3ac20c8acf68bd8d25d13d76785a28568d5258310a4f2115510bdae15ee32c55f13af6b17c76db8e47a579ba6f1ed274b430d94b663db941fd0aac1130be8452a0b3556ab4c0feb30ecfc2cc565fbb67e4d512670b73929ec4f576aa68b8a513e4ed71fd0d0a1c26fb46dc9199769d4955878ff217198f0af234878b249717a46260662e0998380a5b4696e9844ba879973d9f821b63330e1f97ef03074c127bd2d01a65fc3e3b2538c57ac2f80eba6e8661307076189d6ba6f885b734f71b37c8186ba96e02e71e9a3a122670eea685e77396b1df32390299d4b755a8470e3c32f83db440061e4650c12c3487f059cf7c47fcbbaeff5e34f5095d850057f6c6071c69b4f2307b9419d8dea05fa7005d8a14ee2bbf5072994fae433a0040eb23d9c182e4a9ae6af16d8b0aa297a6eea6d771e99954dea94560df7b8c7b68ff2a244b5b1f209830cd954957defae69c46bb6b3b5a0033c347d2a2223601630afabc1fbf95b41f3e6a8c84adf38ba0e0b33795de35229e241ec102909e7fb4562b31b247c166ad80deb2ee2cbcc3c766f9bd9685a5ee6c9e87a29d1715bb1359d2371656f658069b7b2d58502260033cb1cddef31ba979a75f794d1d03cfd0a44f8fb5f10e8267dfa297a12968d1d881656e2c8b6f1a9df2fefd06ada7a4b571f57c55c472266cc4c02d1d3a9e3ef95efd661563efb98338dfe4aba7b3bc6b586ecf9095797866d43a94dfb8f9638ad230b29e751968ed7319003ae232308fe1a4c94a74b24127cab70ee015b8195f45ca77aa0756649184125baf94d1a31cadf7049c2cb661403f42cd1df7344846571ce87435143e69a41f659a54bc8bbb44d0b12fac3c54695ad357ba3c0cc0ca878b09b8c27864add75e145120be84aa49f477da3dcc125fa9180cfa40816ecb69fba7c9a1c9769a3c885234c0dd48032f1dffdb25493fd91f979170a7df93d0d7cad3ceac1bf363e75a1d935fe1040ce51dcea587ed17c2c3dd9ab0a0e2125b7ae948f4da4b70093f0250b1320c54b63eabc9ad5bac0da9d99cd07fc6661a0780c8b7b71e8b24387741b1983bc1134c8c69ad60623089a9f29300a12158799aa2bec571fef9b14d4d472e16feed8d2f08286af04718afc0309bf00c4d560a3024e352944fd5caa484f9dc3c7c75521cce5426c0567d0c378f0510b30421529b3bd55541aa371a3b14af59825c0099edac915ec8a360d048bca05a16cf1d41e28c0ccc31340325243a0dd0633fe1744a28859323150e29a200127b0e346c01a230cff88d1f0827601b7afdf5391e19c8b60637c839601d0f30cd6b0655b38f8249fb493de113b1ad1f0802474c6440e2ac3d1a2e0aa8356e9a5b6970d7c7d3f56519d57023d33b3082a5f786dfb01dddcd0c3a97d0e3eb1298c3c4a03ba4e5a0bdb5c5689f9002692f34ba23a6dbf46c263400ccf38616af6b73ba5ca63562a68e92fed5783aa142fab02a96941bdaa1ac91e1527a79c6b2b9f87d47cd58c0d2023a8dc6fada70c39b6e23ca4fc41056ed84335996e2c46cdab5c7fa7f61ee1f5ea9d67275ca2e9041a7ada11ecc6870f54132e562e10c1d1547f96e2fd75a6f940db3cb1627c2b13381c6f603897b3b3fc6dc098cdaf9bc561a63b8eb0f71df4d12fee0bc8167359e780f4301e6134fbf8013ca9c825510bc65b57be27b440de09fd00dec2ee0d4f6048dd4e96df5de997aec7b7730cdb41f7a3a48c2421c0b2c41293dd2f89d77b27cd73f4c451d13bc67ea8e6d405cdce68f683c54fe6bd71e014515892510134c94465c1c1783d7ec8e243976d9e711e852c37c486cabdebd9c24826aa1103ee5b48dd4823f416158b04dea13b687bc4fe41ee9a783529c692c420bb7a8f6fb438b653007fe0f33c9995ae59dcf3eb9a274eece7e457bea49b628f3c43517c05c2027047260593d24926884ef2aa2de480a50ce5ab383ea3a8dfa8e1f70bc60d92e54bde8f37e8b089462d7a55d33aa8838d76d7a925ccefec1e09ed74fa9c6768da299a1c9382e368b3e99a22a72d606c084a1779eff2fa451027ea61727c391c3a78c7f23b697ba410325d02eadad416c4ab3eb4bce0c5819aad3df174bbe4cd313b64f266b20c2385440d1a0f853aa25bac320a6972d5d22864e39d0e108fec0593d60375bfe10747e99800c5af641a3c726d00e7c46b099b789885c82dd4beefff0fc9f4ad1f81026cfed37e864d721b7bc81f054f448263f06ec24f3d800dfa4d7c982356ab7504dea1fe1d539bf8426a5b553ee98fd59277640e87a607fe1483b005bb0ae68c9165f316fb8aae0f17f9a0036f39086f78894cb151f10171799f1cf5813b96327722e9827d3e8d2b928761ef93207a181507b9c17333fd35d37e737c5e99efcccc3c10d4497e081dde48f1d46530883b5f791ec12e3690b75eb3f622bf1cef93e0f7bed99561601a82e142b0e41afc58532c094eaaa8b494cb39cf61ee212439e829a64b06bf626ce274b45dcdc03dbb086c117f1a6ae4fb0110577182d685bc214891c106cc00ac5e8388e1bf3a5afd7515424556da1f863200f8e6be8bb8e992346d756fc14e15c19375dc759bfe8a8eb6228710e7453e1df33d2968c02574fd175fa5f2939b73a6a96e157f3fe8bbf853afa7ffc5899f33ea9f1b93479a5bc25b0dd4cf3aee321702273b3dd61d7a39202039b4ebc1fe4d0560866cc9b70a4a3c2f252456326f0a5baf350b5c0250c5ec8d5bc80411f4796c61a278dbfc747a29b29911e6dc6268d3517e9d0a6b92f2b21cc4c7d1b477f253a936c637437219ad023164662e26b4ef84ab0c2bc8d2f8283fa746556808d158b0736719098f7daa763be080ad411f29369da5a860afa9dea58ba1444e4a619f0702db57f091cdaafec18f3b6b5f7299b051027c467cf8524d8bf5c884d2957b45d1dd1368afc8f37b814c8835c8e620e72a9720f94c529a6dabf940dab0bb13f58a11ea739970f82309cc5bd7194e4ad71aa4678b8d59099ff0098e0f07bdc7e849c31553a433f08b40a74f3428a4780897d098101927865a0dd06245e49b0fc5ab966355694a4456ccd10ee094ebafecf4d3926c3bb91339bba1c560328f4ab72f0f044c964f0cef4de3f20294f6d4231700098feca547c70134a08e619933053e7da790e57b1372e97e30f93fd0692759693d6eaf26dd50949a2f46dd6e2781454643897614482c626cf7488a978648d792760eebfbae1d695525420278f2a0788e3260ee812ff32e49510308c06f984a9cf2f119646eddc07e601d561b4718135c457e11a84ef3b0745af88db8f7f678f0f8538ec21c940c9bbd0089578d4e1a7a2c4e9482187b492873dbb2207e6aa60cdb679205e41c4704eeea4180122742587e8c42da9169476d1b95ac1ec618cfe0b1f97d52594b3524eceb0511edb4fe8bea1c93945459998eecdffc6dfba4967b0118e37a778d84c20cf9d80c24e0579ce3ab94c51b58effa322cb81f612314c6f2dfbd4e903040cc55fb034a83c2533dec39b6ce264adcf06542120230d530732d7ca19bfb4e4f6b0a8ce0783534abe743b83d585851e33d1df82c6f87bfe8a0b26c10e24c13ba7822c3d7116c337cc28713e44b6c6e73bc09d2b71629a6e10b53aedeea644e14b9c5abcf5ea9ad0ba058e7176a306f9bb152df4e752f36aa4187c1271508b9818138ec0c593481434f402a10831bf731b6f2a4cae5e117cd2b85e97fcb018c23c9fa5b2366f457e5b07bef03b334a54dd7d22ff8c4b0fe5d2bdd5b99549424ebe81f33b40691e12aede8012060d99794f5e3bec7c479092dc1fd1d59cc085b4cbb9cdb3c36a4427846718855ed0c18f845c7fea6eb57d342f4e6cc6d2d23cf799c2fc9e849177ce8d009070e80c02ff9da22c995d1f17780bfaf4d12cd6d559b6ba5437dbc93f524805548ea5753b16c05287c464e8dc49d50046266d12afb04b27c4cc070b9f425b7c30a27759ea633a46664f2843ca657c61e436b1fd31a81ba815b75feac002d125dd925774c919b313c372efc5e5b4d9ed5a9856c1837dac1c419d29ba0454980197ea02258e0e58da293a321ff5d04d494e979de2782f373f6c5aaf2376ae7f989740894ca301f2e39fe750e3005c27dee1f8aaa8a7d6c6921807f9141b4849ca0f207663217768568524cc53c8fed87b00a0174bb5193d73f2560944f0e67bef8227c40c71c0097aa7ea4ab9dc46ac62a7fc09e117c884a360d9dac2b9723844c9081a8a616dfb15f9cfdf3996b18a727a5bedfda4ed02303e53a84cbdbd0d8a5c4b2ad7eea39ed1cc18da2d3f769b8928e858e5007231b6ff2c922b70afa50fb37eb5e692643105f470ff0686f2aa7d343602527a81358862aa8489cdf6f8b4fc5c2bd1d143410a273699e21f6f62705c5758403f5a29d945dcd8f1ea8a3cc4ec22be0e105bd6fa67d26d823b0df7a3a20ee3c13c9a4762ab644c1640c9551d6d6844119b2d868ae90535235fe85675a5947f0b156b90208f2cdd907867ce65102b722e1c400ef0e77614c00314eaeabe6c350b16158e2a593d4d9584f98e243076051d2e43c1ff569b8e8045ec7bf5791cb5bb348c9d36ebef82b85341a2179d0ba3f29c635912d053a1443dec2a0a5a203e0f04dc2a4f6195112686314930aec2fa397a11f1f2d6d2d5870dd25edcedaffe7549bbb6d902138606c24899d79e231f72edda06fc0069af96f8c374c7be33f13314d47ca89a8e1199d74b88b93bd6d26711c3dcfc658f6293b9cec3f6f18a53753e176b35b349d56ee12cd8cc7503772962a4ecb422def050e803a9c04fec2e7089989d168a4ce165ee08bc85977f09794713756c99c78f148cbd1990699faefca5f0afa2ae4d2625c34494e55cec97b830e7d5b619a935b35f7ad68dc07013c68c8779bc8c4e74bb05f011391ab38b348290a16b58de7675b41f50e7ae3629aea480d323028348c35d184a5349020335105f3449e453e3895f92316d5d231506125ae66412f49e5eb070117d052ae284d770d947a8cd24462093a2792040d95822c8293d7715a281f746026b2a5727faf1f2f7848129098e41016e62adeba0b233bf391f3e20c91e545df7c996f7ad2406788582ccb5381f119ca18bcc74dd5390e7e00af763216af81913ccfcebced47dc6bbfc2c16a8e912f0d079cf783ca335532aa9d32b740fa083a1efd70ef776dd3e519c48c033b4a83796141d806dcf6c7863d7abfe7be01535c8ae8ee6a894faa162536a99922ba7803ec5ae1edb7f93759cb71c4204fa85e98dfde1b0f6991be53ccbf4ea5e4e6ab38cc981599dad3b6e685b9aee28f4ddc8c8130f0f808416fa22d0354570c3c012ba96a3a1b242e3938c667f9c8f03a61aa6ffd4c45c500293997c14d162a5c41e05276462aef2e7ce4fbf962750ff5c1b02c0ae2a4d9a9c2a1c6102d1c79d8ef88096dc29d93af66634eb471aae05052947f373d46faa5bbdc72d5a86a2dbd4f367997b265ab1a5730c24c0e6a7e65754f624301c15f573968107795d1d20b90059abd4f675c10000f7bc5052f104c227215c04ea9d01dfa046155be8e0880435843b38c51574db3550e005d6cd30e0ab86ccf78a9cacd202665eac3946dcb6b59483ca4d033557a5b55fa3165968ad14f208978feb4d69fd53b3075bb0d9f082eda1b0b476d1fde2b35a14a495c1a69e2a6cc7e67506bc5b0ef1f4b3a2368b2a8643a6b9e3c782a1a1fdb9cc8a2689deccfa3dd3a493187b54f6bc5b029e55d99ff2118ce80b72a328085895b136e9b8e3f62cd84435ac3d79be475cf3383ab9649280f90e7b5c5c5431c795f94f5b1236c9c145aadc0652fb788e3a7f331b75e969988bd12ff12a0027859698865cabf0d9d6ece4cdfaf5ac1193da8a8e77da4679542100f31e3ca2284c5aaf873c4e83d7211211267cd3de4d6c46cd9f5e2b52bcb28bf87aa08e5a9dc82845b2074231a6b6c7c044e32212be1f68b05f240a673e154f5412e27bbc73ff12508532dee3336a0d62ad7fd996d0e2a8f8933126cc7702a643fea59090a79fbe994aeea5bf40aa1635fc593a2154662e2ffa60f59602e35acd4628324d123ea8106afb1f127e495d710931f7e537c910111759da9e11d3575242a06abb629d047105b3f1ee41f83bf2414281ea8c097b2e9ccfb8de57f1d09560cc499b50de20129aa1476597bdb3dbc9988acba8bfabd53238f75853a24b0ae275fc54603619350068f04e304740df164ba34e46db94288cbfe5e05d7196fb1e8d22d968a090662dec38d32aa711b7b2c33f5b86cf9b1c3985b72b810dda67c3b2a17e24a9542bd32be179096849ccd353aafc276cbd4a81fce2dea4b795e73457bb88217cf7ba621cc1045c6d98639277cead96eb2b9a67a11c9e499d7ad83fc36b5bb6905d9ea7ed611f46e80614bc02668eec66da14e4f9a154e03bf43639b51dd5790ffb4957e34b837bd8b7446140d6d70c2cdc050e23d41aae83434ec361e240f8aa78783fff790e5c41e6a7fd3045eec4a3087cd3b8dd266d0f6da9c991b836c2bd6506275ca336492bc09393dc77c5e14259a59fb73bf51a6f6782e7a93d9609e3a62877e6a1988d4f0a03304c05041a7193ab58309855693a7f79f6efd1ddeb6534a8f1dabbea165f5291e3ba7a8942646ebeab69cd1c6eb3b2686365f0fbb57a32636e8e832fd050b8e181a6dd6cb2f6cedbc5607e6f62741b03b258cb5f5b6bb6971a2d355ad2f6de72ef2d0a620ad90af851ca086d16bd7bccf8d7b38ee67ebbdf445f68933b9b8c455df7d5907e7d3f48bfdf8f949adb7d353793eee904c5c47472626262a299689a764ea471269ad689b4eb2a695fa954e2388ee3382b7497b9eb6c321e6d5e6cf062edf8d5455dd775dd8cccdd3de78537739fda29c8e8f834a48bbe286570c72779a267d8a3211d7fe4cd1cd3cce0df7b8dc8dc6566e775e66866dede9d8274e74e63a50ceda24f3b771ad18ce8a31a0d37c3e1ef36a85da66383da6baecd85c13fb88eaf2bacc956d63318f661d50b83c8d8175a8fd3ba6ebd161bbc66b2dbb026d75b3c95a042923b866b2fd3b19bf214ed2837d9b89332ce131d7b1d8de8ddd7bd351ea4731508491963181f3fc3bcee23cce3b8474d47e9d88761177921f7781eb48f380fc38e4f37d2312facc9d82bed07fc0e63cf38af7b26b51ca3731ee9ab4026bdfb31fa88a0890dea287df498bbd2171bacc9dd47a490c3b493bc9084518d8776ecfba17df4fdd88e9df36a48197be785a4af8694b50f6f8f3f99a47c473131e9ba7b7f7299beddc548c71cd69d747cb1ee240ebfc32613e699f03dc6183fa35a0e9c1d3b15469779f4eefb717f7deb2edf7d2196ef8f3baa1acdfdc5792129739924c217e18c317e777282bfd0e4a48b44dab9789257dd6474ed25af0899471fbda74633faf5b5c6e31d65da9277f46ce471a32f947944bac82bbdf3b2a9d1885e7ad466f04997e9934623af7336b799ee98d4e0f6d668f0b73d7e3feee5372fd4be1a52bedff588730d8933014ba6cf2c5278b3653dbebe0a91ad57cf2ccbaa18b6f5d7a3dd1285882c42d4b01c610a28bf34d3537e4fdbf14be7bceeba4cde95debdd278e06f3c4617852c99bb381c3e73dfa9f4d18cb677e7ae9b78e1f678d3459b77992ed3f8e2b010dc3b6bb76d3b8d8ebff0c443f4ed310b711a7d34a2ee9b10193f662172b7998e4b9fe9d7f7c374d1f783f4ab86255faf61c9a62fac6139dd4bfb9e72ef49f693eca47bd665272727ddf67af2abcb4e4e7e6d27ddb90ee5e4434141e9baaeebba29e0cb8ccf7138c3a32f1492471779214b167df34221f2f612f785cf5c77c235d885a47c72137715d317e2cc3568f2953c959bbcfb42d255bcd82d9ce83253e4c56f5e759217d6b064fcfb11c61863a9f1c0f8db394ff4ce0b5972f7a9d170177db1c1ae7387b98fa5c11a96f823440d4b2e42a2f4c6861ad290f2b0a837adaf7ed1a643235a26818bebe49808e528f7a68f484fc1c78eb157fae671187612f785af341ed831fee6c5463dbbf665df96bde485dae3ef3bec65217e864bc78ebdf0888cb9eed8179e7868cfb0735f78ea3e1a8d8bf988cc5d7b95dd635e88bdf4e5300463dfb3efc77df7fd20dda643fcd974de488f4f19d1903ed1eb68c47122918813d98be16cce8c4516e38e33ec1c767232359ace27c7bc136c3bc7611c87bd9ebbc5380ce362fbeee5b55d7edbaeabaf26a49bbbae19eda46f1ef64223327e6c54e926dd4b369dc30a4cd147dfdee17b2db43949944523d15784ccf5ba3c4d6a3c34ed9bd71d7b53a3d9de7db1417c99893dab358e66fb688357cbbeeb11db23d4906da4496a56f8dce401e4e8430550166315431fb956d565f85c738cd863fcb26abfa9f120248732398cb7a93ce44321b93f65d0c51638cb2454f44c31862c801c7da6d8a9599685369f7e3af93dca473fae73bfbe6d1e9e620a9d7c3d650a1c237c91714e11684ac75f48c47311295974fc4ae3c1fdbabe79e1f3b66d1be9e3414816e15f57d83d9e8793937e2d40e693cbf4f5edf407d1b95f5ee8235fafb41fb6e3eea1edbe130f4272774c3a3e57012199f4596025779f8f8c1ffac8dcb987ddab1d329f9cf342eea22f872198fb789cfcfa7e9c9cf4fd48f96553f375ec85dc6753f3c9171b3cd59cf2f89117d28cf2a9f52069523edae0e9fbfd628328f807fc85329b6ef2a8fd60ba89c90b37904df0b7679b37b389c9a5f643c87d7b6c30f491bb39b3d47e08f1b96fdfcca62fd44036797dd57ee86cf2eb077c0e48eebeb0de66669387b4c1ed7833f9b2579abebcecdbb72fccaab7dd666ec73c1b99af4b2dc76643f3f519ec6e814f3dc8cc5d66eef8d3de1a4d768b2f8fbf10cbd98fec16e3214f054f76c9d1878a213993da0f99f532ce93b7465092ed33d147f2c267d23b2ff491bbd24ade8e5fc25fb81223060c182a2aa7534a0a0a8ac974726262522a9148a39148d4751873dcb669dabd598661d77572f285366f2f95bed0666ce5ed27fc18634e6e3a7d6195b7a33cc58bf105e50bab8c6f824d5f4833bec94fbc185f4cbe90e6ed256f7b8ca8d2b7d13b107fe13de93293e4c5775ef59117dad4ccfd5ee415213377aad174e62eb51e2477fb85d9b97bdbb3a8cd488d072151a6b92cded67470dbb1d77de2775ff88c2f3339af33f6c27beb7d6e47cc3974003f6b0f9f351a19decfa6e6ecb38f9799328d79385f5ecce17dd41b22ac36b4a10d43d986d294b244912429b2d842e28178fc8eb8c1fc8090afdf114708a922038239e1e8073153a7291f3f0dc4cb78f99da69c1107bd2cc0b753c0218cb422c39e6d7588a625374e920683be807b26b98490fcdc799d90736f496f14cdf72ee18c7c6fbea67c59f205f37d6cd4bd9f5717b9bf93035edf7921799a6087caceaefbd375a5621220ecb248a3a0a019bbf4e9f1c958fc0249c68072288d64aceae9b902d6832463d88132f6abafc7c3136a40859d838c4d0edc7b3f43f3bd37423eb1dd7a277b21d9621d7cef8bb4914e6dbfd769f026d12056023f81731cdbfb9d44becfecb59ef5ccebcb9b6eb9af5a8efe8eb8e794e0fb3ebd9752449969a796434a91effb0431e6fa7da340dab0bfa7debddd259032ee97c3101cc990b15f5f9320be60e7c6801bc37e44a3eab1cb9b46d9639749481b316343701a959dc8f5b26bda9d57bb0d4a9ce6d17630b10ea6f6e25a65a53919d6c11d5a938aec1c86b4c4029b2072571c8122478d867322cb32ecf15a9665599665693d08817df3d78b11c4eec146dd2f3c22dbec636970f668706a1ad6d61637982ce09c2718032d43d501b6584016900564015940225555811508e30256555565d9b7a37a78895955252358e7bb2766fdd555619875495aa725e42cb1eaaa9295ecaaaa2a53ae22487f79d56aa55ce31440f40cb3abeff1e59f4225d3872a21e966ca92a2b1855251a351b1aaaaaaaaaaaac68d4e694fedd03c986e71e9a23a808d1b3b5c0850c055550788d181d53800f68533997eee98f33576647a93695e43987096d56854b46daf3001d698759b491c9977619dc561ad1c955e97090bb187758448d1d01517492c6fe6a6dd565b94524abdce72c6b9d1276cb533c7805a6badb5d666963773d6171b03b62f625619d0784c0b0399d2541e34dbca7e41ec6d6c9ef1e57a4f0dd0387b93f1a576e70dcc9634d20dc3688e912378d6b86159965503c605ac615996455f63a6ec075a94247008e669029313388c3f8f9692b39ce52c67398b17966559966559d3a5b4061cbae41b2535e030feb8dc60b13e7f23c6442d7ace39658c2f74ca1c481b8df12077481933c99c69b017ca73d0298c146e20576aa22c885690197c40828a0445b2653d1a792deb05a1a1f659e54d4a675f57b441185f98dd64fbe9edb836794ccb91dd2fc88cccd937447cb19f71c94f74df314d268ca93d0d281ab497e96b034c83e7a3b1d3a07d1891c0fd90c64eb6df21c660b7a8abefa4b5de4af321fb2e69edf723faf4c8c0c96265a7124a5c9315f490f2881aeb4e8c09414ced88406616764c2fc8344a267ee409327d0f7189c90a780b2d6eb5d5bd341faea5f1202457af327b6dfdd9f8d2971e95af69d4a93f9971008c2df4568299123018926c54e869006c805b7e70189700b131e288fe66621ed2281c2232a7514934aaba8e13456662a63bfdfe6e14b8c1202dab1bac0b593c92e5ab283294e56733738e8f04c850a4c0e1338a10b08c48c8c93503655a9fd1940654b3bc0f3c492650153f2ea8420e6b64f9e38223f207a8aa20c8f2f1d59327dbc5cccd36be5429636f34968d2ff130350e12b9e2c8b469c0e10d93ec39e7677f2a0d52531555745185162dd8b942476ad87e49849cc8550811c9b18a2dd45023c72aa0f045111c737d18b07593111714139bde05458342f2851d23c2ca184685225900395a31a44496da7c26611a25af4faa7c26b051ad6117e098abcbcc50f264923e41de8aa02b7072f4f9f9a24b90a38f154a7216b51c1806d88a9f1c7dac38924369822c1fabc8d1a78a25796a39f012b8afd81dc95dfce48d890e0ee9b4175bd3335dcc6df8bb0d529936657c0e7b3153dc7d73d20625e761cd33a99ca54150d6b8d1a0bc0cb0ccf4d4d603744a4a29c53068016602476c4a28ea6b8c3418a1f000eeafca2ca7c822de01d601a6591659326b13ba2ca17487f96d55e025a3d4100f7c07c1717c390cc1a3779f8fa3d1e8f3a2d798e6d743cdf3f333a241d16983a2cbb48fa125434be2c518c894dba6e5b0dfb41cd794390c99cf80e4bd982d2d87f79d9ecd7bce9339866763c4110da40c587ee57149ae31ce71df5e63cabfd572fc3b715f0f3573e73e231adc5ed276c41ce33b71e7e4ad955fd274cd3b39e6995c4f2e195a52dd7b29e568c66519a5f277c35986bf28656ca79ecc283da687a1e508429ff2ccae78b1418e5b59998f9af66d5e4e895d57c29887f1acde4fd38172fcaacda43ce59f96e3744c03d260cd24902889482f8c4b548e9d6a33303e73c0c8a4e8d21b7dc5ebb8cb8b7199696378d7a567cf61120c4fa5c17a15efe4812e1e8c37a34683f5289e4c83f5266f4783f5279e0f5e0f201aac277942345847de13df45df7d344a76ff1ec62cc3f53033a4fc9c77ce4b7935a9cd89b3cf6f5996655aa6e50832df7dde10db3430775c4fda5e334dd3b26cc39cfca6699a967997f64cbbb41cf3510bd2ddaacd6833b3dea5d372809acc0e441269943cc633eed0a0f6794d45c6dee6bda1dad7f3c2e8e3a3512ac0f4f1b296892b43f551186dda22c34630bd8856d143c980e7e9fc10ebb22ccbb2ece5e117e0beb53670352abcaecbfe621660ecea00cf12384ae7ecee9e945a1fa594dee6da1ff5013b81e729125514a9b5ced994523a6badb576adb5d65a6badf5b5d65a6bade7c880e92bfd583a874c9fc6208b98c76eb73cd18772f0153f4892a31a72bcc20744b24ccb217aa6e5d8a4ace2264e61bbb1519b683ecb32a9e5b0465a8ed177b2b279b74f2d87e8bda3f27a683b8801cf5f3e9a7d1f7b8d295bc7b41cd677eaaf879afbfd19d120f6aaed88f9470e1da83eb7ecb2c7bc7b79ebfde899eb071156be1e3311d695deb4d8f532fb4529c35e7a995096f3f2b0cb4c2c4b8de60641b992fbf4f0314fb3d66566837747a786900e82e3207d37c097a712e3cb6cbb7487cdc7273cc9d798b2fc7aa8595e7e4634a89ddad3537aecd906b1632a8dbaa7526eb237905bbee65e4c1efba6278f754b59a53623b5998c52179621e030fa600f5d7ce8d1a8ce340ac9139228bd1830a543b8b7351c013f81e569830ec8e223cb121979c66fe3bc80fb72cb3759d75430269549c406f23c11324f8984c643e14aae92cadb603d07064c7f53a89683cac60dd658e9596a1399262b6765c94b9768abe99204eed316f201cad1c7a727d3c3e4e8e393d3b2e7fc42fb85d6a541fa1529984c594a974e4999c91cbae497d8424f632220d3e9125fe8392fe0f9becbfc58e823900b324bb26f2f9c9fa941fbeb046cbdfac528c05f6cb03379e7e31547a098b7596411e700d3064f4850a2278a1c91a084911cafe588042596c8588e48508248961f036ed55424ecb6afaf8a551035a42f3918aeeca68fb4a75cd7b65d2b62653bc75ddc7148eab28bbaadbb4eb7cc914e0a6bf2f68db45df4d5907297bdfb7e64177d3f34925743cadb53469b86b27da16934baf7de7b5d2727272760209570de8ef1a9f4530dc9922c69b3597b4822e5ecb338926a48d7a6b89565d9e4ebb2d7b240ec16bb3d36440ee331d946f8c275611856c40a662d867535d75a1b9b9a5b73350cc35fb8aeebbaaeebbabaebbaae11d7087b536f41b8296b593cac7bb45b6c6c6a6e6db9a40bd78569d8e8984985851b615e78b1119661231bccc6c6c66654736b6ecdc5beb0e66a9aa6699a76ac8471241289d485ebbaaeebbaae2b73a3ebbad7647361ae18684bfb557a442525122e8c7e45379dc4a5705f4804cd45c0c8a58e0721b9f469d79e7dbbe665262251c783909c3556012159fb2cb092b30f137522518a8894322291eee8777447f7de3bbaf75e2e685e64d765814ccb62f55505d234d0c28874c25ef6cbe3ee35a9b07019e6aeb0db4e176d27fc0ddf63cc6da2df2bfa68baed3103396ddbf7e374d1f703e597e67bba97442a916e8944225dd22d9148a4eb1a91bed16874efbdf76ac15a6badb5d6c64ec1b888eb0ee35e6b71beddbdb8c1a3f43543c5691491f8acd6ca23ac39578afdc20c2cb1bc4aa4c15aab6a81a11cc6d7d41c4bb60a6a4e162e11e9a3939fa4dce425d351b073bfd7c3a6791be99db7915eedf80b8f390edfed5aca33cedb522ed3d876b563dc2bed877b7cce3b65dd372cfb68bacd880c2cc9f861b7a5685bcab7ef47cab3ef87e9584dcdc958cafd4c8f4729994e4826a51289442261f7dc8691b0bb61984824d39827c218638cb3705dd7755dd77571d7ebbc1c8660ed9c17d6d49cedbaba7befedeec5c9984ec6ed646049cdc945c89a739980abea1986d16a1ef364aee8e9fa0f7beb1710ea83f6cf1548bf4a1f996e3ab909ca53ba8bdee1ae3b8a498565c3b98853ce44ef2eced7f59deec783909c75f8ddafee9b17dec773d5edbcae13fd12bdf32aed87ebdd3de68e2faa8090cc7d1658c9f73b65ffc1fd3ee37ecf71bf1f908c0f248b2e7a783b91178abe75dabbef8776eefb81f2ee327d3d1b2afa6c68d6be0be5f1292634281f6dd0f4fdc4c4a4f451e9a3d2a8341a8d44efee48f4ae341a89eee8ba48a38f44225d41749945582402b2e1ad3b7932b3b3a1572412894422d117ca3ceabcc9fd07fe3d877f3faeebdc894ea2fff872e8003e755aa42c4096649b69ea8d0896b481bd1e93366ed43adbf7764a5febb36fde4cfdaa99ea0b7b9bd73c79753569ec1099de0252bdea715a989441ed2b2b65d0571ecd35735534d5ad5b6fa6ba65d964fabde65e499b80b142e997c935d24d3f3975bfce3dbbbcec9c8747adf1e87e554066d1af7bbf93e83b05d92eba6ea50c7cd1afe373cf441ecdbde89b37733fd1471bc434a219d1afe81a79a1a849bf7d9d3b35a9b0dca4c29267f6ce068b4ef7ddbde8f8c7fd4e4146ef4e13a58cf0881caf01324483f646d13d3570d05290b41c179206edbb2e2c20cbeb9f5a3b494f83d606d50b77e4eb196ed034056e1ffbce8bb3a5933d3bb267a8417b5a515aafea45b64aa2c87612c9761ac9a4a4b51a62a8a651fd816c73b5af1e0f3600ea699f46590f9b8a6cdf468836ea17c4063f8696a3ea9d78d3a0eda06e24f1c5bef2faa7b18832ec6b2f21dbb79049fbaa779206add00770d5415f0b35d86389d06080c34e92adbdf5216cc5d864fa41d007241dd4491a55fd415cb6142dc589e4e99f4662c91e19825c5d521153f8f61227c6a8e4caca31258b44ec354fb7643994320631257baaecc93939db91ab0645158f34787db5791ab4ef23378dba620c914e451c493891ed3412eab8f20c000e70d83b48ac2c12c261f34891ed0360036cbbdb4f9c9d4675b66f28e8f5aaaaaac739bd27b0b533c77eead8cf1bfbce6b2fbac5beb69221a6b51857b6b21fd85e341d438e41d91e8818736fef43a3b4dbf7c8606c5b8c6d5bd9b66f9bb76ddfb6ddb06d2cdba6ddcba33056e63dbd5e756fd07c60b99e61f5fabc7ecae64da7e4cf12fb899344a38a348a66fb09054fb633ac1294e490c5da1c4e9e6c718413bb7aa48fbdfc914032480e35ca5abf32090972288588e49065047686bdaecbe6d8a631824b1677007f5da677f41063fa0d88a948136da84819210b8cd2f5ddc503a9276d782c2730acd20df125c69c744e5ad360eb880d76ec79191b8cf1459b6169b09f837490cee16764a6f1e24bcfcbf437bf97a2b0a35bfad69a7e1fe24b8c894dc8fdfe8e27fcf064eb57b6cfae0912b89a01aec2fa45b5f30aa851d7b15baf82628cbdf5caaa8e64eb3d1a75e5281a75a4513a33b543eb64eb15ce4cd5e82f66aa2a992d96f5aa87061195f174054544653cf8c9d63bcf663c88a81e3c8db2aa85043e1121db90b997b2498e3355de2eeb5a1318b43035ea073668edc8da2f2fac91af679915817ab0b94783339f66e6bef0023d808831dcad99b787336337d7afdaf12a28e28bf59422e0b0c6fde68535635e58b35685bc3aa464a65c5c94684be0ac0e356855a1066d8e597eb50b7a352670666badb5564a4538c015cc97cb75baae6aa753d2a788f58ae7a74ac2182290902c82e40839bc35078985c4480e59720594ad2a875550b62a1c613594ad5fb247a34297207c0cd1280b04c2ba118daa0e24821cc62e4290c3e8054e0e59b28584658204c66436410263b3d6cfd05cab50a3eaadd7a11803447cb16ebd5221a21a1041ebd6337b4d90c058fd7a046008b8da99d6e3dc81e6071f628cac9a58424c42b6be6362975a4b4915e3f91a0888368ab0b9de7a8e362a1d291363e46be42274c8d66fc498ea01e105115faccb1e0d5aaf74a40d084419d6adc718633dac9ac8964a9bc2a018b31363e4ad8c275bf5e386e28b75508b8c6702c517ab5a9f9715812a507cb1ea4d7cb12ed32e50d8afda69d0ba919d6c619e524f261f59ec049237b204b3942859ce9087b962aac76ca9d5e5f7012c6ff8c4968a311086895f686d097ce5f9d5288263661b155a8f41d93af6b02606651660ebf29fad9138b9faf5b0267a360757efd37b8bb68cb4c100c800cf67a01338a7ca9c06eb4117e0500ec93527d71410dd525f7b0080073f3b7cb02ea5f4513f447d8ffa20eaf14c593bab95571359bc8696c0b26b15e1205a24b0285f38310675248726964ec536bd29500a1238344d528ca99fd5e79593c518341423672d6f39362aa3935a9b7699e632482a03aeaadc03882084a83e440d904665af5e71628c0172759b4ed5982d1557857248ad6c86df2dd56bcc9410b18b5c9dfbaa6f5f187f7205c403c2eb010eefc55d597d8bde3a08d328976c5931d7ab57d817adf7aabaf5f568b08a40433d516257c0f2406431e3e433d9a22ae02b6f8394ca1c2b2b65e7b495652f2cbbd6d5341fdefd1ab9a3c1069bf626bf533f664941da5d6bad550ec9b43b368893e9110dd26b4b602ce2e96d8c8831d6e9b3231ad52a907289231a158102c004b636c4b4ad5e4ef3a19332561eb533b54302400b6cbf4a9be972b4a2ecad8ab227ad95555d91c5925cad48a81bca6eb0e8509ed798c01226cf7a14142f94a02c892f284a660a4508c5a2088138c0610c254279dea0135f26f71399c0ade9f0f2e80bfa2532ea694ae0be75daa08d2fb1e27eace69090c4230591801ae47ea40d1214a28c49da02092a2613adb81fee67f40509e80428beccb354eb3b090508e586041423c692186088365084640c2531bc689485220531e4e983fdc497f989f508491bd81551c6fcc49220854b6d0c2537e8c498eaf337288931f41345499eb4fa62e8c4978929d17ae2cbac00980287580ff5b89ef832b725f165723f791eb132f6562fab99eb07c69799690c2f1a9c31967c319434388f04960f6328b9a10c19783125e5e82967dd4e34d503724b6d4625ab008736ce2d361e6d898d0777632670fcb42570a4026b53684c90729045c9066d092be8bc56ea00f3fca403c8d9a4937ab2c2b5be3cc125c71c72bc48a825c72a63b59d68aa07e4f9fd88d639fd8c4b11824c11c2ec3929a5b44e9da993e5ab2fbe884139450895137470294fc0a1d531a3ec496b65d9ad1f3f822cfaf464cb04b64ce0302ed1e1e5185fc25a24db6b4ce0b07ed1d79dc0615ca2cd74beae6f881b21d6d198c061a90765497cf1327dc471793277da12385c4992ed331e1d80a7ebddab27ea45efcbc698edd7af18a3fdf2702882c3ce6c11004e00b20070228ece8b38324d0538bbb6022cea66aaf6cc54f53353bd6511633400647b1c8a340dfbcb0b71f80287dc3bd9bec28127dbe3f0457f38e85801873800657b9d6c4b4338a4b103948a93c64ea364b65a13f8ba032a07d48b3e1a3b9d478367b6d897aa80c395a10c0adc1fcaa10e3d3974e8315981431625216651c2b224c66cb76701438cd16ecff2058b121c705894e49695242b425c96e12cebb24c9465a32c23655929cb4cb2cce680b3accb3251968db28c9465a52c33c9b2932c630287f8c84fa332248da24031e616c05a55a5b3ca96ccd96757ac10533af4e8f0c305d18854323931a11060442989d212a526949e506aa21485d2144a1f631a8a465d33d538b0ce2c922d8d1d3c7580aa1360bb836174608ecc008a312cd95ed3c121d681d1a1dce98ad00a172b4956821ac5ad4cc15be9a24125f1c59eae0cad6861850bb75a3b75e8d1a1a7ce4f014b568674e8f98648d5fcf0ca10661962c939a22d81431d7a3425666c7cd1e18a6803071da9434fa3b8dbebe0d3287c7b1d7e624c16ea90042f05899702145fec3be5a7417b1c74a48d942ca20c7b9ba245b60f7168225b9569e22697f203e327be608f45d37165ee7b83f6293f5ff9f9c208e4185fc417fb87291fca92f8627fbdbc6d21db7321dbfac5116b31c63a37544a2b4a2d4a2da517a518a519a597d2ecda9d39007c84c6ce457f7d65e8f268ec783478b880690e69ec3881431a3b3476421a51644be348b68f31e6393cc010876118c33a0c1361d808c3481856c230130cfbf09106ed714f7cb1bf3c2c4583161f3992edb3ec765ed45480b525707823dbd3d8a10184985a198a2df676250b3dd97ec50a31857156b880b3accb3251968db28c9465a52c33c9b293eb6a4be0b0d4a3e3caa59e06eded0e0d2968eccc54df9e060f8d28681c39c0508c69c9f6190aa529949e2855a11406a531285da1d4bad77c109229ed69d495ed330cbb36798c3353256a9f699aa604be2e5ff269d0bef4135fba977a66aafb6c56ea71010f101463e8ed637696b032946d8c4c3f969cf8620f6a81c395a1ed26bed89e46957c1ab5ddbef41363b2db6fde8c062dfd600030050eb39bccc337f1c58611c81c4f7cb18f5ac8360b4898e06407450b2d30e1312da18952cec9111590864c78f081914cd04fc9c230e10922228bedc55a2c215926b0a0130a1a31410429c05b88d8408413bae9c620822d8b1e9c851496d0b4d8e1b2c0e204d7093b5b16452841c6059096451498147a64a4f073710d44703d4149c6028b659143022b0a45ae2c3a50822ac98f5dc3072a148e5857d0a15ce8545aa060022999d5022501a72db617372168b644db33022620916182c5403dc89179820e9ec2117cadcf144a108020a0410a6da02fc0f80b3406f8248852815772f4910210e41af073f4910213bd041c230dcd0446a94217b8cb6222812d24482db8e04163017339fa706164ee70c1858e501634471f212c7aea10b2134d3f3edad08c9dc2fe4e34a1ccddcdcf2924cb79fde8f4e0bc02fa552eff889f33623b39be4883734696de41901610e105536822470760a0800bc24004212f6e962074cd13549d0e25a856d0a1bfea0a11501a370ae540149d206484dca69b5272f41152c274c12003001638b4b9a38c803dd1c4cb1cbf93cc33cbef442385e4acbdb033d91d813c3bce209d6b22508e5960f007584e2c64d92806b6bb7b04032b519df2052c3fbbbbbbbbdb830196dddd4db120359d9a1318acb1aa39b3a5abaa33535891d9d2395f8ed7d5440289b1c9a443c6e8058d159929ce62455632c7633790657ca9bd015a4bd07989dc938e82acab3181ad5f2d6dd89d1cc13983d8d8dcf1856a20d7f842bb9250c8fd5150c8ede47e761921f7c32b771cd2e1e5f955517a5952ac48ee9ef8d21f15c13d493cd81ab218634c2943d8dddd317224c04670cdd1472827d3243097a38f1007280ab095a3cf1668c026bcc517688084206cd14506d2c25a6c61c4905dc10b5c94e4f6b2c50f725fd37274b6451b323dfdb5b08802153e3dd0c10868b006277041031c5280051a58a0031b542106316707d3a71891517022003d40c3134f709103356011d3d0c4089ab8820d2b50411270d069822f68914bdd1970789cb238a250dfd0413eb9a1901b0a2cc80d8533e436a18185522194e96d900b32bdc55f3ca103991a21d35bd60c4eb0e246a6af6684e10c9bbd7509b451d60c4d7001ae827c904aa657484972932428532700654aa98d5e78810657da4f0b1ce47ec4556829f1a34ccf48ea94cc79acd1ddb485ba25c77e4074cbbcf5c974512401d6557a58196092fdecb68ef5d5d86ca95c725fc614c97e00f78c49252581802290a90a1cc69e9094863cbf03d34ff2f838a124db384dd3250bd9c9217d4d0ccadd56cab2685f627837554969d73eb06a8f920b31a57d4d440cb9de0ca3d7b4f36658889223ce6ca98d135fea2d1fe050fefcc89f0f3153d6f617626caa200803e61a43c4978aa3a489f4555ae5884a0df0d7e7c91d094583f5ed499e068fe8967a15151616179719213844b7543b2d1ee0502ac94a1aac97b55bbe82b3e2799ab3595024099d5c2bd04ed56a9d5249aeaff435c42e55a5239da73625070fe09af968598065b6aca5f1a0d2a82b574d89beac3c42477c306f9b4440c0f2b35bd20d4b15647d51cea64a9629a9c58d1ab27c54c912cc72c6ec4832c918639cd2caf6e4153218f9f9782376b19423e9246f1fcc3c8d98001c8b437b3dcc8c78433b0270401c9af6d16ea98f8d9aaf27754abbbcdbb31bcc6f1e088e437bd61701eaa747679687441c1147cb38b1cf8c7de105228ee95d59f3220e79b3c4b84149b51cd2cbb06753cb711df3ae2981594e9aa9774b85a9f531e654ca265576997480f1a5de145feaab96bd6af7569b3965d84ff79b3a6ebeea5962ccf59a2d1d37631fcc8cf8529f5204981e43a4fc0c6bae624c95e3cdb1071341167fb16555abfec69ad15bd953ac76b69ca2c9de9eb45696bd6a13596449e10096578932466ad263c50a92486215d151430e4d39bc61858e14a6392335cd9826956752e0f0c60477883137a6f7eac68d4e95727618623ece396769ceaad2094e7995374abe5a219b50a4c0b56b95a5215ab72005b580b6a0248771880b3f55f60555e8239da9b843ee108900cff7a58622044c1f3358293d1ddd504a6ba66b3892291aae5882062fa0218a52554318195a020fd4c8b47a7d9819a4fb306431e3aaa9a231c6b2d7d65b16a9c1aa62ab0885fb3052016bc10486c733801c904b88a9d1eb838831a327e42aa7c03d9459f842f7509ac1fa421a51d6ab9107ce967a1b45301b4a0211ac0f63cc7579939d29ed967519513e2c1c1f8d6a9d964044f50a22585f3fcd83e915e8f4174466d75d02a4c1fa2dc7cd93a066599b657196852dabb32c91658d2c8b04447cb1e64f6ca929ed75368e8ca9c6892df5d348b7f408e24f2612c2d348ef541c82b982f999a938edf5c3640d0a1b6020d289181373fd119d8a386a12b91e8c31d5eb7b00d1291fb325d6df1a82b91e20d768841004125feaab7a1f73debab262fa82c8ae1a27b79136d2465a27b7915cdff5ed44a79a48e334184a9d5c0f001ee0ae8d335b6c22aa9f884444541b011263eceba58e11b573727d7d8c31d66b3fd1386da473acb599b5d75acddacd5ace5a6c6df7f85512c63b4b9596f422bd9138408c20d9895d60628c7d556daf921029901951f2a7c16a651280cc54bdfc99afb37a55c6ac0b665627287043e08c1e6c08eb021bc294ccb0aa926c49296beda253d850b75807bbc5ba17d9c2943468cd39b1671886615876cdb8aaad41d6d6a04e4922f4d4212f1a1587600105911a5497609124872cd97a58e1541c619593ad5b70460f36c49255b5d68a0d615d6057c086ac1a54836472f8b0232a210947c8d677983e20a1345e6b671c40e646ec9961235baf11a720e11a8471b0206ca80661433528071b52319940974e55a11ad4a0f51a94adb370435889ba24ab31f2d5b61c69466f0fe1a37a1887aa0f6dc41133c6d0c87d1f7248a766c4a11b4472ef70818943497c4ce094e55b88181344ad2c7b6199a5f180bd58e24416fb5a8c9c753633da54463aa9b5c973399bd7091c56255589b42173f765e5911ebe6910071369d47c4dd23a4ee022f8061be95495c2fa40919b7424c6cc4092bb02f1d4281a3cd260e31ba1dcc73743a4bc9d5876116384861a454daf9567a64e3d240e2b0737393385a20332bc200b4252bca00338fa5867a684c8600ba0280089808a35cc00471fdff4314e3f6a561e8271f08d1630cd21bec90d13638262cca5f5c5524e966373b2d596c0fdc7ea5953aacd5c197b164540b8d565babeeb420aca936dcec93657575b025baf9a0e59e4a456af5a8eeb99091298ceabaabe79c3836f555595a3de04097cc22ef3494795b16f0ec9a2aa2ad5dfcc166b24c660328836b2cf06f18988bad77e3d6c2237d37ef220a6e417427b93ad10b2956da022db9f9e37f1c50738ec219985d8627f3d698528a91065d8639e1c8a2fb6a75becad953dd8a7e5006b2fc0610f2599569216d29ac0149b4332c58931b127c66040d39b3dd3a7c12993746a26d1d2083135397063c60c17171616959e79135f6cfd668eb5031cca9e21111470d83f758a46a492c9890965d60a49b7354102a39800dbc7ac11c9d167094e64691b89cf129264297d3ad543b3c5f6c8245ddf43d3ce2fc4d57ba27be585710cd906a179ca38e756ed5853744bba1dddde3a1263bc1cb248de883c23668bfd08c86cb1b73c308bbc385baaef4433ab2f0c22d70f650a2c7a288bdc74fdbced2c4b645923cb225956c9b24c2cebc4b24c9625ba27dacc95ed3322d735895cf3660e69548d31d94c02c81035291c77e238158e83c17131386e85e3ce711ec76d5a0fcd942c3265912ce5a40223c6cabd9b618cf16da5e3cad5b499e9467952811163e5de7752811163e5de67724d664e1299488373c8c49937277767472269d04a2498cf4f8cb9b1d5e78d44229364fb21a6b54aae8aa6c1d0b4189ab6a269d7344fd33e4dbb41d36ea60223c6cabdcfba37683e6096e9565e1891d8346803a427a8040c586a8b5a324524100000007315002028140c078422b17038cf024dd4011480108da44e6046950aa324865114e50c228c1062800c8008008c90066910008fed8ca9d50a679e83796a94c3b9986ab4bdbc3a5c50238467cd5ca33de5d5f1821a23392fa4f102e5f564811b217c136d8d0bd4af070bdc88c0ac987fc444b3994b0ad187d42b484aca4421a40bfe88b9127c45a8b5c81a6814696e31f20608ac22bf88a406095c317a29b28d504c45d5f2c81aaf4045e9cb486a4ca215cf47a051c11cc8fd88e4a288ce821b505fe37f27414777934dc27aa78d895776eeb210e9860856316b39d20d2b60c5be26b206695411b78874631456a1d667bce0cb0d7635b455da506a96262bf175ca9984671c6f793d37e5e513b31a1a2ef9c1c1871490434fcb78dc6ce41bc94623a35902b72db52b829a356c94e2b9d9ead719a14c8050c3867627b29dbf717664e63ef8bdff76fe9614668361b9efca8fbef1dc165b0e22fd5ef90661747e8410ec9ba50a03bad9497c163d3a9cb7379c75ae0387b450f438049e3dc80ca6c90684fa16d82fd183b535d195eb25524b86ef48dd2d92b095f57f5af4153c6348ddaf49685e1b3335eeba05f262a805f968b2693f2479d7e88a5b2e2778c1956a6317b402a9b2ab19171a54c54333d5b553369ffd92a5d2a8649c0acc4a7617b76683158240ce8ecbc9ea5ad12975cecd077dad2271153b2b5f677a33188b25dc7e7c89752f83b3e1973968b00ae5828f23d2e8a103d15bc6601ba9e42fd6439da32ea17d52ccc9cf751716e1571c0866c709dc98e716b02f83f2fffdbb6c759f5ad40cd70fcc9386a2154d78e5ebb1f080187c3c544f629c9c6e0bed321446eb4626454130b39cb1228353a01048f55c9f7db97c96cff9d222a23e4f3098389083644cde1f889a333f0b0a4cde2a3d39155b9ae6bf59b1559ac4a3fb6272e513902edaf2ef68471372f958bd91bc98306e1d24fef1ab9a9d03c6dc3b4df4e46bc3b0eb542254a67f19e3f2098a43aed14cbbff88ce2af0fdd82283d88d0b0e7e97bb0858f052a8ae70821cc89cda4e243d504da8fc49c5dc5af9bebd7f54606f933d4653ca31989521b320714b0599f36161ebbaceb930c4931cb8d901768d1f4b078eb262640bb28dd25159f307107c03906750dcfa31ab3ff801a7cae0a47ec9d9ae03cbd95e27b3a2b2923042606a0fdda58c3fcdff68e5b6fe38614ac77c9a0e2a56e98825c04dcde69c30b9115dec0163d3dcf2d7551fa9d14be88f09d7a8e00fc1b128bea50bb8d277c261066a0ee31346ccb422a438ba997d8ba8c40cd92e4828717c504af366cb6c5467fb7028a9764b55961bc1248672c901174722c4d63b910a96cd691209e22fa105daee335c1e5f2e8ce886020fe5f12cbb552ea3b75fe1fef21921f36482fd1a0a52a1dbc0e18853baf936394cca357694f979b9f9b5465895e5d614e9236f87c0052106783e5b61a466154a1605049b3bbc6e16476a702202136b02f6e017fae84c5241a4edc21bed1ce7d1cdb1bb55c7f4297c321280323edf8f5ccb999594cd56a633614fa052ddfaa4f79095cdb0a9689080e35bc44889cab1f1cb94d702f76087a1afbf0bc390fdc032538aa8a9ec3bb2b4952d696bd930632eed5d5817a16fc82da426ce6421a35b956bda70aa3ece222d6844b4860971b0df965d58d20708f3d848a7dc19c7fde931617db535732cd6eaa4136983e7b87129225cdf59dcf40b732862afc06f60e28016f4218e9b11904a8d103b04e41990566384592cbe57ebc51ac46d327abf41d88078159313e423f2008464128d71d9103b51782032c05f5eff44808241c18eee3bae4bc66ff003e6beab3852a856ba26c4b7f96e2ff1643fd22b2bd26dca08cbf95ff4e76fd2f9bd6c8672df64c3d4d9d9d44d3a1f12a6404abba8086d3180e29248b6bffb7c95f1a283fd2e27717690d2867a3b35a036bcde3db900f632b9634145d24bb53239d050af59f30a5bd630cf854c1aacc58e35e46e13af5aad2177629b4ed6c7c0a5c4c034599fbd149e6c4e50a7b96c7f1785695904c3658e5b6dd029f952e124ec067693287b8693c4032aff61ed927bcf313a29f17bb4e201e177d404f2b6c45d7e15380dba894eb4144a1267c826b28eaaff8061badb1f0942fe5b04716d5873d997192be15d6efe5ede9d5f41aa2a0d96803a3955cf657c8a6bc3d611b83960267dcf0295446618a45c795c3557b195c171a7749d8ed3650e0f25d0eec7e6e849d64849f33659671041ccddc68444e25d432032fb883c13d14f4b7e06fbbe0ac762d14069ee318be154359a57009105729b31abaaa48bffc74862bf5c20734e609d204a4f754cbf26b1a7818ca692c7945dbd492e72b9c0142e5daa57476c2695db1f3ebb81f44b046a1cdb93fbcdb06f1f48e991aee32c48618152c2fa815c874e157d9f664274f20e18f212cca0b48b325adb5e5773690c2bf80e620d366ba6a95a5602f490feb9883a214d9639ecc9222b0b2477ec4ff72555040de756e32c4dd891f79da1ba92fa55609119890b4c7b8ddcf29ae230fa75157d3ddb298eca15318268e6d077d6b4d434518d5ccc41c377838959023c1a6be0d4334862fc38f669174db6468f4fc38c9f2571316bc0b9569add6f6cb2e7a6b816a5bb06ec922047b34f3a1fe46c72b65cebec721c1d8f2034fdef89a88dac8f9bb561ddab2ba915390f6ed3df6c505a7239c9975e91d15aef868a56dd6480ec029f0e12af3b5c37862674fb4de0fa00446c2f701dd4ef006c42ef02132b11a80ad2c39b527463a097cb5f14b745efc7821e000a1dd1a8a0c2cab67b54705119776ded9c797267a193dc6f6507163b09db5589b0e5365ae996e8dce3dea933fbf1e4aec53cf3c7e3628ae9c1220606047a91ee8c3e8bff3383a72003ec2c309a08c2c32526d6e4bfada3206fa13e9b80e7dc4a206463ab0236abb60e8787c0e64355b53b8fb6514024c9c0d9ef4197e8762c40a82a567c5fb462836653933e369a7144a03f38b6fdda30b1be872d3021c6c6971e4e6973fcae4069ce2cd8ba7424472c5137ed588bec4be2f0ad49704191e2193079e8011350a1d260966f23d8fb7e57f6982fa7eb948cf2ddf40473eeb72b8b4d674f9311ec84a33673210034c285d37cffbfa3798020b339eb64b29147c04c282a7527b170d456eb0dae89beeaab4939f6a511bd608490e13d2f8d3c61315bc8761b93cce4a50605066f08ccc8c2c6f21943dce5fbcf547a522edd648cac02fdfa59182099040297a415a2de9961eac45d7454643cd36ff01353f62e215255113253be8f518624fe9a4c81750f55d7a95f6fd7646ec838d28f39d7136ba6417a878f1f7e940d8dc21d375d9901f400155bb9763f9555cd10ae929b81fdaff0a28c519032b7e4c6042657b8ab46e2c004deff0d3cdcc4af06dce1eccad45317f63e2ef991735a323aceda6858eddf0ad69c37cf976990dfc10f015bdeaf5504cba96aadae325912bc4a84f1609c97b412495ddcc8b65c6217c810cacc147cf41cb8900992f972fe709e25571dde0e4cf26f177c75429d334ffc79e2dae21154e1761537e5311014316687b133649ac94da484a1f8f3a023846d3ed185fef8736f4fc4d418d23c0d52e4327813618506e19ac254d52839cca9e02f915a54e4299552c70135e4971350a0b1f5d3fe140d3cc5d209922f94d418b880d7cf2a6172576fd47aa569304df353ebbf848b920a8f0cf8a2023917027817c491131e6b283acb9e7f5cd02da18935ab5e8a8a37bef98e3987f053f5a9f3c134c00c6d660d04072e97be4a3872e1d038abbfc11ac1c3e74e0230aefa39deb827de710d0d1fbbb01c007c72efdfc59a5b300716ae4d7e50d1198d6c7fbce737ea4c85e1c553a9285d351cdb8d2875c3076d25d0acf814f27b0ddd2ab4b39e997615db5861ffba9faf4b6b8af83c4c5b1d9c4e8a31a2c826239a53c9a3a165707dbeb60096e931c1cbf8998ef36f1c8f6288d7dfec300462e283cfe7f3cba51bd0bac7abd07d19a06c9b452f7a8f2c3039d73581721717385f35398465c3506e65d832cfb9cbca9723b534c8cf4efa5796908c97adbcfa33951a92d7a9acffa090eaedc6b8e677cf91830f7afa67f966d77b18c084521d70f57eda50754d5ddda3cb30133e8b8c4c04af284b84d92e81532198760abdc925e55ad2ca535d121481c30c22f0aed61acc14e825e7ea378c0ce45a078d6210299a7d23e71e0db0cc35e298d06e9e75c28d6771b84e7f76254593af4935c3ace9742ea5a7ac7dcc2e5d21840c07703e8dd4a8817ff857bba7d6a4857c202b458f814dbb6a43cf629c11b7503f3e43b12c6f1193d81424e6d4d44a66b84a34e59f94b92d75eedbcf811053b4bab3b0c0f6d7880cb19e1476d4bf17009b7a0b9c7c83b7cd529b8df31d9bebc36617542da4610a5eab5d2a2548d9b79ec7099a6eb07b549300ef3aaf25d57300d896d4f2975bba6f8b5babca5bedbdd42becd0bb66bd64f41d8b292f2bbbed96715cff011e15c1e0d5fdcc2982d23272e3c50de3a1d83efef20867b820edcf5a7d4a1522d0d806bbe5a32421d8692cbfd6f8552abe41d8f9a0039c45cbc7540121c4529808c9497f6ad983386b3d79bf9486f08584744f092dc2919f386463a1f61b328a09de843adf8638818922d4e5aa0ea3a1eb34903d3524ce10fb8286df383a8d8c38e86eb793f0cc7e759a6b0acf1cd9a92ba95a518db6f64aa91a12fb0da213d178cae6b278f8d51b58083c35f5c488b9edb80e8592065c4477ab9002524b33412e60546a9d9bf1014e41ac3804dd2546edc691bd5aa2cb7d1d39d5fd85fd903194cd9bfb0d9b9171f51c899b0cc62056bee48f8dab276d642befe158da23c9ab00b1890b6e719addd82476a1b2afae70e3b940a72b15a27e8985d3cad648e4ea7b9705ec6cd84bf60a2d28a1c751e1f454d17e702fecb3f6740ca019f7449cf91dc6ccbc6b98e2bcd662b60df8083e66991dea34a31161fcb408dafb8357dce44b423d15679f210dce1cd4fde3e1aeed1676a93bb53a16a8b6a4dd4a9b632a000e10a6083b2973c3120ec9c2d55d9f8d4095b2f3727356d9bffb40e46a93285f23ea21c81428774d656b53281bbebfcbb4df987ddee9428a902905c18543f597aa14c492e6828fcab0f8ae7e8d1e122e7a8ce81d8d92c38df04f066339a68080e3f2f95e590e496e61275db59f56ff1e4b01418b375b102274fd795aef11ed5ba0584c1284b60890701b97453adf565c0b8865cc39f81406e222e12c0dae7a855957fb247be9f3faad6c78a69d3d75d5174547eab16f8f68850e48fc52bee0106b65d61d85bba93085d71f63dbd3d6eeec2e9ca4fedbb8628366924afe965e83813853b03945f0398394b8a503331524471c1811c12045cbd28122ddfcce14ac3aaca07f43b3cf806721361edd6d244d179a8dd7483a4b08e6343bd2673b409e0116815e143d5bf85af8d243b4303d9fc13794bb6645ac5184ffeaede1741c885f525ab70eac22c404dc64bb5dd1c3a7b4a62822bf44a2d192c62826f44d85bd593e8fdc097ec83f6740543d4c157ccd0d160e1208769deed6c5494ae3fbd067317c5148564164435226538e6c0357277cef3e8883ea8a196c30b1646831df7d787a5eb9c67512e41991acbbe6984a46784f6b6b54b4195b541f430473412345beb46f9dae3c789a42dee904115cff6af229a9e488a973e7d54d210c0297232cb0be9e46cb4afd38009e1a79b513a7b406a97679a5acdd18a18b6e2e2ef55607fdaff55c7c7499fe4a381c7008343e486e4f0cf32573277ac0c40e8a1ffac36c6df073c2380c5b128857edf573842face340ddfbb04e25311f017e4c73aabb5e9c8ba46ed19f6bc763f3a9bb7d65402235161ecada045cb44bee807510838d922c3ad6431f6e13953bb92e7ad4215cb16dfa846ec798989e293f7054cb10c7af77f941b3761e145074e4b088bc674ba6236a3727b2dd7182b8abfa4d6c50a53fb28ac9e27aaf3af103c2fc9b7bbf776748177accbf0e5032f90b5124e8b7fab58fd28c2a9403ca2e19390c85300bdaf46d24921109b6a8421bba3aa77c1ffdb2c491478025c07e70ece75465a70049431928a3418e4f361abb15085b1d4f8d5a5f4cd0684fd1bed7e040035fd81db83e7343af293fd474b8b2e0f5a0a4a5928ac0d5af0207d79844b771e6366e404e6f63dc624afa7ac2d400d5382a63bb12f84d0d7909ea2c059d9c129cc239c70ef28aa9e20085d40835a9de816fa0f28ea7444eaf8a36c1601818c2cce707be211aa4577a1128435d764f55bcec9b74cf15cbb3c59546932ef9b9125b08966d652c996ee2a8f30ff715e9d262936f347d574c979ebcbf61e9e2d201bfa09472ab45cc3d82ccb6e1f8c9b182c3678bb43dacba773c002fb47cc2411a3af2b3bf0e8cb53a25e036ba8c655c9ebebd172499b4a381c0f21815440c3ee25fb4105b3e998cf88857ba682e4e10c97f69d85ca025c264e94c9d8b40e32c8c0bef80b872c26c473a2061ac3bcc3d5c34420b5312a6f6730bf95cfad68f56d3d8f6f251376def2ae14104152d7d5d0b1cc823e29e2135f552b7775bb03a6d79f5d77bf59f51cfd503e1d4303a129eac5b4865d6ce8c05c276612d199a098c29db91eb84db9e03b78252e3973bbb587575dd07904e8834b5b6c34eb6cb2ced8a78e73640d132595e799632c83c77e620add0bbb1f22763f1d31fb9f213b1e1426138ce29acb07a7f64619280bcfcd03559273e1e978ab6a33b75f0ab3e74f735962aed6e81721d8299f5f568b035f41140411fba17be167dc9fd6f41b510fbfe81866e00d094b08caaf74061a9e28095ba7e6a71b6abea43a4f5bac8b5eaeb1cc6e6f3c46f2d935b7692c35abd13d028234a7512a4eefc4df5d5da72f2e68c2747a93ebed2277008ea468966f069bd90ae4f87efdc5a94c6b4a2f44ef52c71b5c8eac348c57bab350a69b96297ec0e1ee8f2a16b2e0278716fae288edb51111a1f7c1c2e54bd62de726b422adda025f251ebc3702b5d6a9cfa6000cd4a82ed0b9ce11b5c775d2b317efd129241edcaad6308ea4a2672d4e4723962afe8c6221ab01afd4bc4625674f1a5a137ca7217dcff5bc90e218ca04d0a58c77819e01c74634151d433d959acf7964cac83481bbc8f96c1c2eaa0bc0f5a98b1d1e5e35a139bae6920f6dc577f3287a9cca7b688818f5ec304543f99331cc876a0cb389072640b8f52809e9245ff4b30cd6750171ed8234cb5989c4e5925755825e02f0153e0e34a9423cd79fdcf339856896cc5319c1da0d9245e317df878959a8375151a019d7d9753b5b83771dade7c7a42e1f6ac1a4eca00dce6e4f73c6fba1e4671591af67b68c454a1741a114e2ee568d5b70b6b3f48acc18f40120c0043f5b00134a8c29019dce6cd614d83d20b7bdb6a46b628ff8bfdb6f93a794404cc21e53df3107e1d131b6e20ae72191f7cf9d22d660c77ed8a386fc2ec4e90e5bf9308e40433e3e8058b14d50d9bba6b9ae796db96180fcd05612583eb34432a50858171e403cad1ee96fc9595c6e27474145102b06cadf1dc09cbf066372e2697bf127b922b8f06d6634dd7cdd5ba31af1c2ec3e2faf8600faec10eedbbd65810e28c1d13774160ef4679a4ecedc346c13d118a45edca5ea0ccb73efd0a6e4443337b95c12988cc1e5a3fd6d3752865105db1ee28f72361d7eacaf9b5ae861a180c741320b2d13d9ad8a86b52e34bc07ff73741cde633b3aea0906ea02473e0f47a06d7d84dd2646cf3f5e376a59b9379de1d4d4e7e1475beed24f2a663414db6148cd468da5a622b0b2f9866b69d9fd868e8940611b4c0a2558a4598250e8a8ad52453df728ffa23cb7bc3a492951fd6f4cf3e1df479b5354a94e44ce12030607b50487289df787ee229a00c222cc00dff3add2405b3de149c313b7101df2db846b48f90a6405fc90fad9220c2ec0411457b83e2d017cbf4f75a14a1fbd3952164d7f100c613a29b6252f4517f18514e1bdc6ac21458e75d49b3aadb924a48c6f1708864d0e308029561f92c04c2a78d4f74c9f3583e727bedb5c9302071e795b9d9d2d9b212e2c3ac9eba1c488428e1a3980fb46cf326cc39f200ca5d84da48503bf438b30159536d34ebf6459c79071de5f9db066b925e42c0b373a1273b6217805907e442898c98630656c35dda2a01fa0088d158caa092a7aa287cf5eb051b8acd7d0ac9e829469221a4b35a0ee7b6c482c9b57005dd54d2b5a5999d94a317189adea37fec8b5bd8a5c9d164d3a8aa22e8adab1a862ea245709c8bcab4ed1c2cf41b61834a288165d7a1cc72661b2d53f09ae0eb2a6dedfdff40632a7bd7d37246457f098023d5c5497616b5bd91fd597047bc3b7bf94ba3cad2ee12b0297a2aa88e6c31e4fc0be89548eb25a12d88bf4d087a7e9f61a57459f1515ee30e621d678b19563e1114e2503e6a707bcb284f2304ef688fdc828f4f2d91ee827701b28454e8afc24197677795f387c8f73c3a56f28e41ea9781ba6f02a332e1a276c33c0b58451412679bffe45a83a76634dfbc5796081985ed9c8eb63514f79b6019954f1e1742ead43b0a790284a8625cb3c6db21a9a348bdcaff61292e22bd6a84ac2f9b0be9eda276fdbda632c7abb489517ca12f31b4261fcf62234493a39807a88eea678f494f98bcba455635eab6ad19c272bc76ee5754701c6107ae2b1279a3cae55d5985e48655f6f43ca1a2b95434932217767024b5099a8abe34bc321056a22171fc79d1bfeebd01931b29490e557ded2c5d361960743b3c8531688468f3a01bbf70382d70b2e693d0ea9ecb62bdde77326351a7273cbb9731699e7e3f4fc3474fe7a6a76fb0ea2eaa914a7dca8e9b8fb7b31d817bfc12dce865e0a416941ee72812f5dd3eb7702d28a5ff40553b604515fd46f84fab546f966abd233b8bf5de9d31cac052ed77b26295757cf63b983a2e02a3369a1bc948e2ed80d7722fc8dd5829e9ce74af6101a3f1feac167e5d37920e02305c1ab435b0322a700784aedc27131a353621392891dc4912e820d1b4eb8ac690dbcd239adeeddc96677ef161892809da479294302aca12256e361ff9eef3f858ab3af147fc7f09a67d0d01815d241b392987563f3a6c2a2d156fb71c59d5e878ef824c2cd47e53369610f4fdb04c1edfe2ccdc0c1f1827ff5793a3f8f679820e569d4f2fa42e87c625b5f4aa6d1a4426ea8115e42036fa121531d2f1c43461ffac93e715d4876592a59ae3ed60571f52af5568f53f2ac4da90f31e42aeaab4839f9a8260597b37ec3c88b915b772419200b786e689f5ae9e9914aa77f226622ae2b289995f3d173a15ddc599ada13b6b32a72fcc416a826e32616c24201e70ee82bb0766382328ebaaed82545d07f7a8771edc9049a082d55024196c65bb8d4e6e5c6811bee260cbe931d95c5682203f20ecdb5b9223b86c6aa28bcaf06551475ea685967ff508c46997b885a0cf70361c486e92bc786e591476d1969b37966f03e53127c16fce13749e224ba8dc4dca90f16d0143efd4a6077d1cec8737abcc9cf3cca6bea45c9f52ea65164198cf77bc872763b58a646a09e8989a672db90e5c168f8c6cd204d999b36208b84ea4d695655190c0a3e295e33a805e774012e512dffcce3838d1f5471c24f0ce92571c8cc6e57f02d628466aec0ee3a31b74a7026153b7d95cd23f00433a0f178e214af978b80eea06da5a6a47449a32e2e126be2bc51deb406756cae9dd9becf078a0e9118ef611d0efed777fed694f6b74656fb169e76915ee032263363784b0692266c8be4d6b486dd95d6d2040ef8050d98626624d4be797aff0804eee9a67ffc22794f4de542d8d6616a06e6a2f1d6599876a6d951c8c574527d4ddcfe30af32edaa16816c1ff1942166729da2c02e358b8bde5cf0739332c89a12b9f8a455b34866ff5ad2d0674b43903a342ba3967362e2a18134d348b2618d8e9778fb04e39b1d214a6a2a0070855439f14330410c88880b501a9db7b995ca4606528b99b63c5f174e5da3ca28663361cee38d19df0b0e128981abd21f59e138c2c8bf922abd9de58f2f11630ddc819e81561eab4389e88480adf36259f3f812d3fae7a2934f25f9a933fdea5d96a9238aae2b0f8fd4c39e5ae6ca32d97f199a0b32eac2d038aae54cde3f14dc8ea8f8ab24ad65fe4bdb8b50ee928a04146b7c3531883468836a3360ed2bdd4f0ccfe46f9747b511a4093c770d0f83afcf42b2e0b7d67c01394358b9730602c152417b382633c5695e7e5708ee223f678f444625b53fbcc52271863d4461e4324bf5bff6f23c407a1cacd5d162e729c314b53a1f7f3e4040a6c5d87ccd479440983b089f2b9c87c3f1a62764f8800b4038bfb774183019ffb11fed17881a2988b117efc074c134f850f634450cc6b757d592c5dbb0d6631273e842ab8c40641589d418399cad9918eba94e12db13a1d140830186ac298b02e82d9ed76222b4a3c2c5f460b1cde70049d53df015eb6173cf6e2c8f370916fe7ed9be1ae5c9b236cefa3fd0a54e8419b83081508de00de0591bd5b1f5af4d9b156cb53d40ad7e6da22d939e9e7521e552b04b1b4d53bc158d49ea1725e70a08d5686ab41485efbc3a11bb953499dec42c27106fede58dfab855ab950a6d1b036459b85768d2cf8522d19e37d14581e7eae212b6b7bdb234cb81ead1e517410094f5cba5bc1749ed10e1e69c6bcc79b8f3c68c64765e3b024a39debd4b7af003a92598103b8448d39367699b3f1df6ecdcd0c32966622beb8ba60b92d3212967736a255e50d0eede9fdd9425d9986a36f830be9f9393678882b7f93fd871e546b2a2f509e7bf856781baf0b8346bd9a8cb7d5791b3fdd1d394e48a1312b00d783498cd6f15b4ab5181479102aa9ec3262198603356d0162afba85817186d66b9fa4783d2e726df5213c2aa8f20884dd5a8cf17086c99f9a0f04c87eeafa7c53cf33606d5e45ede3a549f3a454d75799e3fff071fda76c09c4b7662e1a6433a533c1ca800122f6fd1bc494721cd1adc042dbfb9c43f17e3b69fee1da17cb22d684463bb8971db144c37560a1bc8cf6f8de35122bac384d08c21b343f806fd9c0cdb32936f491cbc17ea1bbfa489430f1798c5c521aa76b2002af136e2428206ed8a34539cc916f537c57760de15580d1d985c730a78968200559612bf00a3b42acfce3749196aaf7d1da48fd506eb3ba7d15bb442ce45738ee0aecc61ca5b0e52dfb684df95f20d0c3c1440a81df393c5c9a87b03a4bb99194ff8d060b30b1cf82aa5bf793ac2a1f0c5cd81463e64d2983e45560fa4b448fc25a9a3420aca48545e71b493ec0711bafca2faaf23546ed148c25468e928cc9d372798e97a2989545f3b6fcecbd58f7f9196d23c8de420ef715f0fa65075bd7c2f2a65d3911331d55918da2bc68d308e456952248d0d43f2f97a3ef716f3c5b9ab0e3ee463b0cff157a3a1bb3967ac887fa5a94723be3570c3e63f675966c0282d5185a3e6376c2e80c07ffe017823f47a347420112c07da3fc0983aa4b5bf46b324399959f00cda400433de55265c7d08b2728f2e8e5ce5030f35dc18f8bf596b260e05f1292805e9429495feae50cc30d43f08251191b8aa0adb0cb04b516b52451aab02889260a42479af1e582bd105e833867490740e3f3ff814a1176837da9fff19f80dea8f48624b119559086b8ff1251b110de3c05c5bf2658dcc857997a6fa7512893ca9f9e38e765ab1139ad22410fa90ec82d178d6da5af32a744c93998c9ba132d6e2c3b5c45e33513e9ad9f9f1c173893a1a7f50b3e654b7c39092078e78f869ef7bfaf3941f683da74ce97a83af5ea20704a9cad0f786b35cc109239727372ddcd19ce87b1fe8b83352ae066bbe99b6d708cdd43ca8f8884bd97b10391e3dc66a07dffbd5a0e4c9c5570a78af7a0b85c91cebe4c880e9f735c5e140242cafd7176ebf69fc5ea10b8abf60ca6a317dc82374c9105ba69227fea497739572f6649deb7bc5b5a1c57d8dec4d360cba77cec528c1033030eb351c4a29b69e5e73045af06ecd8cef80669d405e524d4065cd35f59b143773fcaa23a4af2f8f92b1e5fa299781376121d2e9433225fe3379659577d707b246b5c1be4a055cf39a6a8131539368390f0613ba167c5fef1186005c133d14dcaa074cdd472008e1c6116aa8b3f35c16e2da19ed17ea71ec5a290ccbfdbc864bf3cc8a86d6cb733b707c27f16574c5a1750f6a0872dc7192199b49f60ab030feef6c8bf00302bb32d773d7fd09a9d27647ebb77e17e23b41fc427726ca0bcb3b4ee7600582b9faf65d649e9e64e1e8d52e95230b9e8bcd269ab321e9bcabf26f297d1856abb5ba90637d64fe6bed32a0bf32c43d091e8868134af56ca64e4573923ab321361ee11ebd3687206a85e2a93585744b1634ae7aefaab55ca37a1f3f866ae03136aa8bf39eb4c1b27f9f0d68149468a5b6d6145eece24bdcf5861f0b73b739447c1ea4148179a4158f70ed837682e660f49697c657d527d59c1a52a5c364226a283f86d118a6923b002b9f4db9ec9f7ea817044478a1bae33873c5da93e8eeb85a0c86bcbfce11026772788cac7077502836bbc2a77a3d3517334436de0ece702f382650ee228bb68ae8c50fcf385b3b968eccfd447dd16aa98161716b2db7a099d8c9ce37a9b7b22e78c80d6d773b734a81915a7e286a8f9a31b3882381756a630c0c72c3b09caa7c8f9d1bc020192b09527560318de4a059c2d9dda9e9e51ec1eec1320eab1c1c95f738f3dd2f03d50f74807bda0e88256d44084e9bc7d7561d4759729467fc95739c4688b098fe1fc42158cc6df2e2a3c23cbe2ae8dcf10aedb6aa131d4f8fe495eaa4a1e40ffe03fec6da27b347a464bdf42ff30dce7ea15004a8dffa5b636d0d21f4e34802a9bb435df3080096bf0d55b8a39e97699f16257d2a6a77496fab24c891c61d2de8be773b7551c0a1e8964b3a012d2bb30ae4e38ed9406248c9d19e521813fc91753d94b2b511ddd44b841b204496c4fe0002eb06895cadffffedaaa9ccb02e92b834c4be4b2b97f37548bcfe99b46269fb913bbf33630c57a35daeaa6a785b92b93740731d3cdbd7c33e8fe8b68c39ef5deb3bb1789a2f1bd2fc0be4595e19f5b9e203a98b3a676e99847325d861d1608538c201a621a6c03b54889da9111e0d41280281f90b76460b9d67b328a3421199ddea7f92b2a3722e82d66810c45449f6c9257e4b98e6a4a12521e011b3282298110dc5b82d2cd65db6219e24ea3f78d5f57b5a41b8e17680e43f703dc441a0d8765fb0fcd8623e745d03ffd495f724fb6b356b8b6e05060446fc80eaf810c0ee2e0657c18886dbd9f43a62fc35a9f212ac33983be187dddb34fd3464b4163e7be5ee277fc63914956767c249ec6fa3ae0100853937c34af14a39531de329302a9de6eb3ae2860ddc237586c6d8c9cc129de98b1c3bc9c1edd5b4429600319e0c1c7e5604f6c847cdc3d9f939e63b33165d5241813f10493850c644e976125fa81d5fa7524039e8d64e6c8be392957b8da8b8b4410013653fb287a96c1d59b42a0a4e55eef14a8fe808c6fe9e10337f013b35df41a46afc76cd5bf4fca7ba91ec643e1cffeea4bb23e1347d55d30e5e78eb959cf6c639a789ca8769a9aa905ecad6e9e2f204b1dc002b9ff5882ac3e01838640b9baafa140025b9e44ecda825bbf65136769e191477d15a163288bf49f1146e613bf36036b716ff49bffcd13d53b84ea1032c8009918193ef17707668e4fd62d660cb71336ffbe195f61ebae640345ff2771117b44dd0b93d733a14de613426781db2bc42c17ccc4f169cd72d9f4ae2e59a64020c1630c780186518a2fff721d1f4370bf1ba459c307ddc31eb34415fe88c431b1360c3221f4f729680ff7de6d393ec53bfe77290f0f0215e2a5e8eb9f7d5a1ca85e2bffdded0e87670e7d5131874488351f1858589bd8ff8b877815d396095e28f2ec5b6b93ad3e6f916f0d3c3946f9496d9290ef300038522cf5bccdbfab03a49d68053a75a0b5be70fbfc8e36c1f06c7786765abc0a341385eb3e35b1404170d36b6791cd73d2cda9f7b229a34c6ca092bf95faf2574a0044820d75ba0a65f21580e1bc803ceecddf99d013c85791f1e3c34fe0bd65a2b3210c9456385543f61635f7a9b22a90bda4d91720eec302ba738051e252dafe21569fe296880ac36730f728345ded9c44ab39b5eacdddc5a7f19d123055c4ad8351bfb54bafb34b56cf85128cfa2b1872a06bdb4e911fbb5fbaa7f6feb4a4570cdcb748187dd28d8dc579a38ac2af4d2678cbfcc9140a9c2a20f5b8cc95a1ee7017c5999a0b7ef9a4bde833f57134750eb7b160de70a11121a91bc2ec4beaf78312763a39edabe01685f6144f02b30f2ef4d5a8be67e4a2bb024e9910db4173875912af09569dbb563a79644831fb20b8a72f1afd57cabe1df49825f7aa730abf157e2e7fc8bcaefdd6e04800a22d0d776e4c89237605edee335233e9c51cdb42182551f9e8a81d5312d84710d031c77d61780e76429509cf1a55eaff100c47b9a4805c7faf48b7cc938f94600aa874d89f2c0ed0c98a6f69cada3f2629604459a6d32e5bc121522dbefdafedc36a3e155e01eb1890bbd19a9896af8475a97cb0a2f3482619d74c983f8c42500eac8c2ca43f9bec860cb7c11db41cb8b2f133c55b76ac54cbaaf07905ab94d61a417f6d1d9377fe2528068a3cdce41925fda5962c719a3f010f65b2171ecc925924e94c07cb0e28e0a557085155612ab12f2e5c530d8eba0a8442e40f2cc316428be4b3cf6496ca649436daf38d51438a2d7bf53b0dd87b2e57e3e4e994dcde854576ddf86da31e15ce71c35813ef16781fd9b3c2b1c4a72a043aa359fb565fd0b874d2cc61e3a892a29b3f636d0e981f54c3ca736650d42782e1a53c533ac0b9d3f11d3a8b0de30156e9dc8b25c44e4fa0cf67b042f364ab09579325a67fda7b6c88a54264b00ea7b97237ad2476a066e55212686ca3d4491cc4d991bbb6ca4df1b176ce0bc59cc2922d27d6372414db9ce8e1b1bf6360d1da8223a1b07c0d0e95895fb39d61c340d6f247f3b62c9169ce4c09c40df642c94258b8cc55ba144328887e233614ffffd5345ff249d0378ffcdac85801a2e84ff089c42d8ec540f5f0be7d5fdc6afc7b1085321f288e390cfc644347188702b05de34f54f5a5668f607951c05b599b53393fee986184da900f950ae8c7b3101d825c0a28cc667dafca45b7c68aca1a9094b30d54831ee043d879faa77b43dff664b0d7b390faa1f6dff028e9e490b5eb61a84f38c3d3f7d3ad6158e5d5ff5a0c7236296a6c38d80a33601e35338fc6ed5e62ab13d31246b54db228735e945ace98529e1942e240e827baea9603b2e61ec7e3ee9a87d3e2069cc6ceb8004d1906d7a96e6fecb60b0143a2637c4387babceb1f82aa0ae86cedb60f8a5aa009fde67e1c63c7d1829e9f9cfe6d2c03d4a2d5f064b0baf66d32b2461d9fece934ac737dd5e657e3b0da5bff699889ac44dcd87264154cb019ca94bc45eb8cf7ef419cc23c64702ece11205f236f7b64848814a23b2b2c599b8f0c1f35bf97d34fd10e49540a110828d682327c4e7dcff05048368b9ea798d091da14f364074a3e1a7d0b21e09ecab66eca2dcbf95fcc66438557925904980b257287123669bdeab3a2871d1c928300aeac5a3fe86809bfca082942e65748c285b30ba7cc7911f82a2c54e2602398ef9b9e61a31728fe6b2a06c056b72294001f620dbf7ecdd6a8e0a59640a173c8ad0b0dadd77f75fdb055605198acae855f96dfefbcc0c790e46f70ea52de05e4298224a0403cd7ac6f48cba0ecfbe4da2ef75b81d1302c22a0b7b93c90bfc0205b385551c09f197a7c1d3cf742e4230ec9c12f4e7b94c152ac0c0b1b5a2eb67a0a35c458fcd55840fb4efdd47b973c6d2ec254ef4576704ed9ba2841c6c56caf3908ae9051184ef35c28ee756da06b2b1c129d5d832ea4b1012ebd9aed83ec344d24ea8bb93001c457c9ca21b352e92470eda963135349434bc7cf509bf037ac28fb337de3688de8be84b3189fb27b61ff6c62d921e751d539335688a5e93882a8908abc3668724b06fbf8be8dbfb71fa2419010d47bda395a263d68eb75392a63e88d8c807c4aa094faf3c3ade347c10d7ff0cec4ddfb7069c6f107c04f3011e94558eddfc11167e9027b1bd38cbcdadc14db6830ad1d5fd986468949cfcfcd95cf9a896141234b2740d809a207746a95eac13598c2093bd8b7ac3a61ab7915d2851cd5203d3da924f676bac75576622dd73826645ee81a9ad25170c06f1bf5370f8b29b3575bcdb2b9369e906df4df57168f3f3c26f4a9870b7cf9fc762b66f9042e18f57e6f27217ad81c97a7dbb67517b8a32ddc59ebaece2deb97c3fb9bfcb40961ef4371336cd7ae804ba1a789137dabff42bf346363aff9f3653d3d67d3a3e76911811d26a0122c388825fd557710bca64681b82e35d65bbe20634d72f6e27d7291b289355e95806f4a2c2b10c105ad7fbf1001fc480fdc98aa9597f56eb4eaaaf964c7d3054b54dc47aeb04e1562e3b5a9ab8dfa666a2d09214ac5de56317f958eeb8737e1f2881baf478d456593f454f5b06d61bcf16a27d1eeeacba9d9d25714dc1db55febc1111bfe69a9a9b8c40c62ed533f37964ed041412af810a03113be8e56d269527832be87a0c2cd1c4d4d8d336b82317cf39821d5831f86a9dd13f70b261fb55574d3b391fd2078993a0c82eb872284ed243486b04284e324fb6ff1aa0a0cd4c0017e6ab08b3b6701b462582c30c01d73c406c2b2817bdf9c1e658026f4557dd175f1bb84dda06258ff75bb86e3bb6a4a74c7e03e201de1cc15312155273885589925a3de8c1304a03294daa6d74656695db74ea3217d15dee8447aee6fcc3ab58779788ae75ddda28a44f5b7b235cf955be69ad53d86a6f5c46d259de38ecb6f4b5a840c5d0760114bc2701d0d50fc7d3bc532c58480adb92889be1b841d51e84533b499d4314a2265566e4e21ab3afaa447c1422ad73ce3c6524acb9bc510c5446431aa14c90a6538698994c2b8611ffa79f5f1826e18fbbb7cd95c249bf614e448993d9dc090e0a54ff27cb0b3b015a1033f72794d0ca48b997df4fd820ced9ab40b0589b982a39665352a7434d85f7906b134cb7bcf950c651197c7b23cb86de5cfa043428fbe79ea943385a69726f0b8a05928c0573fc7837106d5e6ae4fe377848e999594996d6e180d75d07934dd1c1d54de185cd14e3f6a29d25f599e91ca1c99d8830510c63200f7dc544f74e127ab998c4a911a23a7f8e56bbe6bf3b1edc5f76da256d9934f04687613ded20d13d9d89f1d81309509f0faa4498c3220b03e610bac639fe89869101c44fbb9249263beee46c11c3072d29406c4cee85e7d37b3dde1e507898cbb8e7f02653b2c450b79c8569d59bcd08ec00e87eac97b19f588c7f22ab04afe199e5f98f78b91652f451391015c684aab76eac641b11dd43ac44b179643901f04e9ec0d00e92e2993dcecf3926e9e5939a44aa8ef65c2a05601ef319e2f24820340469783f37d6be5ccfbdf7857b90673e71ade173287eb06048e2634c4928ae44c0865198011c1bd4e34dccb06817fc9c8d129c6079465870f96d08759bdf19842a8db6df4cff03f7e60adae5f9a58b6a0735c3e8d5b498cffe0538f0ac0cdef52ba90152012b096611c1003c2281bbd1c7c4656a042601f30ca4d2998607148b419754a1d3ed87f32dfa93c9c77b4e81a02e6f75579bf12ef608e7a2a38e97013aa14b5778e2cdb31af0ed00fd627e94560ec96f31bcc1832d9428fd4aca0e8fd7b796707bf543a2360532a27d0cc9e5b74b49d14bd8922ddc26f13f217e7e30b907c8352c856cdde59d8ba89fc3ce080698d1f487ac8dbc6356f8ede8a9b4dcd4bd8c49299444442cd840110aa080328693d3eacb42d9c159556759d26e63251287bfe4425285859ed629cfe6100731eba757a4404a1c0b1f3b66bc7460e094aab19c7f212a1d95e65940323a36e2d259507e0bce9e65fca8888131cc60ae8420f0e5ada4fcfaf713f22c2237b1678abda70690ed229cffab97244b0b153b39932fc24c32227d2acbec2226f0d4d681c89386cb0c63a4a6f49cfbc68923faa36accf67026e6f29bb052ab9387522e1792b8d0ac0d0e86a95c74a800316606179354b2794fb0d5998a8269097775e42071a82268285c2792642c9f57f91609d0eed3b6e5015d1f47c72f24ff83d1c37785f9253d00763e58ec0bab029c75e0da54b03cbfca1cb073b48de788391e5c5988adb22d7244d5d8f11f988be3ab26d8ee25524d5aa854478367d62a62314300a518f51e2cea30a5c3a1c483cb203b6dd62ad0fd5a166469ce680cc2494086c9c95eb1f95a1edd4be2add42b0b0a18e0d520afbbc7a046b45811a4d94f02b1eef095788e33d7f071062160c894cbbe93f386a2507964abe5f51cdd1990a529c8b83a1a6d0e203d34e6aa8822abae4de4c7afaa0f01bec97ad046e3bc97c5da2c717831cd04e551f61dd9955dc124cff00a4808fff00366ce92aeb65849c37b1ca368c5126934a31c647244f7e060314c52b7090e1cb5b01772ccf43c37ab13c167f52a0d1da700a55be453d189fb3e83ad405c81641a0263ec6bf5d54c9535c60b2b15513d9af90fa031602e623c26b32db6384a5c1078a00b3cf011389a361da7f54b6d13234dd3b7ac28a7d381cb125d22c7d8ea60a235c75f18f05211897e71f8ff328fb0d69b601e56dd5b8c8280189f541f771e0707e796327aa07bfc9a5a0f5a965df21f1a9413ca1f199b7db3194ca7f7b59e61c1d369c7e984da0010920ac521b6b10fda717b4942350119561c4219052607837fc22ad95a2d96c1ff369aa2b84e9900995a17ba829b4d3ba670a50944abe163e6ddbc809869dd3b9018373e8ddd53145c49184a38924d969560ffb30c431eb40a842e1c5b82e938f257aa7af5d601da02ed7d5e97dc13426c79c0a683a47f34258e0c61094d1a7078bde53c72c61c3acdd244a2873a726edb1cfbac1537a1401b1358ea10d6eff36cf929c40ec645e2eabb224826ea2f840b40e1393787fa5f8c39c80130377ac13bc71012e59eea884172082cfc9011859fbfe7794f8262ca9764f11e13910944b78c15cc490bac568c92ba1afa15d27a051e8910b0809850df23522e829099e84d6b4520486d09a615f0525b57a5d027fae4e5fd331b839457cef61cb6c7fd278775e5b88478b346c6a338ae604f2feb39997a882878453dd24caf4ebb512063f1a98e1bb9d1135d1997c5b736bb8c45285af52eaeb84724160304d02e9c4b02ec48f20db866afad4b144e855847f90fb5190ae54056156aae76dd773167646d0e39a225b88d66819528dc44f042c0dfba6b61d891055f0002b4a7d077500a96de93bf726a8294ea0a4b8de8602148ae9152ed6f8783e3397972e67bab869ac6fa9a841b9b226f6be8f9f445fa94f0650100986c9bac59209303430c999971c068433facc8a1bf68c0a4904719dd2fdfc4bcb71b1b8388ca4897bb60cb064d7572c415684282bad3d601b4f45a32847a2282ff4af2e5fc4f11fff58c880ff976d5f7d7a3375f81c9af8494a7be5b6cd830b2383e6a4415a6883d77821a816f52fdf41f7a1bc3e909dfeb7aa15c048208661dd0c927523d1141e9eabab73cedf1d0ded359d1953b6c2fd69cf565549b51ef040d48687194431321abfad3f702fc7b10b074044d22f3a0002e3bca7a63623869680b0ced9eaee392f2281644d82f29b36f3aa88f5da09886e9006e2466b74c0c7e638bb016fee1298f8193096cec2d7cf4e24bfad15a91146bb4aa8266f6181f543a48f2e8a62d1d7794f2bda23fafa87cfadf18ba48c002a876997068f40686fef806b6c59220abdf315c4ab2264c0c13f824d4874006e5d82da59780c338b6b18392ef7fa1a8119073e5f7be5f09ec10153ca827642c441ddf0dcdcda50443bb1380a0a6532c71406e07fb82bb1f8a8e247e428fad8e46f2d1b404847f6b07820e820fec74e83c89fa3b068c5884ce859da3ac63922ea15d68a2c3ff9c239ab446e520f35fb102c6c09ea3857aaabf02dc5714d984e8768e22f9d2644b07fc5a78f68db27fa492fe57f572c43a38320c6ebfffe3576ff3a263d3c2b7cc6ba29929060ec9a21d9b3730c5cb34798380cfb639c96786e0c5597e008ed9ac973903e1956d8f3088713e0170e6da4afb4b8a27ce62d5e74d140263c817204dbe0c0f1259aeead629a679d43bdaddd45363aa91508584f8ae93a4339e84620eb71b1b3d65dc5637e0dc84590c6d8e51e18d6c4ccf86926cd6a8f780092d53fe130767174a82e369cc2b4ed60998d8fb637fb0187e38303df88fe046ff60fc2abdf5230aa1aadc02f2872b9cf546ff64f5c4b20ea275d867bfa0cc80077d2c896b0f460345ae82d3a34fbddeff422c8faf31285663f3e1d75861ce7d7dd1463aa490a776727086872694bb469b90395694a291a459efedeeef9d188e100ebc669319f0a9b944c425bcde767ea7d0a9688e348bdf63530fbf1df3ce810cdb3f2f90348f0b4e0018c1e6edd1a10e1c69b957223d732c8dece0a05349802484be2f3c59b9602a4bf84581e4cfc18069422cf67838940a11fc6983b778c4b4ba4f1cdd9b5eaf7ae11913c922293c19265ff3198c07d022b8c6f3f34a294a1bbec7dce08d260e8be47b5cbad7b0fb047374cd4a45a6fd4ec3bd093fb1b57fbe5cc78ba3fecf38b17820046788298dc76eedca49365cb01ecd13601236660b7c70d2b703095ad0eb5c5715755e88b1f48ec03bf85379ac20efb1d5f9301288b8f927e670a3ab13451d9349e68300c22077f3d9fe196a16c040bbe0821e1b500ef3b674f929ef9175b263219519e91a7238d4e56fd46704f004b1ff75b9b23d90f8cfc85e415cd3a589fa1349cb76de92b733d020bbed4881de7323a2737270b5450cb19d1b32b8680c6d1129cd00077c0e4ad83dcf0d72d27386ec85e8a2e72bda4e199610fc1fd8d5cb744cf279d4823cb8d3c836dc460c441a0004b1e5bfa629ad7a4b86e39891b019587fedb932590f4cea45221cdd7a5ba49ca12151aec22625567a2be43e5345b0ce2b46ae81431bbd165295cccc0d6ca26400e8f660a3ef24559a43b1dd497cae0558bb1d4293a1dcd4e9b0a41238999c5d2fc7a1c3bb641b5fc72f726dae53cc423c70a0dc8e663f40c347259d09fd0a513ecc56d24e29ad275d56354ce306194b33a775ac19d11eed8e9531cc1f0f598e30cec9cb5c6a145e44d7585a6bc509d71a5d5a0a40bad937a4f513f1b9012bf6993354d76cc93b5fcd4c16a1b1fd25f7cc96b763358cf7ed09c55885b101232b0fc0ec13fa4eabc2deac08a05d8d0cadd0dd255125d1323d30bb96b9d0c086ad2ec7df5e7e699f13e7b46e391e9353b1056239a4acc180a0b88c7317f53951992815da416601d115d56bb2a7c884b27059333708599d528f3214281a570c46a7c3980786f556c486f344ae292b6dedb06047c29f4b8e785263eef636122aa0f8ad39f8ed637f5fa036afb8058990ebc9172a66a5f1d02fe8674033b50b6ea567cd18e2b36f9cce5cb2e80b0c60365fc00ef539e9247f96549e3f6672098a2d5698c1f5627244c81b3ead39243085c97237c182b2c35e07cc47294b5a91286cdd499ee2d66c11d2bc65ce1c7fd3ff4e6d0e925e1f85f68d80cb45767c10da812f230a1acc2edfe5805b86d9ef9d6ad09e73355009a021f13ae0e58a1a36e710f7703caf8ce08ca6ab60cf553bc0af96e880ec41b27bc59a09c4ec5ac453606d9cb9b343132138526b6b9a2dbdec9f45640ff313b2f1e08e9159970f3e27efff38b1729e6bb12a4138fdbac4c1e773b6602f9447cf7f9eb45835ff501b3d252aea4b4573eedf78d5f62d1339be22b4133b18cdb62164422a946fa97003f1b725521eee5b6c60f1edea33da4e4f2cf4bfce4b83511457e5ec81412bcb8802f1bc2fe35f82750c4e4736f5911d4c574440c73fd2fc00bdf353798c98002af52de6f620c259e5a116e79cfffa00541771583056aa3c976eaa5a4d7545614505d7bcc35cda8250b2a165c30fdd2ef08faa7f381ce807db095ede804e0f0ed94ab1ad39bd5003f6de11d26f5d7e4bae01e6bab8b1d58ed420325c2414beb5d63ff64f2a594cb19d56b9ed9f664208885442c6eb5e7ef5874e3b9d0deb1e7ac110392204f9b1dc22d6b8ae83992893ab7dbe6906d7977f1906b1aad3f3d606db70e344b69bf241f9d3858d7423cb9bf3e2fda2d8d5e4ef70c8b6260dc3ac6ecebbe19cb4f2120983cfff56cbf1ec21dd1e1a7a0b7a9dc47e24e33e1c994fa0bc947689baeaaa0afd7337e12fd5cb703f1efeb0293a826ad2b3db157c6841535695b95f00293bad7f81de10eb0e6ebdbb33062fa8c3132adfab40601859131a7ca1de54f905d6b45e395fafa1ed2c4d3bea6b88fb3f506397389792d898c1bb47dc97c4036339608e1ebbfd76b745d989d2acd10244bd7d52fe0169a70b332f4c9131a34bb54210de226891224f01ffc77843ca6cd571f1ae73b8dc6c137ae7ef058164a00fd22e035e3dab27a711a094809cea435847601af055e3e4bdf2a3b14784f8137464e634802a890042cfa904222d937130f07f12695cf0d991aaec7e5b30704e2464f07defdceb36338e0b113351400f8b3b487cb7ef337d3d3de87dcf7bcd6bd952987ba22c373f400da3adf1b59b39c376a3b0f50b66e8f742aaf7d323d7d8464a439b94bfb724908289fc473bfe297784d4a043f23768d159e1ca72c31fe106f145966ce07c7c82be46ea55a83463c010db70a3397d238ceb2ccedef9b1810a0404441d7725002f0a2807a7521d7e7642f49faaede38b7d53ad27930d858196c25dec0112d999fbf799a4b8241d0fc049cf4a6fd2a1af826fb81c30fd6666500a28e442dcb93e6e765440f29c05fa7121eb635c8115d41b5e41f3f4a71fbc24b9f10c83f5391653067b20f58fae793e554851698d45ef3eb0a7fc45a7e03b660fe5beb355570df45060cae1665635d74d9bdb0b43fcc9b6ca0d933c27bc30add670f01c5086946bb5d7db1001d6605a22a0583fe99fe4939c3013bf2e76f0cc5d424fb63ce16b9db783f7a5e1625a6048275a77266be7a1ec2dc6eb41f4114255d77e2e0747cdfbda28942a2ec9e4cc2ba426c4ab60dd56793be5ce434b1937a37faca5ece715ae3fab7f78853b2b37519672ea825be4eec02553dda6bddcb23814ef91bd1e35b694588b310a70a32ccc896b0f875a4655aa8c17b667314fa1bdeeb3a984554151a0f990394eca6d03c444d4aaceff7f59b9552c3c28fc5bce83e25f9fd18a0557decc98ae4c1658d8f996e57391850eb21559053175753f71e8278068cab0873cd958d257dba2d4ef04620f050c258344529adba879a96bd5e09300737f5f104b3123c091c4242ebcfb5b463b4852e98aab1e6e717f02424043b13600c1b48816f7e33387e5de0aae4cdb99b9c136d45166d9500782fb35dc58efd5528be31489ae920af72bdf715848714a72acbaee2348d69621ae121deea958b9522e4f8f62f5c053af38819b718eb5461dc9811dba1333eaacc5469864db7eb8f12aba04fe52f96b71681be6ccb775e846b7d056fc47b90f21f809d8da9a3ea251bb56e511e8a31659c57b04c4fd3d6acd1cf4c382185a5c87a528393c1906001ca8e4dfa791a330bcbb62556412bf6188088d5bad3d62475605fc1cc99742b050522ce3603920a9609a537d750d4068071efa29aac42f55e1d526593f28cd09c48468428d02a6175a372cffbdff7ef4b63578c173d8d72bc44592e6739931c04c32089ae16bbacc0f5a7f7ee5ca45845a95cc848acff2314bf2ba3198b2c5b0e89fa8a87514c33e88d2c9705b2795e61fb7c31b949bc04f71a8a47cb95ddf98ca503be706dbe5c0f794692370edad0dd7c71dbc2a3872429ad0cff7f26a671e6e38910dc341c0f93cbfe05b6181a4469616737474b5946d90f673ad0d00ba0c820d554879ca55089477b54826dea5ceb0a932153eda384b5104b9f9af2ab1b3e7ea2ae8106d24f1e5a96720ddc47928aed969537985c21427d074a430fb403b16243d56511f3fd2fbfc1c522f8076de72d02e892b9f615e4864e5f421fec63fc70b2ac8a9d8600e4bf05405626d97b812fedbab0dd2fa955384d24656662541bd005805f78c2a4290cafa73efd2e062f1ebc9dd1cf4e737fc829901b20212221447cb2530125c979c64af15107b9a8813b89c4bff5b560a97817055899003337952f03d7cf6387f03cf34b065339938c5f29dc45d2456d5aae464f5d8db9a2204d1803ec5929f73659842b8476ab9df8226f9eba7635f9ba5f61a3ad391456434f179d6195567adbfe5de49f8df3c041c745b10025491f4ffee7b92841b491679018a04474aa0511c375e03760d1790b7ce0e8a80c5bf1b5394ff928f0d5d41d27dc8974690bab435860b6936d8d0d336eb5dd47510218c9bdc0a78ed5cd02b65a258741d5b57c7118366db4feb34294768dec493c54b0553d52632ad247f78fd521a04689360a86fbfbe53211be6830a4b6bca248301e9cb2d4ef0e0007857668dcfc2473f6adadf16ac0a1754b332f2ce66d35227ea3b5a1d0a312da7299dc3f4e23c452965d3af390c7bca517c0c3c64c9cb6cbd7f220f8cfe3d350563112f01b5ab0e0512ead5052e04dfd8d05c954bc03e972723b3af31bcd096dcf372612b7c53f7c9cc60055a0b4b96788a5471ec6ba6799efec167476cfe5f77efe09b74b1e8035a5c5109762b26fc400cf088683e8709c3e640c4a89557d181eeabf095d73c8663cf63ee855cb017f26c57d22f29ad30504a15b544549636c69b397d2efca6d2d224a273c06a543860e6a3bd3262a93f7a05fe6108bf844964b156a8af04939344c31a9eec40b2903358eb443ad9871565b253c7f463336cbb63986e743514a93e52344da2bbac2131a6ca0de3e04ca5c2b7c3457c0fe7e619d82942f8d6997b33287a51af251b52d5a7675679961bb5bd83fd97d6bccfd1fc74064a8fe28ca68562819aad17c3ca9b621d3cc1743b017ac8bae3e2793b0a7b8f1cfabfe32267c2a1bd7f3d330923e2ee67a09aa3087803bca29f285bf724970d0828705823e1ed96ae165462de5d387606e34dc79c5da04be66083c82378d2bd620e3a0214b335ef7215ddcaa690d0bedc35848a6d68953d8481cf919f4feee1916d2c074c6a153d848660639207973a1018560630e2a4fc38c1b63b9b043c86c1a20c13273fb450a061c3d33cba4b8109a929a394f9346b6faf25738fc384684d9e8519115f0f6b223e24164244841b894faad611c674b446b1aba283265f01fa0056158bb7fe6495ad671a780df9f0676c3a06f029adffeb8a86fe81169e623966fb88095803c97e1528d18b89819c7716499d051b8d89d9757e47b2e161b7071b9247ce39139382be39afaceddad118721677d83055ca55a6a8721d79490930b6ff2724f5533e5ede331417097f109537666e4ad0bd3a5c9ef6b49b48a4f34fdfe87d4f0a10214ce2d07702879dfc9ba6a4b622831978e7cf164a0a44a4b39a6b61ada882656268e6229844b22d6f88e7e0bc3c7b8e250c657a3d5eb2286cdfa4c95299c590e07d6f5564e5cf24df3be56c44bce82ae26e759f84b54f5ca7348cb97ac68d366d9f860161eb1baa15008002e3301422f947f09c1c4e4a6dfb7693586651a66172469103e4031936d6572725eb481c205122125ff110887feb0f9e005b956a122442d93bea341cd3dc8c080602e1b615dc8045e1294c06765fb1d3ab67cb2e3ee5f2e65bf40853432a4d64a41ad810a880caff40b5d45813f45deb8b03932ecdc09ea8286db4ee88c4dba89c70feac485ec6b0a16f8d538dff9bb32cb1c194f0c3a3be6862f2d5f6ba4943f9048675710a88d8d188f270e80dd68a2f8f8056ee0d82a3aeef6aa57725c0168face139a13e02ac3d6275c3a8949edd52e80d93b10927872461fbb7b1717484635030148ba80b419985a80ddf36bff2a2780ab6821a6dd83a2c5253f772f21418fa67e222971443f04c2b22cbe43aa82efe34f5a14a0d8772493542a913d9cc0693a0c30d8029b71a0ed287205251238d4ab3e9807f1f92f300ab110fd7abd72ba03da690ff2e0eec5fcaf8257ea77de842352894a7c7c4340d1414a26fd706f64c0111e2fc3a7ada6d2d5d6a9f3ce455839d09b292069fa0b7c73c37ac3f1e65a8986c38642e0f00da783ed0df59eb7fee2dfb011767f92683007edf33347c1837af7e94bbeefa3cf81df015b982b21265becf7046ef637c1128209685346d11470a3ea2e3268479cdba49b552d9e3a5978fc369b814f74ff697506c1f9f3cfd92e450ea9e129bae94789607e4c642bc4846aebea64a0198a2f252b00dea9c5382f0827fbe8e9c82024c883ff8a9306cba01f671288369241f3cc3b7b73dd267300863e779230d85d7e4ea613edaf424a0d580c9db58f4a7ace4ba0d82e02ece6dd1915bfe9e8ba39cc1400ce65620f336b40f3280c13b395492cd91096098706145d8b06b7cf5836eaff09234220e8f61485a85d5b5902e6443e211146c62a570fa34c29bfaf14b981b254974e0c43533200eb3d342086d1348e48046a1a2cc86e07dd3fb7a17421a1f8b58956c33cb041e175a6ec813ec3dfc0453d8f8082fb686263cc4013047c190472925fa7a1f0b30445543087594020741f296d7d50038990501271e3954a40a9cdf841fc7aaa84fd1c014f9e7e9fc5e0000ff61b9fb337497d7ce183517dfc70924f3fe9e7ad03103fb967f91ea54c2c0b4beea23d0b71fa20fd3b09e1022ec0e4fb345920ce5928017eddf16a7b5763cffc81445f24eb5f357c39a111384971f85081153414886695e2285913edb8adea217fb6093c92cbf0a97596b114da1a8f7fd29c94c71a32ba36f2f9d7834c5b834fa49b3ecd68ec1ba4b408ac8086c2aaf43142e70ac195e2f307dc7dbfce1efa47b61de9493c4fde43ab33ea086b04370a86b907fc09b3bbb45da028e1a83edfcb190a4cc6b81e18eb8f6d6bbbb2e9b187807a00cd6305bf626c59a8e0d86cce34a7a46be8539ef3af60a5713baa3d396807451584f6dd24c6088a8032c4ac5fb8e6d2db85bae79bb64508859e3624e52792d5011590b5517ee1c3b864b9815a8e1d566783e5b6406c687ae71198330d5fa1c350d2a242ebf998628a8c2acaa771edf83c5e58998caf4948bd06b752ffe0d54f868bdbc2356d10de56aeab98c756fbb511086c725d5b577ec659af960d79d9c325c4eb499d7d91d60d3413b5d60a9876333d863c1a9a6af844242df9dd1c21418b2a9be6f86f56bba8fd6914ff0bcfa5061f381226407b401e510a803cd42d381c141a01afb54e06234ed7ea0e107b4c6c82710ac5ff897409eef31cdea591fcc50199af3bae3e47464704c8bfe810916cda54e2e3029041e9781e4e959680430d4345dc5541e637cdff7a1d35fdc134cd39822d89f78b2216f58bea1efb49faa9506909cff0d633a12f2aa4bf98c3c5711bb08d0c81affbeb0251a189f84b5cc60d67537d0d2e130af96c8037e7597036be3914c6a138a7c53858cc4931ce0b715484e3628e15e5bc0887823914c5a13887853858c881228e8b715488f3624e153d52e80c272aff87634531e7c94467ae9c0d11aa9848db07889aa184fba64e4e59f2c481a67808260d078b06ef52bf5b43d24d3377842cf709058438a369b981e38aabac7467baa047fc721f7d918114191c4872c3f8a63943473c8a1f4e4fc2ca01afda14128a3df505bba6130400d3fa5dffe436459aae65c69451e96fc4af44e3e0abe6ba4853349748455f561ea2ceb49a69d5011704e6ba070706aac52de55007e64d9c1fafb6f19c49920e5f4ca851c0a292d2a25c9c968049371cf1aaa2419cbe4f742d6e729d58e5763a3df11cc313efa07c04c5a4e4b322b3176ca090c542ce797f9466e756b19eea2b4ae2c19aae0a4fe714528b450f1e46157b46b163bb58e4ca2854e48c00825c440d13cba403ae219b00723b4454e2224adb3f50afa4eb452017901548102eae101bdc10dc81359d588c7c6aa3374c7c53cd1be49b9c0206704470a667c89e0a6986c2874e7cfe3d2c153439a895db2a6f1d0d6099e21e21360d0bd96c4b0a5b83550a6d1c9967970bea42a2fa47f9f10b474600668308e7ac906c5835dd5eefe41797e304858a1a33caa0ebeda7800368cec02507a9ba346027aa9db2af9b413fa248b99c6a9bf429a83c0bd1e6b8ce1d5f58594da80139118bf24f9454f53b39126dfa3a74a8d063fa22016ece2de1d1315ca4d82116caab0043b8f00d74d09622029154c10c556d7a9bc3849715b8fa53a57139259e7490504d53af5032e64c74230bceeae163309e2f0bcf508f629231601f7c2983e4412d7c4b59705c0af43f6321fb261bf0b5b4475a0235dac2697d2052b157c6c297bd8b1fc7cbe090485cade0361d13452ecd18f3aedd64f4ac1a3a2c803d354d20abc8fb225d41f7347aaca1dc85415256b540b382cdddd08e298986a259ee56dce18c2792105e7a6f3a9689bc43c88c7e3853c39625652fd17f1705f07beceacb6807e51ceb2ab834d6189decbfda8018a9e9f66e9cc306d08d71df7db9f7c5a8f96847bd8b20b9cb705da0fd1ef2edfbb7d3012f486964d1d84e399c6f3e1ce2c369f71135ca7a75db6c62e3521e5f92228eb4b6e6c682e1bcd7f291bcdbbb614e2d1fb1db1db0981975c6a05623e035542afe0704fd014b5b0194d122fb2877c61422850dfdd40fdfc9a06e84a0764019c0cb26db00013ba169c0c9863b675db0eb98c6793163a640db70877a15cac8f6c3abb39228aa151daa9fd9823c6ec4a51bbb33fb0c6fb367da100d2f4ae0ac2674102105609551034a69034f298009f898c89296f7b9e0420415187def311a965920a479e1f7c3d80db9cfb3736ea0972e106e263352551401b2106e6cf1349bb9d632de87106f9076efb958d91badae7718d5ae5e92a0b1f171c7d1c06d2aa324d742b7f7567721aba62ea8e52bc6ce2948a598e9c195a93ae4a7898493d56cf4fed8a388245a1faa371441590b7f0e74304e72711160ec3d2ab486646be20e6c43bff08a8d1e3101ef8cd9387a33ad733306b50471fdd270976c45eba61842843e6ef9ba4044e1a28de07497975524d53f95035274d4958b8ebdb05785a25ee8606f6fb8888904c4a14f78380877bbc5ec815d41c3b4e145324ac399c8161d44835d1a5e4c938548225311290e54b3c874880bdb4cadb9c9612803b9ba7a692070e97023504ea4bf45101919e6729bff759c4af58bfd17093b6cc4a43d7ec251c6616e496141e93a3bc47633ad547609f7925697de50eab756d847cf47ab9e39f826bd1a374a69ab4ba4c9d4e8ba2ef3369aac7375a3a6f7279eb4822ab252a0317bc9a372c0dfe1f7579475da22f1e18926dbd209a6d22ca6497ab6a1b9acdeed17db5d98ec410f0d46a325833ae09528d1059b351af9c4e99f3c6fe0a28bd13949c0f65620152d28160d4854542dabc3c20ce9a3e1da27292c2796208e219af525e91d8d04b1a010e619c9411c8a48d309fd090f3c71c9909af74a9f0771ff9640cf1869a149654783285428261fc4fb9d8726279000120930744d39ef8291ab427c4d9f7c4efbbc99312e3ed1879646369933f111f83801e8a7ef1f078f23166660d93810ba43dc5cebd5ea3c90fcf4d2498880fcae35d9d5e27007968fe67c8c8943103373980f547d675ac5eca7da0be53002e5ce948f635056a139d30ba0cd01c75d4ecbde045a2dde8154d2eb1324371b91849c1c5a1ccc43a89b3b37261a8e96422f21809a7c63604025eb24d1a2dbafdcb0ca2eb2e8583f37bfb996f5aa85f0d29545403139f9569e98ca93a6d088d1760b6a8b013729cd6f9e8bf7e41283626c4d4ccff5ac0ec79ffeebc9e21b4fd444d33f79dec8041fbb73fca000c0df8bb7ef020756eb0d859ca399bbbf0d8eecaaecf88791e10cb98e2e8d1d17830f68a154fbca502fda34f7b8dc56c9de6ce1c70f386d457c40b741f736dfab9677f2321c27cba5d16888a9ce156a481662d871415dc9d109bbe6cdec09efd82ffa2be3bca4ce8cf0467e86012a2fb7004cd21b8e4d484b625a054176d2d03b57e7f426a57a8d148cf039fb41a014e837a542c149c1efa662a6678d7746255e1b43e7815ef942e88cb6ecdbc8fe9dd9e124744d6e87dbc613e61ae993ee0ffddf0d7dacba34ee821b3845bf0c54f15a5cd14f20c7956037b3b29ed59a0ef70803fe3e7a5251acf291632517e47903975d149d9d014cf61bf4ac1b24f8c2ca1d67065989ebc7ca5bc4f83b15c6eb53132858868707e2cfeaa4e9a9c870bbb103d047c3540fa73dca97352c5dd76cd1af9ae14f1aec8d855644e421fe159ab6fdfb48dd8a56c8d577a5a68d9451bc7f7202175b0cad2ecfa252284b9fc7b5d20ba50a62b3e3ffcbd62f5d0b4f08be6129da5ddd02f940ab7da0f51995b478bf4a68d326c6f516033f7fb3f9effc3915a9bfa744233be262c8c06b312a96cd016913c88dc13f9d69e09a56ded614b4d8c3fada88cd3c06a7a3fb1767c7d00f49b3a0cdb1a648df22f80848b56366e86569cf0ad05c663a845da23e500afc43e3434c890178075a2f9b1125ad1d82cc4fd40a071b4da561ff4b4b10049605009068aea55d4e5ed5239d5ee722ea5e99d943ac39d7f10f6e884c6aea37e15bb80f9c4d8296965a7390397fd90a7be6c8c09c030c469a4c6905ccf485635369959c040420bbb5c9cac40d027401bc0f042a0fd79e5a2092b29a0e1669870fa1f9131b2cdbc7e2fbc749ca3795058990cf755e3bf4a75fe6b5e17d35a61fbaa90a6d6006bb39183b01102b807a2c09d6915194b5898beac644eeeefe6b682fedc101e36c9090d6f6cab6e5de52ca2453020a8709040aa7ff71f37dfbf704fe9019c8a9fb1f3f75178efe3f3c28fb584323124ca6333c328b660c047d23df053c439638cc39270b9d23a75138895c947f61c06310d10e17fb6bb8e7119ee2d6b88136adeeeeee3ebbbbbb9bae5c7477751e2592cc03192f45abdce6eb4506937f652f6f61e4adc4d70a7b5e8d2f672c999114c6c401beb9af38a1c56ce2c415453811c51727b0d4f85205a594524a29ab05984a2453ca0ce690d5024c9b29b35c505d52960b585eb0fc49a7cfc97201964f5dce1964c4724133cd9e7353c19c93b35dddb62bea159488ee54ab132a4e73ce4b27a5f4631d55af5627547892884db60ea55fad4ea840d139bd7b22297dd1af1b37a7dd38ce8acd8a4967b76d4768e9a6b4d287c5023c5fde15963edc3704f5b6ed082d1fa5b408e92ffa2e7dea5fcf8382e97f16149f678756faa12a4dab45a54fea2b45a17e2c2a983e7da566ee5f612c7d3e19a2c0d459e9d35c580bc818f75ce8c361f2c7e6410cd8b32aafa5cfd67d24d0ba2f17777fff5cdcb44bc3a30d8521042d8c343a31b4c8d9144e866da8d68551173490c16b938b26b8232eb6f89210e34a1eb11403892cf34dc584d1a21e1152a680ce4850dec8bef1b7f8c3e00b700f1e2efec823fb69b66a5d18f00ca5b888b4b0fd085ea8bf85ae077023d89e0babb8ac6cabed15bd885b33a6872f702d8d29377564bd09b78a5459abbb8cbe002367600cc92149248d5a13f01bfa5d972144c0135ca47fe3460a1ebb690bf7f48d1cf1a725b4487f5c057191d217822191a5e460207d64967248ac659467c68acc200e1b5242c31cd0f41d5064c65a43e4d32e5729ffe2ec2e93dd41974bd0e5a106a47c971a9052035e7655f655760f4fd95d7a31f265a79e05816cd9c3ebb97fee28f7943b768f7157b9cbb873a1dcb6e0ce9d33d03c902e00818c232e1661d2d474a4086e2ae2b119a5232dc826692ca1f428c686dbaf51da2edb7e6207a56cc119ead1af3cde52fa6248e98b2979ec2d4c4d5d7349ad377770ea55f1bf78e8887c7d2805f94e8f28cb0cc1e0440d5b723bc1a486eca04b8606e81cfa3a1996ef06e687253e45297b98c4b3013ac7415519f9769e835e17f99e3c075554e47b3d075749e4d32743074f46a01a85ead139f4bf2cc176e28620f2dd3c07c5dc1f8fd501b65f80ce99e9c9f46db0f344a63fde64aa44a63f823c9d437f260c5ef158503a8f8bf45535f0f0fcf0f078ccc6d18f90135208c216ca6df55bf2f1eae485aac683253c28fa19504c441c0eba991734a62f50d48003ee024e8ab88841450d4b5021fb91c40b36b85294822586fa35762d6f7fa9c7ddef5fb5eefd6fd8189429d0056976f0f497369bcda2c04edf79d7dd8b73756fbff3890367ee0f08c9a97b6e296f1d38c482ede2146c2405dbafad898b447e129bb77795d0cfbde7e94cba1aca262eee3491422e0eb978a47e92fa2decdcb716c9e9bb97e0099c373cf3a63eae3b3b4c5e15af2c924d76726d228daa955b06cb088b1bc1404a89570ebb593e8ddc62694e1f3f3de4db99947b5bfaac0e93db07a4ea6c7751de8ecc8f3c1c942f03768bee1e52171b1004e5c885329c3051850a2c64ed37a777f00b476f4e07c71632fe091af1728ccbac02c51df503c261b057a96df1fbfe3e09a51f0ba8cffb4e3be88a8a3b5e5e308d8b14cbcd07af7dc9788811a0e88202df2a1521f71531cec85eee2b5d76984a1065cfae23f7153170d0a5062a9aa35ac270118394175a902445d7a04a0e5b58c0830f67445162346713ee26f795da0f5a04c1a35571c41526a60e596221c5114d6481831b907a90c92e5984d1c1164814c1250999fc96bfd0df17904a587e33c18124a05479c286232e31c846dc3904e96004175ef86003d2144c64fd8d815ab7fb444d8b252d984268b14c805d24d05c9fe6fa4133b062031296142031a1856c3ecf8cf9c0e10530b0818a920a6c90cdfff9e2fc8d0a1e3f53a5eef9ba71b63b7d422ea555fe7bcc5730ab59d0e08962c9d431e7ac72968066fb1e60f9f26945e29be7efd995ed6cb2c0a73f6d7ddbf324e8795055425d30b5983af812b28c0c444f91afed4ea8958bfd33fba594e279d3ee53062541efe61b233b97e7624b918e35db64812535c2936538919c643a7491204723ad641304168bb23c3ce5176e8f08d2c67574f7bcf238e79c93d6d47003b9b7393a1ca7102e8de7fa6ea066522d6c23a646b78ab2e69459ad6458aa1a3136702b3583baf1b93c9a1bc2094797c33e076e61ada1afa9c36da4085e77778b60a3e35513829f03470834ae1b332d11b5c2a26dc8d9cdd3acb9a232b5a52283569adc31ddddddd8e258672d27f343ea7362ce39674b4435fd2e0de803cfca6be1ad72d5d66a77e47e8e47eedf68be13caebeeeeee061973ce392db6cdc56caad68d5865bace913d96b686b0fcb696b396cb2a34b6b9c69cf3959bb66ea0e0e865d9366ab4502bc830d945422ba7f20c470a641d1de6fb3be8e2a9ce1ed29a3794d2097a764afbaf77879422d9e6d75a2b923a678882a210cb770f479991d46c24e987a19ca4a53cdd95d2b5b74f3ee79c74ce395759b09c734e6a2d47b71b638c1c078349e79421c2f2e9ec1c366911968f3debc9f480e5dbae9e77c5ff3ae54ed7b90e85677078351ee5c16373431c32d30a61466c89734a996384938d1aa18d116ad4e01419a2d4a4d434e409256ec56d32362b1c191c9bc36500f4cc9973db1a0094ea5049e9d5410ac05c7d4074c70a2b76bce039770460aa00201300954e1500ad127f4035e472c0320094bebc02c002487d4078f080e5a35662e03907e0cd7034b99452ca517a9de254ab85237c893738393b940031ef48536b11227e4144b079e9a8f980688ce35e350028c0c947f8fd808f2347081f101540cf175a889c2074a48949e80852ad47081f0f8d6be6468ff5f1e9b12203d3560fafb7cc83863bf9460fb07c03381929024c98bf3d4de59ee0035004745d0ef0afcac0d4e7677ab665835563f501d14f093c3f7b74723b60f932df8a0c5c5f7e8c2a532aa361e0fa1f50fd5664604a65db964249231b5cae5409f1a982c01e8ed4674c172cb538e54ef77b82ebdfeb1477f7aba15ad5a0a1f9d68035228823783b740460c3c3f3f2c843c7ab060c9fc7cdb27ba4c4819dc0cf41812a8e4af301cd989765077ef1f01b8fd9f70f8bf868c322f3060a71db685c3768eb03aa3331cbd34f8fbae716408695c84601a8c3bcc5fa5b8fb7f97874528f2da9523f8697d90777884704a03e7d1b1fd0b6440fda5d613d236ea38813a0c96380598325b352f9789f1298fe013ceab0a9fa8068c839c1f2e9d75a3f86568c3a0002eaea0bae151f55314330d7b012cd2a84801c432bcc343ca584fb5fb37addb3cd6daba0672bdd9edaef47e7d9e70fd07e3f36d98ee2b63052c070017f4c33076c9798e042abc225089f01172726e022349d28430a3c73058d82574cbc90c510416056ee2b59f4f0c5861464311303dbf04205185b0106563541578065ac30c114650201dbdc57988aa612f8cb7d85e986255804a18685195a6051c3a289c6cd60b1b5d8358513a256ed861839555eef43a5f0274445371a97442eda2bb0e7ef513f36962024d4981e1bcb4c18fc7a922b11b9be0c2246fe90eb8f5de7d44759dc0222e619ea00d39f4520918344ab3c4a2249b4034c5fe29881c1d414c60c87520faf5add451255a3ee91dd3992a84a49e4b18d56911c76c5617d050845b556cb6d34ff7225b5b7e4ba81f269fee5d5c7b54aa61a615cffeb677cbf83593e2c68871974910694211db4ccc0801554696a9a9da1644683cb030f9e1ed20c27b2bfbd620610663429f2ea465cebb0ee97dbf614cf9b793363fe35c438353d8892c0dd9d500a8447e7ec70de06d26af3165acb81a311cf35d2ecd861eeeefe2e4aa734f4a7d6fe58eb16aa50dfdafb8ac1f3ec6feff6b91fc3d62b06bc6deee5197e107746aa677577ffce91364ba794ca1b2c1955c6b76ff8008f74a4bf6d937683b26bfd6ddb727b0deedcba72e17452b9a90eafb7bb9d78d670fa9c435c191cea9792927ae1ececbe75bc72c7d921ce862cb06abe3a6c3c3bf315c1b360cb6e4491026d4a5bcdd327ad2dbcd912d88db3dde97a1f2a85635432abda37f5eb409e24d8ccfd65d5b0d19ab9e1a20901478e07c39a970e1b116e44982e5b145134a49d53bf8a60b690e7cbff8d085f625f89d7df9ab1d3775076982842be9c4dbe56c72bff6c40cb5a37d4e47bc37c3d30dfeff345e5c83785235f1c42be3134f9aa5cf9cadcc87735932fab956f0d1bf9daa8916fcba3202b5b50c991d94ca64231b3caf7864cbe2e55be3431f98680f3c591ca37c77ff9825ebee1cdb7e694efabcb5787cdd786cb57842d5fb1e67b43f31d61e68be3f9eef03ab0651494f98a39228ace1f415bdd9c8be1b6bac7398b57adeef9beed4d0072ff4ccf566bc4050002b63aa78e3318c873d562d7dd2a7c6dda298ab41d9e966d51630bd922c19d332d278a1e6b19edf4084264496b8b48cb52834ab72347d3b21c2dbab305a63a67ba28dde13195929013ccf882a67774b7962d54e2d7d3609aa472abde0f8aea8c06590efda08b1f509181cdd70b0dfdbf8e3c76c2f41396ccc041b05c81b9da1734e491df4c6ba9482a92ca0706cb666581e7778c85d9c74211161318252944a590c39848211666ece5f3e5b5c7ef37f3fb3b673e2b077894429f135ca56fe6bfc0ca20cf9994e78fad97c78acc1d920a0b3356a469260c0631d039f325158f5569258bc36a9e7de50c2cd3073f482a7972331f60c9a3bce20326524b9eb34b9e5fa9ecee22cf9f34e4e9248fde94603b0652dde33f57576e0f65eacf211561e7b496b353dc799bc591769f931fa7f9716b6d4b395255ee3bab4d440e2b721812d427b1d9c861f453b469a6a06315cbad92e97d41a6e34522d33f81d58bc7766864f2867e4523537b9b56b150e15388e523028f7776ffe4b0cdfb0fbcb3dfbe75061eefec87fcce767693b643236d85b699c396386ceb4ed7fb50a94f080af461c12040ad43dfab81821901dac703b600edd343f2740f37fa646a7fe478748f0fef217d48681dfa148a4c7f3c40a6de4c1d75cf4c0a199e8e3a87be38c5369bb4cea1aba6555ae7505aeb1c0abb61bb487dfcb8486d8ec042ddb3cd96e4fc9ec24fe774deb8cc6dd982357720cd270bce7c3f219ebd4f4827efa943c59654e17e490c2f136555ead3a7919cb5f6467171d63d4a863cb6cdee128709398cc975a17d76ae51a697e8da20c31f219d439f0383803e5ca43af4b7d98cf50ef7dd4ea634dbdfc2f0464122f10c62431ff632c9f4af50a61fc4457a8732fd219d435f7e40ae0f17696d1a1009e4ba903b87da3b0b7f5857d04f40f70cf118f6dc3eb275e4d3f7e130fbf48308f9e99eeeff3d954d689ffaf4a9fcaf752476d5e873930b256d865209289853cd4ea7d3b66ddeb5e5615bbbda6a247d6afda17b6c4dfab80bdeab091e25ed49c36aada145fade7b8dcef9a7d96412758f7ddad43d34e945c3e69316e97fe1b7523fe20fc71d3bb24d61ff39a342e31cb292267d6ce835a944f7ccd0c9e81c0ac473eda4c035e943c6a59ed766acbbc82813dcd2096b834cadc85426012425cd05b84952ba75387e20a3d039f46793293487261317e977e05c32672e52afcd9b1a8a327d3a9fd4866c38893a873e102ac013efcc585c301752a39d19fb9e625a8d664cd2e60d7d8a6d38628e69ded0a7292bd8bfe665ceb9855e733032c0a3d732fd49df6b1ea334afcdd914f29a83899959afbbd65a6bed1e8dac2648e70970c863d53ff2c85695036c82dbff91815d16b796c0fd9220b81d0d70ffa5d15a72a42e229b9d3792035db2bf18d78fbe42c312d7035ae67ac0e9bbefc2116c635f99c13f7fa9c20c5d24a8b2bf2afb2751c1037d858625599527d8576640221f51e5fe13e87a80777aef0123e0fe3be13e17ba8870dd735b8874c2c90bfd68e3ee8fbb927c5f92ef34c7529d9a71fb355aa3ec7f2f9eddd0d5a3cea94454da0df588ec2fb34d025330f3c61f8ca560e6ac0ad5a119f3889eb40f8743eb383704a5639c0e7de3bfb383835353e372c9fce8508047ee6b2d73b419eb03f4bc1939a4ecdf7142274ceb1e54f69013626981478ec6c2028f558aec53c908f5db51f6af65748c13aa758f1ca37d94ba4ad13df5bdca9ca243ab06bc29c91e0a9d91bdd5d45d183a143c4e97f5078405061ea752769f634dfacc502a491d32ba4786f2069dc305e1421bd2e484664c4ed137fedea2e5c06a817b909d13ca5ec614ae0a035ce6853e03a552e7d8a075fc3ba319d2d2b6843463a2f8df6a2d711b12fe0d691a1de5f1338bcbf860b2cb5a93f0149a3317fdbb60af088f76559b3f33e6bd8b6f3175064c9917be008fd687ec60b2fc94152cdf1a61234e2f739db32c5f4a774a29a5748e465635dc99f33fcf3ce71022f0e85f7c41e4be17ba8878a80f92852e22f253a063993792d6968677b895011e77c61da0cbdffb20d407c96a44ee7b610bdfdf9f2011ef51610b37f8c26b334618f5813f037c29d0082f609c5db399a062961d0d170d108327d96b5e1c0cf8859705ff90137e82581ae09d490b1264669a4f7fb2bf7ab970faeef4364fcee75770ce8e51d9ece427a15a93f52c66c145d9751cf733b953c8828bb295cab0520695e798788c0256d0c489294d32178e3b3b79b447799c445ea644f16ac07d850c2f799c44998c89659c4259be1ce750962fa514b42c7f16cd2859fe14f258ff4b078c2409cefe83ce91ef5f5a47be972e59cebc6c912b20ed62d7016d9be4381fe105dc7dbb68ed07a45becfed2ee3f20fd2ac2361cfddd5970f1f4a7772f0ef350cee690c34e217631e923037b6f97c097e3565df86fcf811b9060f29d23098452d9bd8c81e5f75269def8f5efb08061c0947da1c52d0e7c0fe4012671b16b5f8ef2d860b2945fa6642927d1e5c051660e4cf60ec8dc8548023bf288038e3646d9479b59eec8c0234b4989cbf15ccdd7e4005d44727ccdd7e4a8a9c901d68035df1f90f9e5cbb6e5000493284261e01c3f62224c7487728037c0cc9b3b64a3e395e3549b311b1da7daeb54cb71aa7da1cf529ab1568b8586c3e8ff2bb1d070d1cdc0544eefd2e4a28d8ed7cf1c363a5e3964eee2a287bfe4a26f9140f8357f3f09808f237411c1d15e944086e3836435a08b48cde7f81c610bf287a62c8864351f24bba71a9e3fd68c33a443b39102539ca11bac1a0eb0653560be61d48d817f69c65e5ff3a3cdf14bbff40a5935bff1afb139c2f61b560d47c86a26dd23b3ffe907b8f3c8aad22b48bf26046727596b76920d657f96147864d5f2833c7e43f30c775cf496e1005b1602d8328f06f8f3c8aa65af31f3af21e4cfaab1c0ec9c825c0ed4446b0825b7c606272fa0758145103d80528b01931631180306318c517df09dc0b71a3cd97c5e3c3b4459647f2b933b89a806ad13b47620c311a00a8e238014d60a8e005eee242225b664995c0e51e8c00a306870610a2d50083305144b54968ccc60c10f9f122c18c204494431e092887ac8a7dc49444343780fd846ee2b5c1c710105072e9448d9420c69832daed859e256da728e41748a7b57cb7594db4e73ab2450eedbe4b422044ee54e2272e135c3d85a4609a060d162080a181ea8280114a2c79eb516b7c0dc57c210c20b914709191e525661833cd2c0f2f8da61872e793a89419e3c3011615c53a69ea43b3b1da54b4db20880f71660fc80ba52a9090e79fec9f35a0093021d68f872c511404823490b19484bd400c34c5604c6d195b4d846df36cc7a493a64c9d35ff8a109c56d528728629e5fade71991c6174a4ce1658c2ace40400d8cba6c41ab52431599bfe8125b28d99fc757d9dddd678705de4165ee737a21d3fc45675a2c8f832b37105521c6d012383cc1418cec4939b820ef3c81473ad2b4153179ec51432dfb12494c5c70830d5e647fb1042cb841862a53575c9f6520337b379dd24a96ef56dae5448d4150e0510a49a1d93fda4dbe5bfc5cd88af5e704f3b4c4ee9959d272b4ab63e26cf14a99f651ac423211a5153f026346539119934c849afa0a2c3db766bb952379442342094b9fe046ac2cb0146adbadce91f3a8d229eeec9022930e3df48866e81bf9724af6500ae54012d137f28fb04964279878acf5c9a3ce99b1564bcad03972ca238fb990b7c82e594a59434af9b35d862c25cddbee91010246609ac4228f24d2ac066d7b69bd2878e4b27f8f5266f946acf7168e80f6b6d16ac4cb93e376785eec1ef9605c9afc6267fa48e35ce6a4b9d1aae1f65a9b72ab7337c1edb93cafb5cc7c0c499a6149f6eed2a3b1b496993cf69247a7c7850457fdcfdbe30d0a1e77f0bcd1fa82470fd504cb12ece3aeebbc7eef81f47b1f942938d2162e78faa0532800a9c33db599be942f7ce1560cb61d3f41767866d74f51dc7ed5efbf37d481d29cb0b1995c257cf7f563a0f590ebdf931455dfe021344b710d51853f6fea8f0003bed9993775270f99ffe31854fd758fe9eca9b3b6d3c1cc9e3a1966a0eafb034a81312a10bf12d8be14a197c9fcfd4b4f9e3d79dee9099a674f351cd5d3693b79f6e479a727a098a1a18b674f637fc125d7bfdce9747a20329f3e134eeffa4133eabd7094d97b99bf7a4412b94ebf93bdf7aae02f6c261452f0c021415630ab7fa43e0bf5bbfb23a27aacfafe96c8410f4bb2989722f346640d683534d1130d693b2a0268888bf54fa7571d814fff334fa0e79d1eec8cffc79708bd0c7b61cec85ec8c205a5e0f0c8bca95df3ee7b374452f3bd611864fb31f3293cc2031e9b0985143c362991d4c7842d3818216ab2d4e3ef1d28c970e431775a3113c242e51542441ed3bdcbca281d885d8c750e6a2cd4974baa54c55c56df9eba20f585d41f220353ae4fa4feddbaaeeb4e2af06e4344c163d7b2a3ea03e803269198c7610b1e12b298971f11d5a7c2160c50c483914ca502531f03621a747a1f0e3bfd4cae269f96038826a0a0916b7237018586ec34b3725d6012ba0f55e8504972f772a3639723c1d1fe26c1d1eb4690a5089074e7c8976397edcba7d9c3d1cb5d7778e5c730cb38b99360d092ef8cf9f9a5baa7d2c7fbee37e983bffb2e10987e09aabfbf721173c1f2a9f717ec2c1fff0a338ceaa21e0587390f8e668e2451a1eef57c1f88e73b9bcd76e8fdce1ee8427d7b8ca7c29e4ae57971a4c21e0c42d99f87e7799e1cc5ec2e1e719867e7624694fd59b85429fbf39852be5e2ebaf7de5b87c5bcf79ec354ef85238d07becec0de8f34f9c39709a91f28140a15feb8e8df02f07b31322ed6ea93c880d845172bfce19915eb2d8684a090827f11234758f0d894f9b35632e28e122fd99f07cf92a1ecdfc3656442171099653c55e8427dab76c01d28505459cf7e40312f3f12f0ab5e0674a15eba50dfd98bf1a1025da820e18f8bf633618519e6fbc6e08e1d17c3ac703dfcd16201a8f072ff92132504a61f683ba77b0fec3aa77b0952a9d3fd0537a9d3d9af019e36af098f4da33f34fe2f17a5496809080813264ce638570dc53cc11d32c0434ef809b2f21a49dc7ecdd89451bae3453db6e34ab1aa34918af8785ca4b4698f067928f1f28be6e537f377288e25f8c7771c267db83863413cfc8162d5034cc7d7df00f14229f5ebd7004c63b96ee1df31d24ec142dd8380ec452c0d478c672b4fa42226a5530a2121d1041762e46e820b30b9cb5b1e3defaa004fa5fb2ea9a4d47441c9f47d15229ebc44ee7fd753ba378906a42c80dc49342ca9d5db3ea0130de92b0268684aeebd7e25d81fb73722ebd71566983097aac0fd49be44a2512997a4d2d33f6981eb266916ee24b3b878a5032516eea53cdaa6b8e858a494ced9a4cf283b15a8d491445cf4211b28f3690c3cb69292142bf933a1869bd4417251ca50d2945c4472d125924f6b1993e4517efddb1f90514a69c4f380bc3794579c0b3d4ba429699c077ce8828a29d278d2032c64feb2491eb9e82d955cc4f3955ca6072c54694869c88552ee4bb9e109577a6d2fb52e6858115a927d0e252941729f3efdb77a25e9855af6242547524a8963c90ff048b3cba58d8935c3adddeebe49f711c4c52aa50f1779a60f1e58005aac5f3bcb4ee7d41d4678e4e1f1af5e7f05a67a48a97e8a83000345971698608623a4908d387b18337c1003252960e85286ccfdc91854589b3caef284e539935c9821cfef29f26cad56414a909098224c1542c84045900c688083232f6088428829b229676bce4994a7184879ce39e78c89028fafd78e2d55a481945d47764f4307d9c31c917d3694dd5b45e1ad6870e4bec2451350ec00f7cbbfdf0ef85df47e758fa42121a594a98bdd9d43bfbbbbbb694fd0768ef47e1a8fa594be4710262b40830947a3c83f7777ffd9d27398ffcabda919b76db750f577a84176e8f6939fbe76ee5abb3616e930f9b6c8288ac3bc3ff2a4cced34457e537ff4505b97af7fe08c0c054a267e530930ca26b3be4ba2eda7d4fa2258358c8515cca38d7ef2b7592d52e5ec45d030a9a4c5aaa461ddb30ab6ec9326e89c1a255759d40120f5768081545cb00c533826a59a73cee92a99182c23232323239f26cb7015c6ed086c290f942dbfa1ab32b0a4db733fb9962e86ef331851c97d650c2bd92677920f48e49bda7e024c49e8e3d504ba482db5dde94f70051264ff215dc44599c30bfca92b58cad0a5908ba2d852c252a86513228769c9144422733f53bed2a3b418bec8136c9a0c58727fb2ee80e67a29dddeceadd65aebb60209469a6c6717f69f402246e0cc852d247191fe90131cf6029648c8590d8174ee9ebabaef8c5da45d6749248bac78ac65425cacdb06244828c4ae90caf6691c46f323add40374ebd0bab8d1b4d21014889c3081148a849ecd429278e67e7b1f65e0ce3e3c46654180369ac473ad78d5a2e192c85cffb32ed688013f7d893b78d82db42bc0a395b4d30c22d05208dc4927d892f1bf7f40b7e96c5a8e09479af1db1f7e662c8595ecc2fad1c82aabbe3f8555385aeef153a098e7fec72d0af0d8349997d907c7fdbde0e713f899f5a3c53f62d57faf7aff805496158e16e370c4f98b09c756febe3bf6d337dc7ff7bfc8f2537f5b0268e4d57b961320b17ddb49f503f5aa14eb391c8e46325681e32aab5e7e4056906132cb7ffb16b00abdb30a832aa84223ab9cfa2f06ec9c7a16a8ea9c027fa0deff03398ee3b8d4cb802af8a7de03477faf3f13fc532f637fc0282370cb385538934e4025b3feb584b957c19ff5a3ffeae7ab9efb152bc4e0676b6362629e7e40312af033f72d607b950a447956f5ad025f4c58f553e595e09ffaf9511709f8f6b70ac7a0bc7a1538caac7a0c8eacf047778eeabb73621effcf8c01bd5f81322a9004d5a35e158e4656f97bfb9d7f660a6cf00476e006567934928dacb2ece752f2fbe1c6d856e7d0ced6ea5bba18230833c0d0ea393843c8729c0482e90c1870d1a176a08b883580114b18c96c68595c3a1068fb7bbb48dea411700ea1f485ac85ee4920a794014656657e90cc821d4877401bba6c0776a18b48d73fd3dace5c338dadc9e282eb8ff6b7d0482b4f09663b37ff986ebf466c84ad749f73ce20597f403a0e0c9251a3548ac5c245b80817cd29f3e8c934cab2b692130d711cec3bcd25ba447de33fca196a7222b18dd34a960ee8d836345b094ac7b6a15a93464e6b2a1b36308b353313d36a6d2e1791211b0fd97f9443db50dff86f3a748c1b2515d92889a45194eea9590a512d952e6fa96e71d163ba6039243cc155862c482667c0021d886436dc88e68d7f906c1b9a3741320e0c927553b024f29a8b3e5d736e431eeb6a1b51148775f6df8e3624b6a2ecbe59c9be21d1362cd9a9bc440e1b6badc8e9b792c3c656a24388780da3a3239165a337c9f268369bcd64a317652a47b7227d5c882cdf87ba2726cb6fda317082321f909c447969de30e1f0b08b88150191cc8676dedcf04edff6f754b75a7dfcd45ae775215ec32058e6df60065a08dee9937f4f92c7ce4e0f1e9e954c0eafeb5eff3b44514655e359d0752022099a8c0343a822f328948a94643b3668324500545869927160cbae0ecffbb6bfde26e746d36ad56089e0dd4ffebd7569c66c58fd6fede230b90d9d68dd33e4525ca24bb4a5b0b41251ede2a25777eab52ec990da2849500419e0711bda7c6e431b158107119c3cc123cd336c0cdec8bef150635096406041a526e340d71432a31e4c2e75d8a43c627827c8175af6f7b12361588afe8eea50b951d9537de328540b70101751d9c70d580533098acf6ff9d4ba30e59c7352ff5182a067297fe5208d09327b08be80e56f3fd2d06f39e79cfe94524a151044d35cfaf89b00896cdff39dff63b339f2fc3184dcad1ab951b9c78d762f20e097f4d97ebe756765cf913bc79f0365ebf85370b68e8723d7ad63bf5b67f4afaf823fb5e10e17a787481e0079850e9346dede412ef707640c33b7d9cd8612f4ed9b96b7b03ba7faf65e7db928c39d5434cbf447eec64d18cc8563d046430c05db3c6b4cca362d8c8668910f8adf9642a4bcf9f2fb65519659e6241fa8e46a420d2d0dbb2501dc493e30c99d3b5fe9ed289758809039e0546e2c615cf9a33cd2e4242846342d59f41cc0526b9247afb1d4867896e0913678432ac19d94c46482492a109de4da8871058c51ee57cf394315e89fb20add33ee04f58fb49b862309e8f78fd3af60093a552411666c214b23dd259320d74e7a822527919953c148d76028d370032f7fb4a7fcfd2da06b30943bdcd9e4ec26960062e6d986d19ff3b7bf144c7ab284080d22db73df1f03b6e7282ee2c73f88ff8e3f8fff9c4e67e03c2f7a33b6b276c6e4b7226287e7456772fb353917c76d398c28a534d3a4274899524ab58ccdd4e50b6eab222d4292c920c82078cbee19b83f22b4084936df415a8424eb8f888b31439599ccf18ccd198451761c4f543652f303b281c3514e507584270b625ee69870b40056bd7f24f416b9bbe00f7ad862c6aa49c1a3cb484acdbb8c66ac267497116d56ed86d18d5af7c4cc7ceb97a60e219c21000c758e1b5d571487c9bcbf6b4af7c4787667a8735c87d6397e9467c09112d162d54250cba3ea8dd4a19494101ef489a205348f8b542845f5ac9ac3464c14028863718654500122207440c51344948005773301cb0b18161a355ff352fe53ffb782f43128f36d026ba6e01acd37a6740f4ff6bf189c790bd30f09d0253c89063d28a51c86552c1d3236b3d6f78cd9ccfeaa409d8c6f2a959a01719ae010a57086c054d6519bb1cfe237fe595cf49f015fe906f85a6c667349cac6666623346336ef6f43e4308a33b4b47a0ee748479767ea9ea52d0e4be1942afc2cafa5737474d1d16483831c780001072c3356f336886c5e3cc9f203195c9052384d885221ced04fd9028f9f45caeb3fcb2b394cf5feafa57b5231dfed8333933afe317f55e06f79269c59377a06578e71cafb1a8e532a9cfa2933a623acc1d8e6a74cc9d6626c06de7e64d5b691e2ceab11038e5d1e3fcb885334e21c657fd668f37c0c7adec262527291a5c5c5d4745697eccfdae2a2bb8e12760a96614461628a0c0e11ce945963e3c80a0e920e96ee69a97ebc19e749fba87a3c80718e689f1493aca3f6f706e8fa1990e65ba04eb651f32ad00336bbfecac8c8d080364b401b2110956d66a9acc211c299b908ae1e83ac9f608d1910bf0c383f05229979995781a96f8139b906a4b216ce921970e5038c2314e2cc585ce01167c8652403dad8d03afe36409b590dd06689cd0ab461d239a8d7515b621dcd988ef76721b1bc66d451cb6e63c4bd008fac283844dd3384d3c461f8fd53aa97f9db9da6e751effb84608ffb14f0ac28ac299e4ad57c4ac7a75e9f4a81384b7084409c195ac773bc94149bc799cd984df84bf3c6ff975aa98712d064393e48860344e389d7e30ce9d0ba67e8fec966d6231c758e3885cdac66a97370660ec3598223d43df8fd637cba8ea5ce711d429d63a3844797d12fb984e68dbf8e17e89a81ae21175d4636378c3831186f1e716636b39112513406a45ca8246d7655512d11d50800000000e314000028100c078422a140249ce9b2ac0714800d839c40724c170a84490ec3280a4206196308008400008c3184c086b602722f905f137be5a7470e736967ea9711edda4f1ff7498eb1ae9a57449fc9cfbe3886807955925537a29cb78eb8fe54914ab60b88752af6955eb0dd38e6709abcbb9ecd67e24015b2e91bf1c6a1476d60444d8e971a1b9e6ac03911fbe549b3fe9ddbe3394b23a44a32e9336e4ca5af622193885f578e5879b4cc87882dfa9faa00e889c1a627fcb878deb74bdcf884acf769681f9c300b4c99345871bd8ed270d38fb3846eee4b12dcf2b8c3ee6ae0502c3e58dc79e79a5c7e24d0c4630e8266b2509f6f2113cd082f46dd7324c1fa0ea3f9302cacfcc6f0af2e6f9f45080e1035b5480d74986014d0bb8453be0a2ddb9949b1d37d4af98525af429313472b326a03d0a0266a0589c50892b435d2121f1f1ec8866c9d0fd5be8c14e928569dcf06be18db13ace3a1c0960e4bfbb8589956d2363d920dc99e796e0b87826985c2d4fe3b94d30db07ed52f9169215b7924b670ac576c357a511e11f97044640edd64af70089a748243f3753122a6722b4fbe33ae72f41e2cb7fc2791ecef5dd09abd80dae3a458a697558c4c9ce36d2cfc301d2522041e93ebdb76f629cb9dfc6e13891e84b3353250cb97fde514104c1ad7b86ad56c59ad85efd4724527487cf16621cd0ce9145cd0826f4554e0732a96b69f2ad0fb4ffa1048e946b4aa4096702b5d28e6e57334cca72c2f22bb4330b9250c10373597b16ba42026ccba9f248e3c66ef01c47c01b384728249d6d26761ccbf2b6c52c4d37bce62a5384ef8b1e3da751288805b3e2fbf4de3e892450853a0806d1e386eef69d39f001c795b029cef5bc29bc6e3141b683a9fb658502ad6f5827e8f4e9cb447a30b0418e3ee4763fa37f8d9813296a6d1a61fa1ba19cacf00b86825dc550ddad46b7907525a96dc7fba1c6dc4e2fa1d49945575498e447e71db73ade13b3c30ee562f3f7843df1adb82c741c80bce996ac33d3e2d21e5cbbacde446ebbfa23faa7820840adf6c097f842bbb3251d7475de715542c1cd96d980be1cf25985b8b2e9802468be58de71b624239a3a4a3ccdc1533e589b6175e0031c054fe61166cc92baffd15d57cd9432bbc51449090ba5b04001ce4f20b833de56042224e0e145be7daf84b46c6d1506f522a0e90dacbfd0927a76fb8a4e9b1dccb82379486bb0605f744c9aee69947bc9554d288e2cb38fee432ea106e399538c9aa0584ca5a3bc953c972b019b9525c72e79a6d4588d5cfbf33fda19e03006ebd1ca0e8a37de01966a0f2156c78fe09934215c1459d88f7667cd37f6d9d30164b78d89f2269a87810cc979fb19aa859dfed2eaa092d885446c78aa8b31b9293918c90888a3fd84738c8b535259ae375e79405e70aa1287aa2ec19b55abf696ed512299b3b1ee8973c069684df9141952a129264989dc54df7cef54f4cda4894e6a14ff817bb5702729e248a89fcd66fac019a66fc8a159536c17d5051eecb0fcd9b96d03fe438c182d008e6af5d78219f628e0e3f9ee9c72f56a5658d0d8b48ca8b084b31143059b9486b9033704b291204d9072679c783482242e44aab0b118d8c2a3d6f03197886fd429de824161a30e7c0d29ba8e986a89634625cb7752002ae45dc52ef80adc5276f3f241391ec9506d4ae6a30b7cc2a6a2ac633d69b8a09074a178b301fb06bcbaa993f9e9c7e1d057b192d4e157e76da3cdb744b56490dc6eb4f4adb5efb2c12cb1eee705e70e131855bf461b67ca8f4b80dd03857abc75a20a8437c805810e0809ac575717501324b23edd4c50950622069605a22813816d9e9a81bc65109b395e44024883e5ddd3c844159204396dde181ba210110d24b6c6ec9d6f974d6537e6fd4081995dcaaa044c334f5ea78107de9f8145f89e4ab7f63c413fed1a2df7c239af964448d72500c501000aa1e78e3c4884c96d1abf14ec8257f9f9c06078c487d9996b9feaef631426801565bd423ab942ad2ac5efb35aa4f117c59e675aca388dfc83775246c0645ccd164a909897d9fb80338fce430ee19d9313bb21360b9d49e3c454d50780801480f014e3f09191211c5a2a06d811d0911d4a842f9ebbb2061c387825005461dc844d3f482b6d4d03409dcc5d5f4a35d8a4c6399d2a74ca6c5b58ea00a7b867651f16bdf66e6f08f10991a910ec3c6298c3ee0e466fb8bb36a6b2a92561ae819268977e1b7276eb1a96b87eb27f15ae87f3396676482ec9b1b2dea91b2b307f562c46f5c15e8a8260d9474c6263e0f297d8a7d2089ddde27d640bc69958de74efab8e14a6c2fcb694f30c62d7348019e5b9cb4c935ac0b4c02b6dd53a91f7cab87b562306b8fbef173b6434d3f8830baf78727c54eb4b10918992a2b1157835431902a9d4e9f75b60a6f3cae252eee8c66d7c256a839dbf045b91b491ccc3ac91b97b78b8da9941ba5b89ea729d9f7e4ca31df6dd25798cec1259dc4633aced19b3757b195010210db8b985978a68512e83c20fe9580cfeb6ae8a04c243ec404a2234af1ea46215d22dbce5a55fcb1ec636dbbbc8118d24d9127f1cf4b5967160d11c2946a19455ab38452f5edf182794040b21854105ace0e4223b3950ced401f11f90a9f0e3226dfb71947a02d7672eb4bd0c6e2f6bf68bae1b1913309056e81b01fbc55dec02692977b0e7c9fa9d522142cb88289270b7ad74aad7b890e28e17db6a434f24120f83f299cad2115e62e7777ef652c3caaee4506acdbe8d412b09c617b06ead72840de019b83d44d89ea5ede38583cda42831c393ce99b8e739e03a2ee05050be466f49fcc3a08f9f058913a03d016dd0b83d8332eb70300d8902aba176419af32e7fdf40337c6320f8c7be328ca2dad08b2b888e6ebc0e6da9e58eff4036f6e29a1ae93b269d03f5152a40f7b3631b8937d49faa31a05266d55772726382a692e7d4e100a7ca238736f688fb752d6d6ce7dae6dee118dd5f35ff824c1be089e799c5f4344ffcd96902df5680a2b2662915378c39660355c44d5df24e8e44f7176fff91b71d4deb3912ec939491cf05b768ef979bdcbb58db7e1636f7be8c931f67ee9861aa53e34c2e1fd99542ff791fa73b31fdcfed08d1ba8c4b21b760ab86f4e1e96fee29cc95b8a61a684a3c02b1b0c3b044c3a9af8f0382562fc73c4ab1008f889fc98df2b4efc23ceefdf32010e7186d2278ae257c3bcab35b86bbbb0d885ebd64c4cc80d379ac178553a1e35cbfd17ba5baa9aba83ef5a7681c1edf2e269cd775795c0d3145bfb70476c42812ac6d34fbd7610f0eabc921d2b3bc65e5bc3043abda685a18751010edf394ff0a02953db571599101a63cd09391f0d40af4c385e2741e0ea9d19062beaa96bac6d18f49e2bd1edda16766e6cc173f884699f682b72d0995098d0ed9e628dbe147eae9b99e6d20e41cf122557522d5bbf61be49b0cbb2ca50cd6b9c8bd46892c14e082b4eb3ac18550704a7fe94511281dd62e9cf73b9e523a0c9fec6083927bf5f35d570207934079894113cfde37703f702ed24dffc845015eed19e01d3547ce3e970e7539536d81f304d70bfc49eb52a7ef068aef7287f018c9c37afdf4ff9fbaf69544d574ba9cd80f8a0f152ce75d114fbe111b41df44c44db6c335eee0f37b956b67ec0cb5953a45720624237bd5e3392273b4350d0230b94fe96682c3016093895dd8ebfff25381d1722c02cbda80f10f93b00008ddbbfa90dd6d9989bd07ef52d8977d424062a41998bbe4172ad440ed3c8dde77d746f442d78d97d4891684b6e8672511d843e8cda0c6dbc659d123ed53cbc2f4ac375c107cd0311b4cb39adca322db1a89f42c296b7970638a3cd88e0db425b270baf645b2c5fe02198643bb371eba8e5c761331e6c1bb92eb76f312e406b41a47f187c694e0af7fc2acbc23d737bafecaa4a4c365c755b78dc62157a23bdd99b3d333dff7c6fa402efa8d2d03bc09ee9be0a1a9ab0c0826ab449b7bb80e8834fc25f8a7e95287efa2fc9c92e8a7e557caede99451e7ba07e8006396e4a950795f29e395903a7736dfe10e9029de557e933e5740818b860437f13d3ee4f8ddae8c712a65a934cc9bbf004b8a695e1a31d2269f0a9cfcbb63f6f840e470bcc02738864eff62ba58ebca13bfe1042370734f4c9ceb9bc0533bbd12c863cc9aeae7fdc1a7be5bb6635657eea14b29f4c6768baaf9a19dcea6f0f703ebff97e6b3935426e404fff442e4a5592d76dd09848953da1f6d6335fa7587ce91e75b67f8a2ed39092b1e5a763e701ec82ecdb3d00f0c30322f069cb6b07401943aec782b47ac9eaee009f76ace58020e245c0d26e413268a823354b6f40fe3e433c09138446747980cca80271baa726076aa80b67b0efef9fd6fdd472372eec55b8516b569162196378dc9d3655ae1855224b0061e648f7369a31a6179cc7344aa17e913517d1ad49cebe93295b7e9520549c7896ec098dd535ef4c5ee36305ebaf1b964f8b66d8321211964eb46ab6d271077b19562625acb1a6efc17a44f76f5356d68abe56fa8d6c646bb6d2f38dda42b610e975f0920aab86793100ae6e5475e4c879ab7bb69e0a40034d9c5318012059320012de20bc71c6549f229e308ce170c8c4268443763468528870c424022d64035fd6e6e85277eaa195063fc371548e57358c4d5a4ce539470771b5a4b7b173a7a5df11e771c8b1966619feee80a02b57fd46bf0b14f8a16fba8af8362df6857a96585883888be27df52ce4d3c30b0c8633969742038c4c336733cfb028eca631898caf1fbb0c216eb740db0078e1063bef6582f3db739f15921da9846c4c4c238a80c5598b23f0d1d261c9f9156c8fcf5c4a20eb4a06cc25b5ee803604969b4a00bb39d510a4307c28111556e222c023c7f3642789cd0edcf17a1de0912b028567bb426151352de3a04edec2447abbc9a4f2118d2bcb809a0ee92b004e593833a6ee379365114878790d805a3f4dfcce0d38c796b78f022df1d8e3bdc6ec899431913bb66c4398cc66e27fe48bb87ef07dd89e993fac7598a3d599da10c57b8ba6b5bf178f4f13842573ace87a1646eb26f03c022bd49958ef14b73834df9c62ee813f72040387b0fd94f591e32e5641f6ce2193ad26921d962174c8e8ed1837504a90af669c33c2a5aadfe300e8801458b3b7a28b4c850ee0bcf9878958c911098973c366a8f052e12e94caaf8b91d4443391052dbd6617f91271dbf452d05a33b7d2ae0b4866a433737efdda46d16fb931f166d337552b659ae2155f067f2ed87496be51805ee2b3085810df78be40ca7460223e6f4deefa69c7bea5c8eca9f51512f9a83620a7e5b209bd5627ec9d64800be0144b1eb8b10c0896b8b119132afb1f51102b5782a5a7c960459f44a172839c923981dd941ed83ec63f1c3f2f7e755ad19bd1b74f7a8861e92f3bc9e179c540014c106de3ccee0a517c83c6d4c45246a4b40388ab7b1986700aa71c2af572862c0701eb909922742c6555a48a882323c44c9ef3c9137e027ebc0e301946d1b54176e6a58a01dce485ce8fd8ed5f830f49d5c6f61550fdb0f46ab5d10a94f0816dde8fc51dd831dc323b467f79210da67d44de33ca0f2ba597f0cd09768e453aec883810c09c2a3884372b025e3f48f1ddbfa4edeef9f188487296cf78097c25890982c743ef96a93a33a638223309bb2c8e6ca33d0c3b1d48878b88ec36b4ff6662c409974b56e676f2b7d47ca447812f1df15d5f92405ff93e6bcd08c8923ddb8e65712726bff0df0e4a546b9bf087308e86f92e6540d83d4cd0666d25bf321960ccd9c2684c9560002d6ea27942f2aaef19c24bd49dca9fd48927d2961f0eb6c20b07c41d9503e55cae30bc7b9a46e2ed584793371173d35b23102e91f2bb88d25361ffbc733ccb66ffeeddd375348803ef3decac492eacef165d803c8b66cf6067c86757c5b4f9758b68f41e7d5f8339a44ebd15d8664409a083f9d96900d93971f8b6787833a4110d03f69c4b88799492bac2f1fe7bf8efe7fab3cfd194708cb146099fc37e55fd4a9777a372593ddd24e7bea812a6584ad2ea3c33c7a9053319ed436f2d6cbb6a7573c6a491b975a04c08446ebda5ec520fba2830b2a16f3a7f6e5d218328495e383adb814b059c553ac1b45d17ca1aae290967b3583343726620d2cc68478be8eb39a3c6bf1e186b53a5538a4b84a9e9cd041da5a79409830840a17c842bc45481c032a2f716078b30bd8e127263fe55083903c749dc52a48e86c60937a3e3bc015d10400b3006c528a55e0c529f0aff3d0d38c1a1c9d2c914ece6990b2eb731abafaf744a19c995221b4d51b5f870540225b55c796851a2f295d4b2762814bd83142c2cc09ac29903d22bdeac7ae74cb34414d6c15e870052600e281493b430d9b6775b0cb3260844de84c1e8e0b08ff4b132484ad16da9d58f060a86eed50e2908d8c24c7d1f0fd5728b3859e076c26212fe43c26db85c9a7c9bc090c4af9865d28a6a5eeb367b08d7d81c178324c998b81351e4e8f0991ea447eb9d63fe096e989770338858e2339e50a1594fd12346d19b686047326a489efdb7a553966b413785afb67fe93316e445e1969401c0453f71862776312cefef2c645d1db88dcf0f503cd1544c1553f33935c17c7f5bc4e47a9c39628e654238dda6cced57bccad93157761b85b2dc76e82f720ff7cd9af7caf491442d1e949d4a38062c4b9e0d6016b218122abf3e7d11dd1a2167f017bad834e9d46e51c3aea6a3f529f58e494daf30f98b2d9dc5a9fff2114b935e952c3229ac47867a24f331b3e61e6e691166c8b6c769f5c083818d0acb2745a4b9692752bfffbfad4aae9953e8980cdbc6daad9134877b704c61a8d653a9d482be474e8cc0f5dd672ca1f0d8615c54092065ee2316d16ce29209b241d5266fe6e763f4251d8d9496e32ca47ce0412e499a3aacd83d9a30da96f6ec2c4083725dbb807cc282c7de2118f93a92c4975eca0e1773f5f8c9a76fd46efd5b6a452a1bf9d7ae4e9776d5f079841eea711af2e404abaa4df33e4f2451ca4deed5e506e3cf5cb4dbd55c16631cd7b6692621f51375a64a3ccb1b2a477e0aa03a0d1bf9f7fe3f6c12fcba5bc391dadea018e3b15c6e95a304751cbf5b1d0b09f1fbab51197b0226f3452b4c86ccef8e7ad604adbe534b995585925210cf276c6a223ed85400ad12004a1a89a8e5cdacea0d44d804f481f48ab8152582505e9802ae7bfdb08bc9ecfac3abe9f622e07cc0b3d7620af19cc4442ec5fd33344858771004f5a58629ab9b2c6238a53b2101ed0a93e0824b674fa7171aa5e3415aa48360cd07dfd17b0380cfc18f627eace1d2b9b92ad7846171666bcceac1c2cf1b81e4264090d65e2ddbe2c048b39a37a2cecce3bd36fd1f40d084f27d6dc6045485be9992ccb4715ede65666b6d7413eaf383402b3ccd675f0d75613be9595992e26b01c349ca3b018df11a6aa06a422009d1ef505bf247a3e1e76926e8de5c93d2232e335978d8931e45fc72be3478741b920c19051bc8c076bef6d35a282a3b46df40ab55560041781142b8ee8c0c61c12122512a127e92d77a2955b16d113bf5320c8e1e2834d724bd5ed38557412f14d687f138285379fcfe419b5ae068150f860115b297bf8a50a4ffdee9c0d91016aae92923fe0189573fdc08e1ee1dbe7742afac158e7de1d41eb979e7b11f5ed1ca560d3ca4a71ca539d048c94f156f1d0dd9a88603ecd60d7c751e63c7b22e4e4bb5ef878ee110c8d18ef01d36d61659bd07ac353c35f4eb9d0efb0b7abcc0c1dabf8b460e8ed143b57d2c2fffdb58a0c337a5dcc2401496621f00cb548f760ac920a5e8e2fd435d72840d0875cbff933a4a356b4896db47dd3c5904ee03059d3e9e445320f7eb3d96328e00e0653b822e2eac25afa62096c5a3d28c52fb7e4e8a5a7e2c017bc3bf90ec4cea8b5295450c6c5f66d4c02714ff1116d1cbf8c1da3bc05c3d9a0fc42614b7672a006fb02047d4fa73b8ad8dd502dd162ab2edf10a6d953231fe7917f719ca0f4fa6eb5d854b5a14873eb900ab1b6f62fb5d2817d9c5073f1be900eb620ea46ca1248ca497f56e2321403ec15facaa76e5cce455826d1131cc33f467c999e71ab81d31327975c7b2a6e5713a51060e0f9a92e52f50be06eb2e6f768b51e8bddd00d739a43d3b4a918f781dcc7599d79887dc8e47c7bb62101ce9c5ed4f50b4b2ce501c97054a2e31e2dcdb811c1e4147dc3a38d5caaedd755501b45c2d126d3a10e5d2b373f3c2927a5a2ba438a506c0055bfac4ed40571fa68e08ce4da3233808ddf525029c92266f30de1ae3f63cb445591b9e5281b261b5b2a22bda69ad2c1a502d4d54fbe60ac8eb5b060468309b2bd602f0464894f87e384b04e2de46c4de03e21d965713d1e0b211a5559ee921aa582b8ffaaccde846e9896f1f9ca30efdfce600bfd53f7b0f7a6f757be0cc93fd14cf26c73a92c2bc427449c438ac1e500650a2bd6c2c0dda13925a1cf3fe3f9f3ba5466c8f421a54e5b195215a695c7099e07f746d49510eecffe5d87d2316d099a52b63d732839de11d4ffa90ab055b5da085aa0a966a3565659b5da57098ff006fa0465c1fb00bf382e714564ece06c512d6bc6f216e74d5a47410db394a85c13b014147ca80905bc2f06be6555b15b508c03e72503b75c96a00d4b7949f1183931520954eaf0ed1e1cf0b2cc70d99c4b67a0fde5e1967244192dc155c98aafb44d90ae6f3e0393fc14403a08f70a730aebac7784f35f91206e8cb73ff238d3587288911eda5a289d86543e4cd13f7baf87448f88b63cdc3b3496f0f676e4d997ae2aaf8038593b3509ca9ed06553605811fe99a04201db4236deb22a698a29c68f36ca82770d8d14a7917458dd067472759a6daaccbb9f1ad3aeddb588a165ba0a665b8a97eebe0b69fdde3fc2ad1853344016712df630aa074b9bd4e81d2d9e4b21bc8b9d62aef165fc6005e14f900e388fa7d7c2c8c7624f9c5ad769addeecb1cbd38e109b526c346ef046a6dd574769948e71a1ffcc7fb94ed3ac398ed1a7eb347160467dd095f2ba66da4306400b5e49cfa9f96ceaf2901da946f32fccd191442045b8111fe95d6dfc098bdbe38ab3ff756cd0393df1f57bdf87b665beb2b9f81b719fa4299726d911ac3eb14b22b3304d1650a1e340ea46d7b3296b6d8870d355f77f0654b04ceb23c9e0ef75cac20e430cc3f025134ee9c7244a43d44e38aa604f48e3011f98c33a32a99c61000db28c84d82ab3f9d4dbcf4dde02ec4a8ab6b837451e309db76da39702db804873cfa019b59f30cf8306920744ea87bd99f5ea08499d7ef15959bb592a13ff61f873a23757a46f7c635f9ac8304d7a8f0a9c8a8f8b5929fa4756e3dba981492f6ac19b61774fd4bc4a2405871492f6122faad0ba8e1b103591288387ab481a0ea3832d39de102760058c406acbd9b65df88cb2a3b0f5599c4524978479e880de612271b369cc66ed1ae99d80b132b8054ccbcf44eb09451c6313dc524700071bba7e048ac4314444946c2d74993102559eb3573713c6d63c42f65a8bb57dd873ea7e0bfc1e1287c45310ce042c162999c84fa00989beb2691054464d2fc5c96158b6ad1b59a998b71b8b5adef26ad588d6d5bf206962864c217bec2c2a95a65fbad5781ddec39af36c088ba19c7a01c6cd8efd59d34c43cd6aba668e44f7c6ae42dcf2c3b01b6b8cf25bb4e87bfed6462e66ca256b136aa84276bca9de0120a3145312543568282582e447872081219549813ce1095b08c7462aca0fac286437c981e8c8c5964941a22876046c5c3a7802cd8779da1ad84bdd8a31d81c006ce11ed5bfa289fa1ed56492e01247e0ea9334010f676586c5a6a9d533ad6b21a42856c3b74cbbae980b3444184f21ddf42dec291a28738dd8bfc925b9b6baa3972140d4b2df43f205fc52301f3c43187e1d01472b31ff422d9d580e996a3f2a9bc26345d7f47a9b62b1bd7d5e756d50baac3dd9f73c7293da33682c31b03ba972204ca04d0a11065084770601c300ab7f6c403ebf0dae73d9c69e7b64affdd2a1f0c287a423942da89cb98d3565970723f9d6f695a04b5eede32d2d585c4442a25f078428d4e7540af9304165adc2f547998ecd0b510ae49f34720808829782894c671fd8f24e8bdaccc8ef93b6be9b67b37c5955bfd050e1e65e42903d3560c3423fbe9ee23498611633cd7d281d29f67a0be8a036c84e4113bc150bb9872373f83499e0808140c5ce127a14cfb40226ec24ea0168c6abcd7409df4432fd9ea44cce797c2fb89e83e985f4b3a62e0914ae362e39b884dee808931a080413a4c3d8a055b7c9f4e461b5875c0ba507fc2e0eca8ad78e7ca550e510a2deb5c4b3e6d2d963ba08a49d8fe1b3ba07281ddc234f46e5050a6c09dadfeb1283a19f02c81a042f606ede252e0be0a49f33883d269782f5faad65a9cd1509dd6aea5321c2c1a2cf300a4da6ddafe0f50bb62411eab1e49800fa3494838b30578d6926911e64802ce3756dc5b0607008e858383fc02dc81444fee824e92571550a544f92bfa860dc7fbcf96f0ed0d7ba011e788ab6e0d1f7f839d3f2926af360d8fd9e276e7d834666601243fbb098d22042e8fbf3e0a02355a106705d5343db13be5f040ec3c892cc7e3b6667f904b32a485b7a6699bc97c2e6be8341243f0697c32df14fd060a1ae7d3f28cd129d985fdaaaf0ecb972385c9251d05697d4a95434f2e84c933c35d672001fd2066023b5040ed1fdedcca6801922b301f7b2f577eaad6b00c3b778a96ab85112b29ce2b6d37a919d263060b49cbe21523f8e546fa0bf517b84bbb6a976e2123d5544f94e99b0400f86c6670970896445d00b966494a7a58b62027d0172b8ce0760f3dffc30e1bb133fb017094b16ed978422929f97b604b5df52a721909d4b26ea737a870efa1f2ecee0fe994e8b5275d8d634085c5892c22912ac64be5237b40f00a09b13f5698dd6eece0d9f89cb5d70c08bc6775569a0c74a999640caacd892944dbcb3336e8941b01a1175f57bf27d0e872ddb94bc6699c7b8b9e603ccd401c11ce7e0f350c3d38bca00e3bf5e0842057615c1e879cac89f5a73f2d2faef41ca599d2387c56c913a73435b643c90624d2b744eef9314a3add7b85a61d8022b0a6ce8164cb3d481b847252ee820fe17fbdfbcbb9994852f844d8ee5615723c9393e4a596d858347afe0dfd9832e5f0a80ec8f6ca61bf6841bb60e7ca7cadb057bf41c8b8eb4d7fe16008ae05186f5e14a6075f4e6df9a1875f20de209cc33bdb85a48b00684922ad2107076981807f010abc063de72f97ad500348c0176807820bcc242702f0c22bc000e8c558629bd037005b5af8de32d34c864bafeb845a5bbb621bfc286f587500c55c10fe3ae4a4a878eba0b06528e97fd75ace677ac0c0815b6e7a2baf21d1457e3edd35ee17842e5766e24ed9d268c51b88614f71b5540442f717fd35a002d3abe784f2012c912196e16514548aba933c22eb130aba5ee2955c387856f2ca0dea65fd01e6d0cf1203bb6ec8637be758bb44d7b75a31b6d66ad7c020ee30f4415ca37e0e05197b2dd0f658576be0ec21646b3e13ac1d515e000877eb876a8f34f51c35e94c20b4da188e5e2570c890eaeca4835615b0386057b4fed12d501e211257da9545b68d1bc782612a14cc1b62af5e469c028b6dfed1299ccfa9c7f0467db05a74772d6addbfa430ad56793eaf7ac4e6a6cbba1c7e4d757ae8ae0a24c933b57d5e05cea15c682eddd60187ddc207ce588497c1d948000f84d206e1d1cf3504c4b9cd7a538f573b17a84db640e95765e385a7bb082cb93c92957cc68fe944cc6826d4da81aaeaebb68831af4347b4c859b3a4f6192937de8ab240d33494b735382ccfdc4c6fc11152e07970f98edf0c8394be5ea318cef0eec4f8142573643ad5fdeba63dd0fbf0340119567bad5f180998b633623630385abd1010c5e4c5767cd07c0debfa8a168a9fab9b1719427990737d0acaa5a56fbb11c28551d43e7b18069911ea55dbe525f75660ba16cd62ab645fcf4bd873fd3c4ad3c3981729a22ef371a7ed9dc5d78ea2cad42c00ee198d79195505a5fd58013dfdcbec3d395f9d400589cdc53ac38c1ce0fae2d5bf93a906d494b82846239afa937693e9cc6be2e35323e0e45d35c1bbb3dfaf12730baf0764cfb15209244ba567e92f161f8a3096eaaf1211460870b80a8fe2d5c1387689e696a0e040a0bb4245cac088676a7a7b963b34a4d146e768bd7e40a6e840ed651769dfe32f61e0a518c34fd3ae75e1223f2d207ac169c82d1c376e18ef8cd688a38e242167ee26f2aae89eec9180e321204eba027ec4fef032f65a527460119ef46b4b0743a6b3515870a46166222835558b0dded55c5c8a0f9319d5b469577162883e69bf0cc5e36df91adfebd6fcad2e36ee8bd7fe3c605e2fa18a14cec503749b8b045521c4e9b1649df291d2cd25c40a17a93a13ca5ae299e6923be454b36a9bfc3819b1952a9bec20c24509c6640b0924d35e91ec8b824feb84a57920f57469a2e4f42d959bf16d22919109a6827cb46a78ff64d27902103e78543639e3a7b0f0899f26dc8cd3e1480ad58a2953ee561733fe1d06d102ce8c90479bdce2d3224c1b689479b76d83fd33bfdfc36e39dc7214b50759b4886030348bfbca8b130b05355dbb3fd375d7f7729192a72db4bdc207e8a664ff753f8e2b7768b0ff67957bc14cff790a566361cadc3367fbdbd08dd1c5e785a3692c3bf24a4778290a8522302978970e5480c690cd3db2e75bac7a712eaaf9ccc6c61d05d9877198d4a4a33f52e44294b349c1351f3fda863243492a9f3e9dc187a1d42c9196ca9ffa15af12234c4d254d0678e4de910afdd4e547fe4103afe7c6750d6cf0a37093ffb3fc25dc01ee955d57cf3af69f02c0215df68b2d2c1d318125a735d803038385373eb742e50591fc800b9a799731e468c8654a7dbf43ce86684f7c64c8939d7f4b563f9cede26fbbdcc66aacdecf603ceddafd6381331fa0ecfaa1440d500aea27805ebcb4133a74965752edf2d63d031a45c07771566c249f30391cc0663bb433920a18aa0e7ea63890f39512b2147b8c98aff7990e8bb9d8a24848507121c52875705036404aabff60b65e4d65c22725bdcfd97091293eef8324698e3f261e6894712daa53c7d49d8fec450d7c41d43bbff7ef6e2567411ccb9701d8fcd004dcb8f039d5ff923a03e3dfdb003e1bda22a866c99cf4e280b16eb6c0eaa95ce738356130de58e414da7710a6ca03a90023ce9c1d359720265f2c647248d749448ad8ab758caf49f11f2bbf5b4edcf6ab5dc00d4c2fde080e45981ff874435ba35a9139e986179848698b481a38f97aea635e5da1a1b082f17fd68917187d39df9e920322ba535cd1d01603606fab67594fc4d3565636f0fe020cfe8266c3a05cb0b2e011f2568d933a027739c86232423a39559489aee0d2f660da9ed0ef2223b1a5d5ba0186d7a6b630b147e0c6d21a8d65cf0731f8e9a8bc328121f7a96a60660ff27eb7fba40de49318418921725f1c37b0b1208689ae4f7f1c97e9b844d2c2f5684efc8722352469621aff339946e0235c7b8c619619b4bf477e1f7e5af4751027160be6706e30a573e52cfca2db45e007f46e3603f862513dfc253cfed582ebf86c04783a8abce33dc66906c6ec789c13ed3f3e61a6403edafe6b439a990a94712a8da862ea18909309f1e15da8b8812d95fde3b4595f624589fc045d309f287b5a5f22d8c50df6b7c9d69355a9dc9528f1dca2be79b24ace35632d002c45a672f295a85926aa9abaf421f6c9dc02418e68da3dd6303f6dddaee5068f014720a9333690e967c52541577ed438df38f8840c54e6044c7dab9409e271a4e0971f41d769a6855690171940aa8e630919007ca7d1b531ba2a28e73c8711ba2a16ec960591b2735a1df2ec8a5c4e559efeb618119f1949763f8d3ad5d3c1a13c2cc76c65b1247550fc56a98004c10dc4aa26c71820d1a268b7f5f1a8c9c28648130b77a340b8408bae9ebe062d14594a7af3a2b66135c144c1958109833bb5c082b349a0f88153a60f496253607689dff9fb5d0751a3990cc9ee5369a09cf11e2e6ef4f2f4fbc485c10d2b599959946722296741ec315d893d2ba0a469086ed0b704802be95535b4be6784b3033665f6d8ac4708d8bebb1499f56343eb4c018fed659f153996de7c5608778acc0e6dcae102b5109e4af790ca528c11ad5f22563cdff474eb3d205a968c5ae89a1ab5185c1e9a83d9bd6518f64f5f5c231188835a8076a9848d6bc6a9c3239432a4b3dfcb83d109dd851e4966d5c11ce0ed56579080ad984349f5c21fbacf54a533029020a13abfcde1e3cd4c08cc4aad4b7f0e8b70f2d26d9cf0f45097ed96d749aa52ba5f269de075f58ba5323d48374bfc4a69aeae59e20f9da4e0fea007366e811d44f89c0ff941915304ec2da0fd40e087b0b010c5e7fb3a1b15869328831e09b146f928bf84ca0e723913e65180268fa183a01a967211f516f44cd23e78fbf11886f6bd31518cacad4ea292b9baae82157a7f07a1f9c921b0124d6a1b84e0f4e94340e22ebb9540445c2242c26f46e121c44ca739770c21f0d79bfc1f0a21b08ac1a62997329140b9ac0ee2e4f0abd1841097c0e478cbbcd7d87e3692e3bddda51c6fe46172bccb2b49c988f4cacc467761e9bda6d8841020798852d4cb487afa90a66044843633f42aeda7c4634748263a826f7e6c716bebbc0282bb9b0a2bb64586510255f5610812b553560e9bff9216065f69ff98b329bfb08260a446c0f0392df4a000097c626b9704fd374798b816790d1aa980a916b2a0f5c81b4e84b1439685481d19b3aa951affd2a10164082260d4abc85690ffd88a372a6864b50e43046d78ec4543cca8236690f8ffc224531822318319fc74f1b5283e4a06e1f597b0e7d2f88ceb3edc721516626e346f0467898830e92e4e65f5930538fcdf08fd902f379e5586b39c844e126d34f96fa766ee72be3022bfc7dc4fe19a18267be10bd41cdd968144dfe318b7df421e5b9e790cc743530fc33ccf10553a9135ee8940f3de66ebe6be97cd9580f3292cd007ca8adc456ac9227114e20210efaf0b0d75381f7a27ac2d1229235b91def09acb551059a44f015b1ace6bed269e75b861084ba3828fcd30e61a1aad05388251d48a61e64e14a5ff68eba8632dbdd149123b8987169fa3c50e95b1b8a1e6d4cd79e591f51f4b595b6ee7f38a0601caedf274b853c2783a1e2def7525a03752d44f82e6aceb53d7f158256ab5ad1d4fce2ad6283d4d4a5171df4a09730ac5c6d5ed026b86dcb5bd25f8829f652155d813146d7b4abfc744d11c225d14d2a31220d3978fd5ef2175f1e6aacd827117c312eb12319bf905c0741cce21d6e438f6af9fe1d9e7236d1b317677427500135084c6ab4905e7097c006553ec031101e762eceab3cf011359045ec35ea43693a8dddcbc72052f1356fe819b051be42ff1f1de48bd39cff5dff63e04898dfb6aeb5750d93a9c5573e1e292c707115754b38e758787066dafe481630eca4e256bbe328f802c2d050270a8c69a6c96e9a14386c8d7936053cb37649adbec1706ba86014770e8a45ebfcead2b85174729a8e9f7eb76b56debe6d39075c6ef37aaa4363e8bfd7bb764f7581de99500301b92f67c5920d396762ad99c8b3c623149741e176c7165cf137f84131f57c9c08360b39e386eaf939c1610ec72b1d514c26fcfe0d4963f37da06af3dc152f6d2890b4abbfb34dc3d651d6188856fc9a8113890b01f3deda67045168e1f1dcc01b5fba8fb95df7f7d6c75698647deb20252d6a6ae7dd6065234d826682abc64f8da11c2c73cda8d4ed89a8b86f916417dc94ba2f7bd2a7a8e9100c8411bd2197dfccf0a5603e6506ce762e817fad0430511e5a13967482a3ffbf92c60d826aac19ced544d22d1288d07d9ea7d460ff35aa2ea82a98e79a9e2bab0c28604167f1c1878fe4b4ecf3bc36956798e676767b78fb3b515a75a34a714377f983100764b61358ceb4152961443ca4d2d334abdbf553550eb92137924679c852f16815af77b2015ba0817b9a98ddf3ba1d11879dc87b09ad52c8eaf6719ee3e654c77e924eb1a8b8369e58545b46c5d8fb2f30be78f4aa6424230e7970b50e303819bc7762a96d9a79f354ff7172d117fd0acd6bfd41377f5dcaa4a4e0dd397d8765b159f65492070c3657527d8e7ac95cdde897d54f34eac746bccaaf12b41f0c3254ed1f11a3b8fc2fa55d3e0a82e84cff29b5f3ff828772d1e2d0009c65225fc98517051c4b655aad449951a6d09e41f305190bf952a5ca77a11053bd161b0ead1330ae6d7941a8e07667d5a9311dd65879dc7a2857d6afad4b359a66943fab6de1b2e5651512a0a20cffc7ebcce74d7f210582d414570c8dd5ed4671e0088280fbe57f599cf81d4fe7edaaa9bc68d83f8946afe4d72c5bf4bbe105e0e576ba343c0c46ed69c37b6249e5730d8601fabc689b939f6e9111e782143dd390bbc369ca31f33de4e6bbddeaf88fb45de23c181f3639a35d7687792605f888025561aa7022d7f75ac4f46151654d1d90f902cfeab15ba5e58195b0c5a73b24931962ef7d281dacccb2ddb9b51b93461c5e273f6cbb926b5ff32720c67b3146e9e38a173660b798edc4c982f3c5ef0cff9f9d26bfc40c436ec346f1f728cc50e3ca14da161ef0d187f4386a308ae7e0dddc5d844dd73df681dd80949beac709d5780a6048bac8a36a0f008b590677a2a6c6a1626d30b4d074872d316ebab519bb9ac070bae0a8b03d9acfd5a1a83192e8562fc099638fa5476ec4e0877dd1b82d59065e97389b324b9d25fcef07eb24a630c8e295412caea10b9be9dfe867d9ae8857486970f7ff2e57faf7fca378fbef8a9dffb11f7695a9abb392e810104e39a22c239411d1fd4a05f7b49fac2117aa46247348a90c21e11289090921db95ea5234ba3ea04a43efcae44efe77016d39fe165ccf12a05b32c9e266fae82c0488cfb8eb68b59da389fda6593c92104d7726ac7c922c42dd562d0173e4eeac669432c165077823f0a8f017f9a07a712b4722873538d5985170bd6bed00281060ccec2aa9b306bcacb79e19851954b7ac143aa05d18f46d696492c46ed831fd14263c114ed5c8c78f3170b1c9e0ec46083afa31a3976d4e1720892927f21e28bac3a562a87d8460d6dad353984ec8148a28e0e018c107e4ff305026f312cc49dd04b4cf07891da4abf302175c6f4da4d407573babfbd286bbd215ae9f249fc533016d5823f1f7ca170113180534d4a0a75df0026a4208055c7dff68365e640386f4a7259dc9b52b64012268ef86018d20a16cedcf7fc2147ea8a61c818b65d8be736f9a33cffb6f4298c3ba464825364a771307f0e2245e7801941803c53406022d16d3e18ba5ec9c3c5ede66789500616c13e36f9e5e7e6535b6bc3e0fb6128b9c06c14b1871b6d270071a4869ce23b4783766dc029a976dc5e05879f7302b8ce1ca4d856836b92b479ff7ceed694cac98cf6da1424eec724cc0ca96a758c1fd69a7367da0bb5773150f11948b13152b162ab30ced998809be15db5327d7609c217d8506c7a2a9d5c7ec6ad8006da39e3c306146c814a2da24e81369f58a603fc2c81e9f940b1ab89ed40d431dd59221b69e860347044a18660d9ee24a4dba4582bf79368d883b0f208026e6f5ba5317eb5db45e5472a7793f568655597509ce5bd2f0b371f39a77c737774ee53933d32f6375001b010ee97628612073c7d503fba3249c7c892130785493b5b9ea1b7a13817b281e978c289df8507d82bb2e44dc798f15a91e5563d5f28115d590f8ea5d5f368d212c8f9661cc5ee57b68cb01149db4798b0065b2a0aa02222c7d3fb0b880e82f5d3819b614721cf56b335219c5e0f5994c9d3d9c5912f721aee3b184e4732715f71a2cb23dad766c571241e53d55e0af52fb1916fd5fa699b418faff598d3619583413a1e32556525c6bfc403136650f6ef87ed2c1c4de256e14132fb3b1e498a0cef87e2357e5c7145ec605d7dad4fa9723d174f9cc76412b1fa269de98042469845b6b2105cb64370da82a18e668bc4b182ae79053dfc8e8533d68a2026221e9ddc285c826fb55763d06e107f4b65f61f8bd7a6ed68e22970280d866ecfb8d3e3c6ab6c21f7f2a46e71b4bd1ad10e49d26ea8d221065cd3d523c00f8c09a6053ddd3b9bfe3ce5365c7a36ee2320d2b892dc33f72abd27436f23bd165c822a080cbc4811de8af64cedb9b4e2cad96a6ce9e9f4fce5432142cde43944c477856635a842977984529b6d43e575499a98dd7959cd2ac0cce6fce23a671b2b154833937b6381549b21f655f3890aba2739ef4bb2859ac5534b46d46ceeca3f0c5e1c949d834edf2b2e4a4e9ccb1d252ae3a02cc434f46adffe0f5ebd407aac94d0828a3974e0e127881a3a30a1d928a31978c47c8d7531edf7993982d0940950681bfd77bec4099d082a8ee80b107f7309bbc81f9d6954f147db17724d909f38cd8a34c9864e978e58eb6067c2680e61ee5a59d09f23c4990053173d8e7aa40d45175db6fb9010054a34e5329040a05ad68acd335610c611f812f6e0ed2c7c897ddfcf12734eee3cc31fab48071c08c45be4cb2fe1b4ace30e65afd0fbe76369fd951e0c242ff7416e2cc6b46bbe59a4ea9551fb01af8d4bc0303ec031348b49f77fd36d1444055d4eee3a778c983629543f1fad81c1958be999b160ca7a62220bce9c233963120cd05e478c9b1deb3a250aca8535e4f8ae3874b2415f4f9526916bae0296a46fab037ddfccb730ac923ce85231d051c9f8f64a37e43937806b68097f30cd22bda4e8935336023b670c4a508a61c38676c747053109ae53579894a9e28b456a817ed45e2e2a0a2ef6b1455a4c720315daa5378116de065c3ea456353ea69443921ad20e54fba92200b540977bca44d26cb0444a6d0fab973c2ef8b98694a09b1c44e7cd047f3c4da7e7a03846177a3773680fc63bba43f21d34b1a44ce8551f15cefab13af9e95dffde6e36f6e8e84e49e2b403706de5fa86e68a8a0d38c9a8063833c4b94464026d6fa84a857e439b93cd0e6a1b74014ce914a854549e4ded46c29b1b9ef4d92da3e6470a4a0496efc925734f34407066b777528258797174e294ec2857b1647988ec61840976dbf4da9fa63196eaff0b9bdf2ac9446e3518518346e63a96fba3360d769be52b6b8d572982b36e7a25a431cea32ba86d99138450c65f4922b7014b23f13db201ae224c8f06aba50f9712031445ea7240b6181df22b6e6587c713bbca22f8dc394329548ca6b17030a623abdde8955551ebb23e0712430fdfcbd3ae92af2f81fa104374a60cb7c4d3c50c026dfd3fcce877200a53dc0180a229a62b2d25d7c0e8ffc9eaf60db6fa16dd10525b6b5e33b681976dd9413cef4d564ee4c04417b5ad53fefc850eecd3a8dacc9bce1d22f146740e8c1d7472d14bbba3658e25802a2e8376fa4419e4f4d62d0f97562d5fbddaa1d3ad7ee1939039ee1fe7b22a2d750476284ae4d3da2d2337e40c77a9ff4783749f14a3f450475906b164037a6bf4409c4a3997d6323cba787fc2328f1909ae3266864c7be592d23a214234e23fc02362a25bc195ebaca45983df7022da912b1ca747a0c9d29701b0714b0a8545b39c1891a9cf1f4b988e86daed65a69886f146e6a9cee33674ae41634e3e20d119639dc965eb81806d3d0d68c3d5bc4720da27290ea35d41a513c4a6e0ae074dabc0f397d497f219571cf9f15c10bac82f14a9c45fdb8ae35c533b5f853460e9658bf40e99a81b38fd8318375ee9b6e546f550a2006e98b775725dcf1a299f10ab32ba213e2fd6b69f6c510b63b5b9622dd0eaa07b12494effd62e6b2a4a593bbdee706b2b53bd16ba2a1196879fa78798767eabb4ceae0b9d52a6690d74448abcb50a1f43f950815473015975729b60a487b11c02203f4889eb6bc3a48643bccccb9840aea2ab0e1efd70aec4528fbd6f2e852164a944980565693845bc04a0de724c2ff4a7ee03bb081f991070c9fdb0d506e1084a231013e32d06e1198d32c8c8ec448d8b81b4b151304179033aa66aa6c83d447622e9208ee6bbcac8cceb1b20ae522457a7fa9179511059ea396342437d2feacc51c7c4ec199ccff69bc0c0434b6555d44f75eef2170dc9972d68d7939c0e637bfb41f11c5d2384cac426827d10b3b9e97450b7261075e011f881347e5114cb13997ecfc60084ee2168de1dcfea59c9897cfe2fc02c9e347ded97485fee27c2dba843e6f4ec57e337b49f677cf3d1e9d4c42848e4a4d179ad0c1bdf422ceb811dcff90284b81cd7784d424ffc7f6af10f8923e9990016315b3fb1f50b126c28a2142ac98742a4739bc032fa0858a37fba741f20be0cfd3b19465e178992e11c119d8d3f406566147fd2741d91bf68b97478a674e19943510f5d4c0b081bab4927e927819196b1043792a3c03499228f8140a1d6439c026a8638328783ca9fa18e512a0bbf1830e887c7afe55b1cd8be895e1224a018320a384bf5eb0253564f427e9b38f25a94000f6ce6b9c1cc389e6347ff1c2a94a697a6c343776303ba4c4ff43d923dd454d8f2a05b715e818241ce46060d1152ffa35c2f09eb38635417f97ff9d106a092418b959905995ad31d303f6699460c79fa854bd6e37a186dfe2f8c39cc727833275c23eae2f2aabb903d1488597f143c13e6603b2f7448cfc28746fe02d746988a80fce8e7cb9b51f23180183eccd1933c3e3586e832821b410f4a9521dafbb40cfa6cf078dc3e65c71075281744fb2e6b6e01c5bca1c8cd11ae6b41d76ab82233c6ae6caf190ffce5bf557eaad523c1758f10a6137322e3bf8921140ff1c44bc2f46d5fccf4cee39f4de3a854ad50d7f3897aade66daff0d8b15de0d592b64a889d4c1323c79d8d138d0b805463450ee135cef3a1aa188afd7e90af9a9066d8e3088b1f768097c10f1fb8013594dc4717484f8685d25bcca62e1fad73d67956d566657b2f2ad3309ba57ce65a06a7aa98d8eba7dab0fdf96c947a48161066268cbadb9e1f1912044a62e872e5b294bf35a9143cca01ea3f14c436ee15ac43bed1bc0d072b9c45da9f57c16f89b24851427102040059019a1a146054968f867c276276d668b3c752d54c221e01263f21044f5f7027ed28456364200785b0258c0e1dd9ce1c635a80078ae2f62fd0b0f1dc5de404df74bf12ce3ff1d10a8f9332b62a1017b6e8ef9e2cb49a1b8aeed420a1d3422c370b03de8feeac3f0f4ad9f5ea45875f95178fc6086c2be660178a3e23ea3e6ff10927f0776b88e50e3a72e1027a9471b6904f8867d345941c7bb4b1816e9573258158e4ba392ead97e3eec428f6fdddc6ed0d9ee62b15398e8def8960e6d0d5f0e2c175894f7905cc8b200ff4ca0147c1cf85201ffcc97df085db2e9565d89cbc69ab354190cd22b7713a5603dabb9b1447335eaffbcf645960c66c46086e266554294108c257171b91537e751da80ba560219314c3f55fc6d05198bc021c0b3d2b84f3042cf9ba3585484acf53b9b6853876fd4b6de9813b8bcc8156f68db06b665e011ca3590f47e18ef1ca5980b1cbe8b6ab7fbf1f57fc9041e46930dcfc28dc8bd342fc9af947876774d7d2ddcf304a829f4632108745209f32829dfc221b13e8b4719bba50b5058ca1964f2b56a2036fc298ed426fb048285c98c2d8cf6f8f36adc3dfc4c31c6512553cb6a9c58247fc7834a48f2e402ecac58def374108b92879b77521de26076be56dd2f30559eb40694271d68716e921b07b680eb0d3d280e32cf978e41f24ed78c0f7e98f58fed6a8e60bf5e15d227ae1592ae346ca659052021080ac39199a67eec84233ff310d0ae0e9a7848552df9e3fd46a7d3f72c999a8a6019c1f3da9f2d1679a97d16706df92d547062a9974f50d897f2446148acb54b4f3ba2a07da7bcdc68329462823330d7e06b392494394f91dd53735cd1de3643f6306c8de2af3cec869d97facd3578bef4aed2da6b9633c632bdf50c1a24b325147fe29140cc719eb2018941ea3ae7f7bf681745261b2c0a3a9cf63769e1922df6a50b7cd7b97de35a3db74d469313ce4fbb1d48419c2231fdc7ca108d454bc82b58388fe75cd2e41b43b9e1067a3e71b6a2684dfc333aad0d74242d3b41eeaf3def0545b43d3514bb9e373545b405298883cd333b50b5a5a22835d01c5416424cb49663e8bd176bb5c4705e1127348ae67a66fd8ced114cf86e73198b9a8926cbd59d88577f3a0f926e9c6accafe1920660d683686185b70954b85108435fb13d5724b7ffa677129293a4ff6cca71bcf23115424ed0ac267bc9dd3b940e82ab41d68279db2df74a4a5e22e99bccf2c2f464c1a611f0041192321490b9b3c0fd7ba79bc58de2b35cb417247aaace4f342ffae5dc237500a5ed3eb69e1e6ac4748dcb2e1139ebe5612c011b7dc4fa639a12d66c309ce7c194cf489a3d9e4deab569d9fd347ee47c785ccd71cbd1f7f27671834e3acf96d46c00a786632af2258ebf1339359ca428a1c8b5d9e3d65e26f9ef70e787a9a5109adba7f3aeb27c79bf906341c34f50255e8bd0154c3a9b8ad1190f17ccd8ba68bfcb432189b080534e5768254891d97c40e0b4cb3d125283c63c368833c08da8925c7b081e562b6ee4c805fbb3f58f718d4fe75a5d300c6335a88404527783c459e42e1e2bf3da19f5e1e4bb0d06c6ed826db31f7e99d5dcd052513da566f15037284204ac6a3557b5817a4c143c3d35ee92106973ce6ca00efc0c38b835660920869e04e85b4b326f86acefb8254dec262fc93d0beefe919783b83c2e9a0b12da2ca3caaeb860957b0405a5bb6b4d3356a6064275f863548995f1110bf23ecd25984e3568aea7572ba476599f27d60713131a9a0e70dc3a5a5ba5f56f42db739039947a7e398ddcf91a5c39985a4a02cdfacbaae106c126c790babb2b09922732ec4d47d5ccd5d566ff8642d32a8379f275c2ec25d8eb5e0f2ec0d80c4bf53bc3fdd41066a7f0438f375f84c299c0a051a85d1c050a1ed3bc6dde315f5eb20061a8558810d3213b8e004ca7f8332e322ebde4f7fbd7f2dd5107f3f0c597e231fa50c8236b23bc6e902d0e711978e13cf20371e9bbf72b14d6e6145fa08c076c741f80387b2179c0d6d8f8785d14eefdfa7ef41ce864fd898055411b7fb8bf7a67a5ca2a7b3c02c29e61d2556eebdc69d42622123b3ce78ca952a2deeab6776c230f7cff2cef89e91c791479073eda87c0dadc68fc8947a24c535aba9c6053ab1fbfdd503330176354eb3be1b7a7a3f2cbd007a7a8292d15c2e7760325449d64507b4f41ffcfa2de31d40d7d0eba1419407069001d548a6e73bb8bece7c88a508b2a1ae02cb1d4032a37e902fb56168e841550cbc0b9fef9ea3c7186c84bde4da69710e482aa3c157ae6a5b4f2708f38e51b91a55827f5c5b01262f84a42ecbc5d5a3060c1f9c457f4d46390151832642d0ddaf3bb06485f1b5c95dab7b54501d0ceb6bfac441d939f8b34a86698995ae137b70bdad4428fea98dad15339b26bff97f6e8827d4bd583ca67e0f3c1b3e1d69dbec2d2ed46860ee2c252981956753523d45d4ee9509270a501d874cb24eef76e1beccf63b6a0ba0472ccc9215782a33289c092fc1fa41ed34df0c5e580aa74e6c2b50bcec6d787ff2d074c15f9e82411fa70f5fe5308fbfea3b0f485051fc99a29d0caba6fb3744f680b6fb82f1b645db7b5641c0aa8b4f5e3c1bd926229c18c69439e40cbc67e77369279ebac52174ea6d903b11d20698a32aace5b20a8d0bddae4750b3cce959eb2e67290423c5a1a74858c60177e5e33565c18c616ccfc0219315da6d1882c4a56cb6172c60f8bb0f0c086661e34701504aed3d7d6a5a81e6156e482c3f74cbde1156da3283cc3270ef9202fdd6e438e1fe9f35c611163efa1c703247802c5de878bec456fb27f81f71e6a23c54412cc6ee053b94f75918cc5dea341abc15da895082d8ee38f5e0029d0ad074a8e7c0d3845b8f594724266679c3cecfdc5a613872f16ec4b691ca51143ca10004fe85404815f469e566f89a83fa59212526d7b9feb62b45c898419e78232067b2776262e3225895877b1f9e189160fcdfb4837abc0df56238bb7f55c41ae34df1816368b784aa6d38bd470121389198ad0dc884da9278c10740635ab62bb638a8a539859f3ec874b7a262e6e8cfbe142aecd25937a3dd802497662e197ae9747ebafc72624c9c530a16bfc933d4f0b5f2e3ba163f9f36356728181c8ab2138b955eb01c88bdd1322daedf4141f307e186d0f05983143c178b8687306354069caf1d937b6c8086c440523e5a12a03b01a944110b89161402b73feddc5e7004ff7ce492ab87c2d5d24f4bc163e201df4773e2ae695c392d5dcdce6dc57902d217bc542645ba85e284d26c746c6cbacaa4415b71626740346df3d3712a8f111069877727f720deee0361da5ceeb1c0ea6e8284a579298ba75d1b985207b2d1879b3ed083ecb4e8b0b08e5e74c14310fb7e2b4ff75069833b5619361eed5ab45031dc27bd7987e412dbb7b39917f7d87c0af904dd076db1f88091211dd0b62210ae287b101971f71019e5898a925142e303dba0cd7a669d42a266c0f29c88e82eab9591c2294001e86c92378007b643d416ee108dfeafa8b57d76e68a2746c853712efb65ac315f8086fc97a420e372d5a001638b64bd2b44b383f5f21999b74e5e8c831526c6f8a40a6628891973553534cfb4d18b6bf2f41fa009e8a2fbd24e2d1ee0eb0b9cc1bbded316ea369609afe61aae588b40f4e79db8325de89743f97199f2a0687e639be24c5787a4848104c1649975e6eb2feb00b03061d811d87f5dd0be4da9b1be0b656efb7e9b15fdd43407cd4aff560062a2631b6a61a6bd787f453ae31827a1597238a2e804fba7dcf0fda9939f8f9f631ef53a189ca3063188794261ab61cc8d6ba57a61672c1f45cdd755e4ac2a8a1c3e84ef04756eb3b3e84f44722d14db131a6e54fa87f3be1b5a6fc1d43c5792272e4fbc30550d27873eabaf6d3d6db052e14431908541df7d99fae59b320b8ac71781f31162f15baa6e539a0a1af52704b030143e070bb68085055038d3d9dd62ae5941a52eedb34cc846666605ac6aa0fb74ab860176bae5ac08f2262af9fd0fe7afb276797f9559df36ecbd6463a3cbe52ab7e245d68ba0716e6f843a03b6041a0ca2d9fba15867864ff4c187f89d16c4296a8d736833c44be46f2eaf3d82544ccd9f8e7ef4daad51074dcb189e41f495e4021d9cfae3a67ea1a147f185a6d23bce5df827b1a01527bb7e1073cb5be2df0bc579fb1b53407a80fbba9ee25e18f76c3435b7ff0c800f00ddab191d38a61cbeafc9d0cb540b0098bde69771d177d787bae3abda7f50ecbe55f7ffba9dbfcec6530e224f7bfb2fa7b0306ead5f47ce4818548740d2b4e6a2ad84ec0d70617d09cd20d6d6cd6ac061bcc379dd64182604fa15db5da82ac77c81c52698eeedf52fcec0ad171f4e9ce442326a2987cdf712182e6e6d31b02d5edb25650cd3e43c374b6b939403315d3cd25b74abed79a33fa26f0bd76571c0aacb2da87628a53062f8ae319654a3acd1cf9fca1a887200deaa987021d39f95d4755cdca992bef28244b7f68b2cefda30281ec6cd4ed6c4ad84874dfec2e74e23684dc70076b2f9400c628ad95c1530dc32f23165df90ae66b25403efad45a8aef83eb58b656e57082790a31f35b54539b6bc13e196d52da139ac7e3dc6f375812e550a78a8c3cc03b58deb4e0e1ead5e53e613d6c3e25d331f38786973115f03533d3248a724b79e04b5a7249416abdf5b02de58054fc4cf8251536acf7f685c08458815c15e80e87420f727da0237157dda937791ee37dbf7abf9f7cb5fdf3410f05756f722db6fd2f3a091cefea7051e32fd76f677952f28f76fbb2b237dcc84671f02539e115fc60c295c9959dc3e0b14f3f6513686060de18f5ae2b37917cf3ce27e273a78849e53e59450573dc6fe82606fe16d4416373e7fc8738353fa3af662f6ee42dcf578d225e61349fe66daed2494b58ef36d8f4805b8732022e512e88b55635e21e5c2ff4feb6459b98a8cffc98605179c5194b89480881c9c002aa0fe94c8cbc2861c6b904e1df9e2a34c8ac664736d0a50ad5b3f47e94fa9a0738f559c65db5d11b20e14c8abd86f39d936e125125e62c1f5a5774e46a31bad445428274e11cb807537c0048a46c6186469eee4d460fe35f4d14c14c75f5987a26742906070713f8aeaef5ccb781c72ae170816ac147b2697abc164fce88150f397ea42542e60fa89c5003334b20c3577ba4a0813d39b7e96b9043acac0bcaa690bb2fef9a53e0d0236f98c1af45a81e2933dbc99a75ab90e45d098d7d25c6655ac53e3745e2beba9cffd16a4fbec5d70a5cbe95eb25f9022d9ab7b57915f8116d9cd41f2d729c0d8c177bff53a4169c08cb4b8db9cbbb78b4235d982176a89c1c205867d2099f7153e82718c0151b8f3443602a5f0bebc4ec08a69879f2c45668f5e5fecc91bb71a5fd33432fb5d100a553e9988e0b7fd9693f187014e595b423015de2873094a0930602dc84c42faadd4a09c9a4650a3d26f6468ec4b41d8223eefc9e8c1eaac0367353aa4c4b8a467e86f4b38ce369f02c326e5158e0e11898de34f63252b9ee353bf66b822beaa3dad78ca170430a1565afe413369282e4a649ed3ad9497a52182823209470202faf29fe3e676c5b66d49d07e2460ec50f27625aadaf3a8d1f8579bd5f1fc4d9e27c43cac2e006ea0665457ca216a03c171c6e0a292c4975302d20049473b6d569417e699043d3c71f5730e2e2951e6dc07b7f46c2d3bcea66294aabf5f709e69d3e1ab1f8f482b0d1231a2380185e3016e7da768be4c4f1b69cf0f8d6df71e7b923499d16bf46151d42f77d7a387bdd45f436ddf39cc02674b48dcea76747833f4cf4b78dfaa06b19e80c37cc5852f641c396827e22a7768ba2631640c8ef629cae0ce84d97e80c3025bd0182aac95565ba7e2ec95c8176aaa68ad1082e45bccb0918afff364bfe3b2d59a45ebfd451b52dc8e5713d9f50ea4ef1ec2430e819cbd25029a54c6600ed69f56cf49c4ca2c6f773057b1b00a0f37f9b8b1fa0848aeb9888340d54b944ae3ceecdbea14064d6ae268ad8c084c6185e4fe1289d33b8b09b279240acdc09ba1e73f9d961e5c44c2dcc629a64991431ce0e2bd7485e4ec35799b4b040a0c1bcc1c1c3babd3feba05f3931e2dcddd26219c2f7f3001cee6965a402dc0c8f07c461ae10a82834e60fd453a130d3cf756ce9f5ba18107c80618b7c9b6bc370b86c4cabbaa58dfae9e829d212a7ad8a7cf66bb914ae004365c15c56cdc17e5a64878533674be6633ef7508cad1cbebf367da261bdffce20c04c032c0f769299bdd63c2a9dc653d6bf48d37de5d1602e0ab1cd43db37bfb6023ff53a996cb4a4fe3ea2825d006a16ffe079970e8c3f64bdbc8973deeef3bdffddba6ec09c3c2306c7d3246c1a6df7a80316628c3a361d8164791267efc8793c2f27dbe6adc2fbfc07703cdcc725574b405ed11c9cffb623d945225845fe464d36a918727588486a30217abbfbd83b2b94dcfda69135410096458bf788adff2048d2b277f947a9efc41c93af95b09e9c95f49d8c99f7c7b38f957e7b96d88dbcd87e7172bac6c40ac9f4dad99e661551ce052d32fe0ad644a472eef459e7d7f9d31b7f51467928a8201553434a9aefd919b62fa42e962eee610da75b4dd8db79a4d3a7cf3f5a659984a65b8eef70033b0bcea3f5ac842d8254f00b297015cd080f6644c32d85bfd3dc20eb1798640c4fe73b690f59a5685e5918c936c7d885ae7b05aff41e9fd8da150aa4ba2c57458fc8558cabbb9a5eb015d5287fe1e6d86255f1d6fab7f8207183e631bdad03edf8907b4821193a5bf22349185096ddf86df999af5e621477a8bb61d72515b06354a6a4e549f0d6f4ed6db6cac0c75697dab6eeb5b4277e61c71b6941eb2529a2d4217ed81d085e641c80d0ebab440b3195feaa00d8b7511d7f415976ae11f691dd0782d4a257c862bd04a96a2d7cd158678ffbf11c44474e5aa14909eb8e21fd4fa8020961fb328556e3d1dfe02e32e5741f3c6091bee36276b88382f1561892c5a84ced8a0da45a24c09a5e1e425a7faba8759d8cc12af1931ec4befc38bbb68457e4e925bf1c1128bd09707e2b3ea1bd6f820002dc7091a3a74d68a4744c071ed4982d830f28723180b335dc21ef6a8873dbeb0d1d7c33a9fc025e2af4e721b0d83d13443afe14a59f76aa7eb5c6eb7a7c84d51dbfe38e10f1bd273603f068ce7427f4ec8c786f518e8cf01e7b9b09f13feb1213d06f69ed73a1b00789eeacfb102ab97301e39acf1aa4d62070634ae320c55d65e6d9ad5b7adead8ab0f4b9535579d66f56daa36e6eac3526ded55a75d651b558f75d5b0a85ef3ea69acda565ef57e76d6fe8fa03522ae9768d6f300ddfb3591fdff8379cce2f24b1f2cf116ffd790db9db9c0b6d223796081cfefece4e725cc7b4303c11e7d3fa139193372afbed43e6270ec7d80867c3a0742a76fac56f640e9dc18ac592a154c91cc6a36438e1c28d64cd15680c53b21adecbc59611672dd850fbed1eb95ff63a3e48785b711f0c5438418ee210a79f30f0b790965488212ffb98375fe153a5abe65e5c5a1c65df55ddcaaa87c79db67a8e5e136614c10d1e284dd149e08690c98e9e262b9a28893de092511b4a1e00319d837145c927828ac0a86b7953164ab5c55a905e984197a959ad70dec4857199689dffbdfbeae914ac5277af374be22cd896479d0e786caa1b206fdd813f0ee98f50a5b70d4252d568f8f4ef7581507958ad0ae861348d5bad258f836f3fcaa870b8b2cbef5685abc428f541cb0329d983227994feb1cd2787833411f5288c7a4f159227eae82ddee1bcb3041aeccf36e1c3e2bd0c9c05952eed317f51b27e686fe53e1573412a74c3e79f4bc40a3eb961f43071da8a8956faec00333f939d8400d75c1388d96d24e4b792d112b51e2e8ef0a1dfc10ffbe80b3dcd48e18fe69bc7c2b9bb2d84090ad03f060cd47aef2d75999f20fbc9c5e0038873231f691e8300f0d937283863c8c33dcc29743d0c9da34d40ae982781b3bb8d57bbb1e0875e51c325fbb428dc7b524ae4f28dc4ae25e6f87eb1dbe2d2e6ce6e639ba307ca531367ce6f7a290322b9cd33f7bbd5b186f916cd031b93fc49d217c23153148d081c83034c00322c93dba8aa2edfed4ce1a26c904c4df3b6a2af27e450bb9963c9755265648f1504355a8a9bfcaab2e2e1ee130e83669068f8193bc030177a3cdc505347e2f85331cb99f76a52a34c9bb67891b0fb2668a18de4f8ad00404f214c0befe54004e5d05b794e7e8906cc7bbb53dab71a6fa7d293ac5c51340b8a1bcbbd052d31491cd2be2579570ad251a1e4b927d39dca3a75b8c40555459e91c1146902ff1e840c723163436a179e34efe24ca188cd9529c8c95072c9037c13f00a0354c898e260411db115e6e0cbe1a1c6011e5a453deb2fe30ae5c5140937f0bed70bf0621ae14179055f22554eeae31e2d30139b7be480c897c36c43de0d6cdbf4db30428cb77656f06652094927ec24442460d41ff59a50a74192215a19cc0cdba2588bda407a43e49ab11bf49ac799d2c5db24dfc8547a1f46f4aa1dca84ec92e171df0d2a432e1ebfb935e8b5c2089cb717f1f27ae96750d4318e0756a8dcda69b5416e00e0b3a58700c17abf892f89e42798b83aee19344269f1c505e01c97049cb0d1420872d3afdb9fed72de9a0eca0df195453b31f2bb6ae8fa6926a4948af57d7ab0c838598836be1546c8ea14aab616d14d5ae6405797db1a98d683eaeff729becbb02cbe43bc0cb44ee7495c77cb93a0ce7aa63d251309fdca6f17ac1433d0bad2d1df5f5a5f63725ece921bf83bf6d1d88fafdb27debbf4cf351a38cd484546dc13494c4de5bc64cd51f8270679e89de8e5bdc359e03a4c107c2746c72cb511abed9ff3c7c89dd7c32423085991cc55b07adcc01d7908052748a0ac817313b35dd3e9594a4ec6970c490160e58d2a20638a5007793b1428cbb7fb0b3902d0edaf6c0ddcef58bb66701742ed555e771a73b458ca6cd211cc0d24ea6fd18e4013c4b155fc095aa8bc04ae282af5a4995a660ade8a0b125b663d85b80917ee16ce6a61b7d9c3f73779dbd9e151703946a03e79cbb5fc4b9328094fd755bf9a0e5968ecb6c38bfa4b97d7c2d6e8ebca409fc64b1dbd2197cb14a67baabd1363a12153b39bc0ba7adfe4078055c17fbca495080245b3a8d2e7e4b72806e750d46cfa0bc3116fdd5a32d326be1b4f82c212e1e7ad9390d221d68a61dfc39df3e3ca4eef4b835c47d2b9e3cb346bf2771778b869eb38bfa1b0cfe30a6541ae4361d8371230ab0c438e42fe903165a47ea98dc9049c737621d724fdf4ad0067a43cf8aedb906991d21dea3ccbb5c424f23ee5dddf49ce5cd8e56a666bfd19ef5cd7dce470640265fcc937d78d65f72c23bf8cd0d869c24418359455a488d36ba9c4f18757c72e2e0abc2f97bf5327b71ed42bfa2da8c8243ee825b332cefac021bd35dade4c3e2ae95281abd16758d899126ddb227516f9e44cc9082d57ede8463b1b356aabdf94fd2237517ee7fed14f20215e02ea38ba2d7b4dc1d90972ba234112bf39ea5c906ae7ef178df4a199bb94395a2fe1fbd18d5233675a279c95164bb606dd32896925587d8c2cb5aafadb11a281a302e25465b8fdb585b17cb85979a7a8af564587b7943547893c36f966133686fdfe9bd548d9bd7a7c1d0f7ab3befa4930d06ee0cdaa5cbf6f4917c7efd06ab8419aa5fb6a32a5c914934cf6fdb89c32f53a82dcb813015502f85611781cc2621d0225e486d01cce4a267ab7438141d1beaf5105587a0226be8c6bf4c447b3d9094cc9db1ed49d91a0a51a9a08284fc3068e8395c70aab4a777a1751cfffb998867cb745c7dc69ecef6ad3ef0896bca87302d522229491e45043ee08cfcaade8a8750382533d1a16a7712ff07b73ab3166fc787bc351879d0780156daf42a805a622d61fa9859fa8d10afc9d54cb014a17a587909586f3528ee1ec2c92e509612ef98f0e2309f58bbaccedaf702b443cbd2895d7e6657d3b7bd6601da15aa98e5f0055a2ccccadfbd12f8c211a61e20764986aad4606dca5811b00e83286f068a2aea0536df800e1a7cf0a5af95da8f73d8d89a918a86b5df5b39ab798768eb97d9cf3ca40f48584ca848130b0af1332bc7df163f75494672a2ebda5f364b7f44594c64b331d7d7604c861302d0d94b59dac11f372ba7e456aa83d9d2bec8c2a22e0dc2dc689d55d30c2ea7bbd4249f53cfe135eee78462de2c83e389e8e87da13b13d58537307d2d434af4f0bb75b120c8879b548a3bf62819c1214881c697232c2d121bc5e9029efad98ff1bb6423fdc8593faad54768fa49d75052c8cd06e4944038efa198392c6c2d6486422cdbefa0a662a35f0a1aadb681c207166af67ddba68649d59e8daa0c588a02a619f5fe03ad85527db56065232f96be8d16f2e371a15b4c253a74cffdeef4e8558ec44567d6856269e17da1ea895947e9a17bb61cb586ace373e4dedbcf8e8d4e1417dd1d463acd755c38708b0a77f0cce86cbac362ea0acbcb8f8db188a55cfb0ed64ff60fb95797efab79957f421803b648b00ca734e57703c6a5ae62c18e54707ba0bbdd34c72cc3d56ad3e186a508973f616cd2927879f6999aa5d7d4ffb97ee0c233b2c558cec9cee6d6fbecdd0588a12034d46502fe28a76305d4a63ebe31659f6aa725787033e08a8cd900d1f9132e7fa8b0fef381ef26668ad80a71448a1d74c9d4637d35bbeb3d56f582a7231d928b78189091cfddba90ec378d5044c581f82a20783817b88f0693e71260efa270f5577bf48fb1a4cd34f5998d98b19a5838c430766ef969a127b46334148de499cde245f19d73eabf3b424e9f09e7964d0e526ec7275510c51931c10ecf0a7c7cfb5110d27060d3c3d3ffdc01a62b7756e8737481152cd2337debab5c01df519da22e0e305dfb9f28a531d6dd22d2b6e894f662fc60a8ddaa6ec59b9ead501ad39346b9c13ff2870129ae59bbe1c5d0304a813ab60a3f855f291a042e7a00f86ab56c66422755aa34077aefd338b55bdc2eb8056ce03da00566a78ce23f15703d41b988b00edb34638734fe3ba915ee97672be46da3a389134fac5c351dd88c7eab563a86e5794be90ea1005171717429d159508e4717cc98853f504f1976d0d49050bbbfcdb3413e0e459dd6f42b74b04555fa3971335a8660032c7f6ac5ba29a6c04497025063a5b3f2430c7bf6d10c15c308cc037dd177f1ca4dbc3334e067920f078942885dad8136815eee93ee01451b3638a79db42a2169e90c0144a503ba6faa7c5a30141a37079d9c1aac1bf75499a599a3bd1333d9a4dc6d957636a2dddc6b0d63ee66ca4679f5e5bc41c866ef25645c4fec0bd6dc6800c7df6573b0784e66bd9dffa15b7e2ed6b45e44b2c446bdc728fe96365ada24b4899429a5aa07a907b20731de3396db7d99b590d8ed9bc69c26a8f6cc53c7bcc77bbca746d4f1036804cbfd0b4eb6c27096bf163cd7b1b8f843a5559565436ff5c0329f2cebaeb065d6ca5a592b75b356d6ea629f1b60a7deb8fe9e368475ffcb538e88e2329fdadd9d9da18004cb4ef6d6c46b21dc757313d6c05e9ef12f6996856ebeb065afc458cdd0034198e3462331c2574822d1605a17b55acb3e24fbd80efb9b9cb3dfd4029f5a0a2aa2bcda27870524a89c220a40695f6400a8eccb30a77f8e60c40a50394536190c01a58d4e8a645dfca0b2d149cc7c0ee9a8e560cb7a9dd4746cf35829b414db2ca0a9e82c5a082a803247ed88edb164327cc230ac62b95bd6b8be1bd89602c553d90e82a5709d6d7278c63fc9131538b93a5c7f250da5a1384dffa9775a49145bb68f92f6f1f1b9e2d33ede8fcb85d06dae892d4d26cf9dd26e10860e0b5b6badb5c27eaa5c7f6f4915f9c49f3ff30c17bdc7f008b664d70b318461183e69c31486190f9ecf38317d8d6db66fb07bb69c87fc4d4aebcdb6ab6977cb6ea5979b757afd3e2ea47dcd3e4dac9a266edbf669a2563531e3b84f1369a789d3f33e4df4af418ed3402ea4bdf6739b4e67a8b932e15524b3561bf41ee441d383466fdf8e39f647f48da0caf926d35ca5e6181281dda423d8f9620718f6de248207d56a6d18fe6b4f722d49d8c9def6cdb164588cc8e58da03cc8825b8b6d188aebef416c33ba2e12ddcf7fb8e4ff719c0ffb2d634e8b8fd17b4a74d52cc5f8207dcb93464fc5f4b73ce95dc4181f2feff22fa3abc6987ed2e83cad598ae9910647b9bccbe82da3ef60b1a5fffc7094dee92873a4bd151985435f0acef7441cfa96bde33d5ec575865d6ae9cf13b76c495eb7e2884b3b2cbef4a0ee0a5b7ad0f50f5961370faa1ee441ae1909b9680a0ce32e4e266fce9ecc9d83e31132cd1375aaa1c07a610dffeef628e3b2a5e97a6832997e1080982d1b093bd9b55a1b86ff640f8c5872eb3c91e43f4e9d2747f99b4c24aa6f8f259d637fc84a48c5bf37f3c4367c47c03ec7213ce35fc5b041d7df3b21d8b25eaf5ec37b33579b0b0bdbcf5858224e7c70cf4fa4fb7e22dcbf2c1127f389f4774f849febefde477ff773f4c1fdddebc0cffd7dfa227fb8f7e7ba691947e79c1f0d8cd2e9e7cf5a7307dd7af282c168ca11528a20a3882c538c5072798a11474e97a714a17379b290596ef38471fb656cdc2e4763988c1491830b809efcf0451358644f04ed2066042fa45081d7f5d7f43ff901c1967f7f66a3056520ddf9b57263997df69a902ccb5e4876671965947183b0a590ec4e127569414c21220a9e5b1ab9730a11aa4b5e9e4283d4f5c0a7ce5f6659f6b4e73ac8636ce27aeebade7ce27a544aa9ddac0451bed0e27bfe8c6dbce751bb990cd9fd4240b588dd0f88eef79ef641b91e371196a710c1da7c76b4ebbaacebb4aeebbaebbaaeab3442a59125d77b16790a1139b89de8d5923ffbd0975ce8b3a7cfbf81107fc97d22b0dc1efcd087c0186e22102af9b9f7d92235e33d8b65df9a6559c6bf818c72bf01eee773ef4f4108703fb99ff5bbeb5f4e1018a94c8d78ad6d01032ba00401e5226b910294d7af32a85cf141ac092ab030620951a05c64580ea84e81932b8ea05c6401098a882e584340b9c84160c23f00630a2b5e2817bd9613503909b0a204305411b0065074c1990055c4250accb1f0c5155817414ba2479079ea2c30488207657a82eaef187fbe62901f2f6b90213de6c93610ac5f55a3b2d7d9c10554f6aa46695fda981ed9c8d787a3b43186c798ec6bd07e1b8938f15143f6db673db6bf71ee20e29059ead7c48fc7fe187b38d9a679cabe633dc64c39ce1c3b32d9dad698f06c9f9366da568fe0b88e2733333328e20f71b6e219511019a1ed99a396cf65babbbc7c4e22bdbcf44b167221895e605e606060482e2d3030230b030323820981303030a6ef081818b549d90be8d840d43a3ac9b4df1060e7e74db5cfb2186d9421bbda6be39091ce39c5f058c6e9f63b3c6a1b9d506d72f6f46f0865c86ef6d938c4493a7e9b17b69b099e4743566cc6aeaff1950361e8bccf8644d5855d5a5a5cdc65489614926a584aaa2f4cc8902c2924d5b0a1157186621c304570bfeff648e90c1afc62a2460d7007c7732c71b03b8ecb658d0e00046005772bffa228ae100000d48811451a33421544981792288a2e62cb48b4a2288adb8d503f4c10301542fda60e0aee4dbd7e2f31f0d869fae4d12dabb53bbac3a33e5a86d73f2ad6eee77b9ab83d48df031dc0c2adcfd973200cd97b1e2883e952eefb6655fb8e538159a0ef8930cc02fd4e24f50dfa2dda02e4606bfdb8bfb1d2281e33151f9e253a299816507a5849b2c47ea834e151e96081b9a028610565b9f2734d36c2c892f4370c3f549af05015d5c102734109ca72e5a7d5c48fb80a088b14572b212388af849ab08d23d484a53c43fe6c49b0d47dc451da92904d3ae90c997886df6b8da76ffdf48672e0895d77e09329ed9fb77bce1962e2d567999bbdf7b494b9b5fc5b431bf692e600d23766a8095bfbb217da81adef75fb57e7a4d99d978e65ce659ea9d5e7e7dae9b3c6d03964fac60c8de09429edd7f92ff48df933942972ed7803cf88e6bfd0fd7c8e9bdd97fe897eed1796bee823e3d055e66affc5c860aff6f66a74d478e079b67b2ce9ac3c8363a66e69a2226dcba856e9b773f9371209766bfd2a4f8d1a402187b8b0fef9cb1385bcb01ee86ec30fd7696041e38aa661c5695041411a55501a5664f5d3401a57341a3ab065ad5078847868da130641d118131a998cfe1b63c0716bd2334fa3b185e0528bb5249801a3f7e726d7ff13ab0ecff868ac3a70c95f0549ae33b9fedb58c1d6f5076b60cb4ed59d79aaa9593ac2340d0493e00998a6590eaac016b89aa7518ae7ba3796e093eb60cf0559d77f648148f1d7752f1f0c0f56e3c6badde0a4bfb644db71d2e7a8f138e94f6baaa6fcd44baa2a49dda94d564faec36ef9654328e196953584eb9632b7f25cf7da73fde964398fd3388d97ef37b80f315f9873cef18629654a3fdcba1bc7496ff5ddd1aded38cd12b6692534609a0e82497fd776b6160176903ad2430b92b0cddc6cf0021f569d5167ff66ad36744a07ce11827a4fc039b656df50b1cdccc672b3010fd7fb5bc94eb35a9da36530c4660312b535f5b5c014db4c5005aa463da08ab278c66b84cdd258990f8e934286f8973575fd6b6a9e6a543e2bd6ada954cb2ae4364cf0b232461b943603b14db6c3013edd3013ae9341ce53894bfe3555ea2c79e85231745fb0d8925d2f5bd8b232d0c5c2960c04040808080808482aebc2ab3cd6016c03639b1648cd890ab365e6c4f5274b7cf2deb7b164e1fa6b238f954bfdee97244992244918100d69f5b79189e405bbb0da370ff266e36b61b5f24328b0f3fb6f9cebf80fc4d9872fe3343463923dcd9e34956c8462f9d7ce609aea248dc7a4ff2686fe3222934e83a3b4d1ce12aa4ab1760ad114a2299c64dd32337f562c8f22d10ed6da2eec17c26249eeaec2120478ccc528016042d52f57b84c6730033cf1fb4e6cd4a42213efaba8aa28ef452f2c8f369ca4945d3079c9399151212feccc28cd28fd8981679c131b55b9dfc184aadf456105f8f08c27a0ec299759289b07d73f014cc37ce253ff48c1a40ba88f7b23288f137b25366b76cf7d37f62aa69b15d3df8df5ebd83c63b76298935d935d935d935df3cbbec2960d639bee877148f27335710efafdd65f065ce217c628824b7c11d03798d422b8e4ef210fcff8c3fc63f8af551bc311068b7d3f54697087a6c39cf3e7e9fbe8ffffffd31fcaf8fff7fa3eb0dcad3e7687e8c630a309b67c8e39410441c0f7f9186af569f6f18921e59d01e13d1c4257c8fc6c903cd39f652de220a4823b42f7035d864f08ea9de30c3c799640c0226012709017135ce798d3799b7bced07c2a6a16d75d0831f08c3fc782c4623113cfe090e91c25bed1cf527418e0762cc8ceb6e3eeee5ebd6358ec0b37947ed862c4ec08ddf9eca7ef79e61b6f706f0c230c70085b86b77beeab2b8c76d9806301f1472a35436d4c919776a77e6ec794f8d1aafcffff9b4c24c902125419e670155be400085544e3620928facfafee6920b659c0f55ed9fa965c3d7105f87bee5eb97f9e7eccf99aaff99a2f7e0d619bf9fec4c91e1f01e7e824f8864b098e82b6c38f2043aad8ef961c04430c0dd9d3af211b6bb0ad76f9f8f7ca7fd542f8b265b7c856abd56ab5baf5e3dee1a0b62a1293c562b1582c56b358ff930536cf9cac56ef78e8fbc6927b4dcb3e0a665f4118b2394947b0f46fe62c8decdc0dcce79e8a25c3eef7a5919dcb11117d4e8c8f51072328f0fdc3197cf222b8f3150f564e912941200125b2d77ffb611b866234e6d8d1df3828c6f800dfe55d8ca03c2b76508c8f960e9a2577e119fd68f4951d9de5bd6061bd10c1961e731ff7f1d84b15b6741f9febcfeaa016ca1660f8c6728244640045092191512c506454f81d16b69c574c16abb63c0793b81038b3e46f3291e43fec4b86897f71867409f8d445e0923f126e9614ce84c160305863a971cbc6a2691af91e15fb950bab721da7099aa71eb34aa172458b56f29dacf708c3fbd34a5a89efd430ba73f574bb8603eb7f33bd3d879818ec5c6596a22e425c582b9674b2c5509db38adc45b7acb754036c82259dec1d78ac13e3e9739417022e4f89e2c37d02c5de16fb4e07f80b86c5499f3dd7759c74d5f539291deb9d3a3d8f9011e41e86bbae4bb93ea35ccf71c96979d17b45b0255dc5f48fde084a24c6ccb76f0445c988f908fde8a7e843f4966671d26bcee843df22da9c1c582afe480c99b0a5b358568a97fc6bb5360c3328604e2847f4a3508aed7eba57e7ea7c32203fb5fc774b1a03822d69ac95f9f8e9eb341f6c99b5b29e0da873358cc62a3886df58fe77598b1b4bd3cc6a7c604b2a856da6d028fad4b966c99ffec0fb925ed17d49b9a046985fd2245c20055c9ee2e2e2662db6d1ae7f9604db68efef699d6b9e68b5218d75ae4bde8eb3273c7383d83c6538e892bf675cb0ae58fd401545d6444ccc96540a95e2343ece00d5a35a46f6d293ace5a47f925b3a14ad0596fecf7f0d8461e41ecd4933d1ef038c7045091a2f4f714189b56e3ce6c796dee3b99ab8963069e9cbbd0d07c4362db00d6df9f9d965288a10e27a2d226da13197c7df092c4f71c2078aebcf77b6cf902a6b39e166ad79a231fa04ebf29428539a785def99a7ac35e5092c539ed082092f1e5069dd2996300fa8f05cffec4906e5fad71b121bd5a2df39c305b67a7737d7dd3e335cd06f0158e5e85b12d7a75c9ddee3ae9e96eb6766509c663ed348f1e13902b131e9dfb8f75590c7d318dbcc9fff52862db3d43d128b42a1601b3ad23178c6798097c6aad8d27be81036e634d90f519694c0ca2d6996eb1f4e61cbac15a3e1e61c59915cc2662eefc97ab21614d655b7cc5ad90da8d8d4afbba338a885ec3a0b7cb25cf21771a2eb2c2a4d220eea2141414141414356b40a9b6c3033576631366dd398bc8ec251abb547666d9ab1bcf96da2f695823bc8ab8d96c4699f06ee083d38963517fc4d0c1aa3405fdc50d302d18fecc8be68649aa79691bdc4b930a0e5e432dad08285b1d830862df9f563ecd6dce564bfa6698e849e7eaf9d749a17bb654318b6ac644d3b79bf1fed9c3dd670b29ec0765fd65bb20bfcc41a937d3796360361e0464a5e6feee06ab5fe55e1b14c29aa2c4262d3b29d79dac6d0b5b17687303c58ef96610c77c1906db21d26b6e3f1ce49129c83d4375a6c436194e89ab0cd7c52e710f9860be0fa97309e653bdc4fb7c3137a851d0febab2185d932dbb9e4cececececece8ea85585688f27f364ce7e76d7d1038ac852adb53689867517dd32311b4d0216caebcacfb26c766aa7837a49bbbab395d33815204ed31f7aff21f3f4bdf79fd8bdd775cf898c0299789f79ef39f78fdbff8d9d9a25d52c1920b4028be3a403b13aa6565a06cc5dca921603359525ae5b3614b2d2a64b6c7052b483dd5153a36535e4e660113ab68d0b2f2ca595ab349fb2675b435ed8d9bdc4a585cb75931d992693c96432994cb3566bc3903479405ae59ec57558739aa609c80464b22691c994e32c98e2041254110ec9112811903da144a39398ec996deaf5df78d8660157545f243615d504b96dba66cb96355657eba16375f48ab0a5c75af5b5d0bbd6ad81ccaed1938fc1257fc275a723a7023b369598077dc85b3fb6c67ab223b98da5a93479ec04b6742c589ca6ec18908d309c663ec902e914dbf0e53c86a3c6e4a4af566cd331efc17f15f355cc574042a368ace167c983b0292a4ed6d7365ad671c7fc8c6674344d95c73c88052ef9d728312962269e7999a82cf198c7ea0af496215a4878df9c81674c24a55eefac64e008304dfd2a537daa8f0f57b94a67680301d3642408e225e9a20571b2fb8797806033986cc8740e08902f5421bdc5b15a1f36125c84ad1b906abb1e9b244bf2e6851848b25b9b6b2e77ab63fb19db6f5f026073bd54b1e54f72dac832b16462624bd2a4a1ff9f4dfaf384f779dfd951546bc85a300c59ad3db195393be3c66bf70dee81ccd3e5923f57ed04dab6ea373ce3cf72b1582c1657371d374e368bc57de16962741c8967ba91e425ff26bdbcd4a7d6e285737416ac02db741078c66d98b66df3486ce39486506cbd2549fb9d256c13ea2cf8d42d2eadd8c6a1f889fb2aaf700ceb852bf117dbb4dc530c5cf20f9fb837f12555c71ef124ad049faa0ea0e8b2586e05966729c8f71228d647eedee0241027bdfbfdf224ae013e75165cf2976232954aa2f8027a01f9caab413a822dbb45ca38e3be629f1802cff56f2db0748eed5b6c2d78bc94b02599dd9244ba2114dbdc37ea6b608c270079bd5eafd76b76f7eba5c542477855aed76a6d1872f8ffaaaf202658e699155dad66ac6ae6c9e45c735d3535975f3ffaa7f2ec08c12ed79daef0ce39524ae7970cab4eb0432ac7a71170c99fe346a321b5776265d46aedff1776392d8413ad1fa08a7043f800d57d0e0b4850456850dde8a4ec57c332180cc6fd42f0a261482c8cb9635f10b661ae613ce32f433eacd5629b5e750f5cf20fc3d188d3b8da2037eee8ec5b487663b2f16b62ffc6fc3b0806e4ed8098b0060cd630186c4b85506032916418fef0d6010a123da8229b9420504e9eef9c3d6cb331c1a71ac54ac1548c804fcd44942e81aa97e8257ef8bd310ee8184bd2746c696b845a0eb60cefcac1ef9e9e9e9e9e9e9eeef9e196c0ccfcd99cd99c324c273e5071996ccf6005ec36a69ccc2fe224a65a61a71151485645266a824d71cb031c806dfa3f0a4ea8828721ba584d81722b331882142cc8a2478707943fd7865579534ba9e392d3885c8a1ef43dfc7ff8dfc4e0ca329c9ad512b0eb4488199d94d2e7e197f21cd9b3e1a79a39e2e81b97cbe572dd48d183d591cb3d40422501977b8084756bb5a19b4ca2327edaf5c978cf29d3d3dee69f97ff663215c76f8ae46dd73cb1af9c5c72fd7b2ced6d15cf20615ff3bbc1878441352f30b0ec426e08c2d2f71bf875691028b8a7b8dea2819d545ae5e2c1dd6eb16cdf925f9dba4cea8f97ab5d292f881b6fb2d137841999cd916783cdce4708c3728fc72b244cde1edb354bfe2e977705c8742b3664c5d2efcaf5675850898a0d0244cb461f16a8f5a63a95621b325e681c3f71d9f748f11390590ac34fddc44b2fd2c481e30299960613db414c930126a3300d8e936c03e434f4b9a0bf61d8208e33c0341f93fed958b9cefbc090c9025ce7691f08ce100a6f8f373e7565a968694162475b8b67ecb8b5b656b7cf4929a57466d9ccb21108a574b4a19fb3cc9015ab51fffa9e4f181eec16b262391ea2ff40226c8a95da627fd27c7c2a6b9eb4d72cf1380d578ff84903aa3a308d16e4a46b5160a7d161a5369d4de534b179ea23db6b9e3ae59bcf3c09c9e2c3dd2d36372027fd373098f4df94781f1812d9518bf78121911db5b4930f0c89eca8c5e50343223b6a7189cdd3c6036f6030cd660226ddb7d43c8dc62d082e7916bb1b0db6d4d6e46e48369611b6dc5a2f1fd6dc7ae6c9fee86bb5360cff6bcab4c15e5a74aa553ca3f3b3f9380d07da518b0ba96b6e14e2a47f8e38c449ffbee69620e8ef6e168b63b17e6789930ec383ad3a5b2b64c5965b2b02dc0d812af048147ce6895d7b6940f30426712dc8694015683ee00ea80428fe3cb98b58d00578c6bf0b1b3864e02161c56a9acc01ae7f5883a4851d18b67618665f8421199a9ad6df361ddbb6755ef2d739e2790c322bec7e0ed9c2f66a3b8dbebe08044cfa83de7310274722f5d2fc2adad04bf32d08482f8d989cdafb74ceaf8eb067e8a46b7724f6dcd0db52c8931b1a9bb59af71b716851076963db9e2ff7a4141337f2602a65583f668eadc4c925acc5dd49d243d60843d0412772780a2653b050394510e0c31228a0b42f828012447105aadb7440e568dfe5e05baec0333aae2b802ea0139d6835d07efb0a68bf8d31b302db73a81e4e938ddadb30c8101c2054cac9470f86350ef5ad0a2fa3d16ce4e093058065284c73523add338d9a4ca64dcba8b561ad0d53154dab2a928ac433a42c23b10dfbab48b70718c840a5fa81400c532357d3b1fe53fc2ac7829c3a35735464a8542a958a54a9361d1d3a74b40e2fb98e5ec23f1d59a6836d422f3787f67fbf3e1843437dee6ba8cf8d356c6fbdcb200cd5880f64ecd040e504103f28414d60071a9441043196e860069bc85b0dda73da6fda1341f56f3d56edb79c6d133514394b74fc204370804c066198389c6a85a48a98ab6894852dc330c481c16030180efd81c7e2d839265f54dff0af4ec536c172df58c1b582cbe572b9b21478bd5aad56abd5aa86bcb0b3b30ff96d7412173947f7c0377ce019ff52002fe2f0a7a52b02c54675df77600f909fc0adc2d52a0bbd1fb8a8822a3287806290c9078e3f6649c401324b44b01e03b6e95592663949bf5f6c60afd818acb2ce4193a47bc5334ad8b2e687051cc7ff6b7eac5eabd56a95d1b95a550ff5b49060ee44ce90472925e709038db3040bbb71e1fabf5ae5820b150683c16038ad96cb090e674dcd8fad66c82ca528a5b6fae0f5019ee9e76e8f41b2d8b297e89eb259ada46c9abdb605116b4cd586644d8debca0a7684b8eef95779e64d97bcee7831fe4368f07e7b1fde6fdf3de610e99efb6dfc3e87881322f5b7fec4d4ca7160cb4e1121b27ded3ea6df47fded7dc4f44843fded6ba8a31d6be8c656e164599665db4ac7c94ef5604b86a5623aa6c7981e5b1d47947f039807df740e1d7dc34f3ff35c850e17cd5ec7bf0e6d27732f891320274f605df48debdcf39c522c47a162ddf2af6a75fd65388a1c92dc1ef344c7981f5961cbbfd9983a400cd5ef69a08f1c157a50f4b31ab4dc6bc073ddd94109308a8ee12cf987848d80115e5c44e149e284104674308517ecf860091e44d16117810d884842c9ce1043e8005584162b86c4164e408107148f01860b9820038817f01043bd70821f24769ae04511c040f9fb379d9e28e4be6d21b727a272e592a61a9e8ec5aaa44db47def78bf7df97387c5da444a8e701d27037cb2cc2506e3fab7499bf44e6a04ab7ddb566393216ddb0644c8909c1a1aa3405fb4200486c02fc4c49e460cb03fbf796abce45eaaa9e1a9a9d9b69f53f453247a065b241a6f9822101b703ae5343fc5edc56ed5a47a87c5309cb90571d286f9cd77dee0e43604a7b497e464c9586a4227b7bf53d4ee0eaee2fc849c04c6d73d3dff9082307430eb646a9ff7313b482ffb76d4dced356b415148140245a391e7b5fc376a1965df6735cd6ad68a342d04825c58eebbdf3eb1a4975627bb668e3523efc7c463973bd16a771dd97bd9d7a66eb51880ef82a1eb6533f3ce38db42f445d7a324a53274febc73d26d6e74fbeae5e199b55a1b86ff2469ea3e0e84c1033ff0f340204256644521db8216bcc09060482f307fe340b86ab6b4b86c624ea7fc88d73c4e6a5bd8d2de1a6fb14df6348460d29ffbd20279669a56f2aeaa4aae9397679eeaf414074defba92d08a42e0e73dad65d35ee264bfd33a67af9498bc1ab53ccc935ede69883344204230e9ced19defa31617d20b8c0aa1672f50b02af0388d5faf9bcb14b5dba246ca5ee85421e4be300cbbd32e0d51ac2385d1b1ce89e5bcdd8843dfd253d365345d5c462efe4d0586f473e4c117e6fd1b0643a2898119fdc8cbe8499c147de5227aca55ae936556f494eb38e947445f79921c3ffda0af1e1c6873d67e2ac5c919f3d43e29a01d18cb573ca91df4f6d83dce40409cf41c27fd5f4cb873104f3f9dc15dc21ada735d7d8f137dcca0bdfb6c9eeff9d9fff6dacfeebafbacd3c23b438fda3c5cf2af48d4ef23f8344489eb4f80236ce9a9920f6dd27a32f2e00b857bff1f9ed788258c5a4a622b146fc6ca6629ea295769337ab01b0f37baa774e8ca93ccd0711a1c86e4d44fdd6ddcb66edbbc6dfbb60ddcb6d0b689b6cdb677bbbb15c79cdf9ce09ca1394573da394773b6ccefd1e43faf3672f7d43cdfc7e3739409678c21597978dca58227e08921a610d7df0b819fd771d5fa3f57b7f736f16fe598347935e454d9972a99a6fd601b67544d8f1f14e0d307fc0942000ae2334813967e2cbc592cc8dcd1bc5631fba4429db32ba74205b240e6e9e70ad5b442df6440f0c06cc346e70899fd6f9cce2a93e7e4396dc87cdacf4bb50f4c4a5d87161275db802ce0e48ffeed290f4c93023e851307bbb8895858be3d0ae9cb3ca33d05b8e4ff23f54aa552a95488230c71a452ad1eac90c06038323019980cce3cc9c8c06460323263f5c2fad7b45003ab81d5c06a603670421c30180c06c359b17e7eef958bf269246dc2c793024ae9143bc0b0eff77a5c5dbd2fc3d07454e83d51048a409128257a0b5a11113cadeb7a9cb6698c8f4f8cf1f181a1181fa2075f347a2aa67bf045dd8bbad087c0de8972a7a7fd44719fc7b988ca0a09fda1f3a8642e16253d0bd18c0c0000004314000028100a860322d170402e9935351f14800a7d904278589b4aa35992a3309032c6284318218680c00080c8cc681404b5b220f9cc5e6bd3492d34d766ddd02c8c3e5fa64eb3cd378e510d666d1b230dfe3827b357333cb6b240192658e0e40adc68a8da53dcc0ef3aeb9cd64ac2b925bd57efe8b3df3d2dbc1df83114630bda482d40e9e1b9747963bd8ab829dae53fa6c4e850e6f0e8038763e253f4534041e8b89b2ec538e2c7710d05ac49417048c945506497e0682b7cb2cb558305b36d020dfe0442cd6c589b98d83cb5928168f71069bc42595e49322875ab49e1755a1729a952358eb4391f9102d398bca7f0927416c399688e726de656ae55d349e8083006eaf9fca3fc09962a3cb3c1338ae7a80ac8e6f22c2c4485ce3b26257b273eab4bab644876215b5d49c7543c8589c62e99b76e538bc91cdd84935f78c136983a2e0f0f92645886b678f5c68417adc31aee1de48dc9dfbd002e7bcef52029a641c280b920a09a1550cd8572430c4e32e424f2a4f625cfc67b35627bff09844e73fb5ee50d969db3de64d32a84badd967a3e2295b150359926226f699b81e428533202ccea88d7c44fca73debe9b286d9990cac1909064ae6fa37733ec4edfb9360f483889a74f3f7062785aa73e81c596e60e88c8b01e256836902013d5095ec93e349fe6ce77496ff5902a8a09d68930d87c6750bee58e8d4e62937f7fd114528bc8c2ec13afe011a6b087d7d1a67d4b95dc57498d2ad83a113e2a4b9fc2cf5dab0ea343a9343309990c806b9428539b84df61d5fcc8d6a7576c96c22bfe4520d9d17b8ae171d25c5eca69f7633f2c83c4d4d2dbdee80d06c2b50958f9a948f8a1d488f06105eb7203496cf25394c87591e510a3aec8978d4b0c0ee01e25ee8c6a70ce548642803d21d5d00782cef316e80e3d55ff7f64c4dc40c0e1426a8d4feb5beb29c3fc9a25f817452759269314a45d215ce057716325e084db2414ba0c7019e4a0557cb24649159269c222f36ddd224944e262ce5f1c9d4432653bf413aaf2d49136e2c7458742dc2c92d4a4b04f8fecdea2ec63279ace4149722b51e5a2acee0a2f77030efaf01778e4631ca607337c04fcf1f9bbb595a589c8ce43fc6888a9ed9a6560dec997ff553ef856316160dee4088b4909b5186b7a15a80c7181490fb41f67c480888b974d4a1273388ef516d1c4536b5c9a5c139956d8b579f653ecc71445914fa1ac3a00e7c3e8137f4d50fa6841014a3d84b4764c054306be24302568e644cd1c3b41b30c23fbf3dd4f9c4bd50d5be0c5a0e8646846649f22eea8ca7903779fe88b25ee59c295f5a1fe74e0fb66f85507ecc6e7a8f24fd6beccc5f1ad72388bbd51620d3d0584b8068c2746656dfc0d468df97494768f935143a44efdb5868bda432d40c53bd399b3ca751a7daf4182fa07fa22b74dc2bbc3954d956653a3221c022d66e2427cf1116b4a3542f4d0ad54abe3b0a428d08d783580e265f872ff5415a37cd2c290cf0ab3cbc11dba9f0614cace9a8a6cfc2732b412f8f549490dcc598a78204bd7c5a26db73cc26e2fba11812c67582944b047ced9fc0e53a94022583108b3e6493e77195fb92c049b92c3346e3fa3b10a2ea307ea2acbb89336e2c82f7f90a38218c766726a2b3e56e65f3494365c02100b28bb8c0c40ad5e5c69bebd12d7ce85a5671d7039e71375566f919af789c3c05516778e3629a0100de9d4c0b8eb8bc15aede334edcc4e45dce45a80eed14f8bb0e94411b3c1a9a426b5bcfbc8de7c8205237777fd631ce7f2d493ced6ffc40ed7395f823b74bb58fb866c67a2ccba87e6d20ad94e54ea840493c026442fa60bf244ff9506194cf84d3cb9c66736044027fbc0c2a58cea051a34eb3977ebadbbeb85fbf55c063a4d99a49ad8ef100d32bb6d4c178585e02ec62bc71c472adda82f830c4ea6574786a30b55fc1fa6845e8edef03448ea162239171a5ff2436e1399c22b1b0ce5f3d4a689e1a034023c7bb6c71e67d1919edbc847b0a428f1402e4d85837c5d00d2c6e8330945913a058132eda3231634b829d55d6cc83bfaec8abf06240e6eb71319f4c4edad4c043e5a93925d18bde8e37bd5671ecdd472dfa7f7d9fae404fd2cab4130ae9db3d9a83a6c3b20df635e68d96fc0616306bbfe876bc7f62433b9ba44a1759a6216bbfb677201c3c237605936a03f0b80029e38ec1957166add2984bd5f5f6119db2a9cf1647d10b132b746ad72a313cde214316211d309b0c07168cc0b8a642863b05dff53d18288c33920979e68e12fd252b77c49426e8579ae15e23d660c9a5fc2b8275e72d026a8a667790e8b915c3fea82c56fd4da04782132a871225e13727acf4a14bb26c4dc6559944af29febcb46b0929d1e02df855836fe4e440d7e9675239269554f504b0d4c6edd59cbe989b31dba58dd7d05b3d9cc04874261bc8c6832b66a0d845095245f19f1041b59e8ed39f559d99f35c8b70901297295fda7fb6adefd5db117fe092ef20b0f1e0d74c7633977b691c153178c3f45b46ab2d04d51a390080bf51571d6e611a28a08b230317e47822fdbc2f8e580752eca87dc20416b0918791a6f0343a1caf112c740c43f9614366557f6dcca5a9943157e3a99bb70ca190ac526ace5a055caf112d385674364832b58648682337e17165d2215b2c1af89f4f9144774e1c135399f6eb018c5a6c49fa527af9fdc04b9b37fc9e5f6d6ec6813c334b8ade6fdb9e561efe564685d8851b32676ea0b145fc8694725491e8866e7de41d84e51ab884ecc48b0cb8094665b8689439e51aa1121ced1a13888c2442e823115cfa156b3f854c216171a64e766d3869c96a165fe74872e309c22f94dab696bca927b6a1a2847640a356dac515bfd8dd4e1b4573c3d670e2e98328db213d28594db5bb514b004992e3ab5f54eb324b0fdd74ba9b290eadb5b48140b85feb675ee4e965b9bfe929efe8e587f298dd67ffdfa291f248cbbb0f6049701cf6a64b57248e96540fda2f624513470dc62d05bd7455bec7106eea22ecac93ea14430a3321eb55faddea25b0a2103fa403cb228ad464b25edee9fea2de470da67139ae2c6cc9d9300880f9f5dc815f850348da410f14bc08906395d521fd4e52c486eb64c96cef7e3c38d575c384f09fb2b3361a1b75204e7f5fb464681b24b1c964c7fea2f46048da41751c2ba880dde09a48e886db58fbea804f3da3216a124da816811cf50534b82f3028a881dd0eee825101e39d157c2a2388e298425cfa4acc75bc2358e4f319b4f2ff2922e5b5b874d135efff9a564ec64a63f7b0f5a2b7bd3af6c4d1f51978bc27fff6f0744b587ca7c1cffe999bf6298c9cc8d5254ac1bf008fa2853c743f705fa88c8dc6f81c0028e4cd38cd6131ff14ffb58866cd14e88dde50e09b516e78eb5a7b31a37a90c49d6023d7edc20261524578857a907d139d6797b87bb7dec7e72e4e5154c3919a79c4f9c226ab140bcaf3b83c542b2b307191d187a7af02e1d5c444a680c55c42f61f3dfe5411bde8001c7bb065fc59020ba4e1de2fa0339ec9f7f67499efa0ff46000c3808019f7a04a01b448f198c36e9b8e251e7f3254acb7a7e2ad911d11ec74f225f6404c507766abab8481aa52fbb266ecc9f133b846bcc9427f7f2c9ce0d1c6aaec1fef4068e43525f3dba49189f67ae87229848a03dd352d21b7485a33c70f4243f575937285170b987ad1b826e853fb603a586a9c337b905c5a4446848fb1d43d454dd8270cefe6cbb00f42dc4fd4473f14cc7a5bb301caa190d9f18a95b27d6890d42b89fa4e7cfbb7e7a74d47400c8d9d61a9dd6383ca342ad5991ee506bb646ef2bf107a4d6404d5a0fa79511b4a3142c88a5b0884c626a5181ab542ece0541d39ca979e1a3c61d5fc98c6d2425575f673b1cea9670fa452834891cdd984de0933b2fcbcf20c351cec0340a9bebd639c501bfadfd31a56239f15157562df829a5bbb495708d0e5f9aed5c68a05d19a24540759b76b3c5b40d6e5f3e3b6c10456ed01093abda0189a28f5c2fbfe20fe55753529cc3355c44d67e032dcc8a2755756a32b1b1d6f230b312cc6dd319fc669fe7db9e468be156a14af8f8c4f87a96bc435068bfb4cdaa72fbde0829f3492d353d0e7f9300e69501b7b8a15502b74a1f64fbda140dcb703a8edd30986488fee286a3af049570e173a5be020c674478614c5be9a1ebabbfe2aa49051bff1fd0b3da17d76f501cd46dc95de8f323f6c3ba5795fd32c5d5b9b670b192eab5494c49e1638f93555c22515fcf792ad84f1ed0f51758ca0245ac30521e5821846ba1daba943fcff3f95f732aa65aa812d26d8cf2ed5475106ab5e732ef1bbc8d9cb9fe059a2070e0e34698970bb3291dc03a822f231c92432ec946a76081825f2ed14061e50e8528d5be0e5b0619517e9d9ef95e905d98cc97c24dc84e00bb811368179f898319d2aaf992baa73393ce00fe7c02f7867cb7161214dd761f597f033f1d59aae5309241c8d2250c532b37ea2bbca68eb2b71cb74aaffcd24232bf58919e5b4a247c1c939f534129583c2021a5599218e73aa013d73ea1068169102cb4a9c3247fefd293aeb4815859c1870de770af2ce275e1fecf0160855599892fb51f33fa3118ee08b27839cff185b0051a247a9d35eefa850db992a3dbd38e1024f9daf097926cc088ab191c578a23545c4bb7932b71d1ac33418cd43247640198a2bc905775d290c266dfab2751ba7c0a23703503bd16c2ac4deefd8fdc7d5e4ee4b2bfc147ee8d359b7907cb6a8c471528d2cb4a804a885c54a5975230fa1f25d0f04b150d0c46289e864a41b27ddcb2e1086b3534799f6b0fb2183e0bae2b5dc490e31855d569bb055d2da64f3eef297f7d9736c0b1853acb01e9e31ef7f6b54fe188d43e7fa8e604cbb6bb534175dadbbbb1837a17cd82cb058664756a71e68d9b70db0c4adf7f6bf7d2b22e752fcc0081be352a58402e472b463285d63dd8ab8cecca7f1f5022ef9c5619889dd7bcaf3f869506a855f5632ef2ed7eb7b11987c4a461923723983daec943d648c45ef421a6ac334805c6dac5bc496c83f043705385281f4d0d0047a557087fb74a7c1c18da86dd08131a7a015ccd3405cc01a440728f5713169c5d10778b0abdad986b3ec829aed2eef706daf42f5fda349eac87fe58628655044796c3ebcc215e49d934a7bfd57dabc49f09b1d12f011be0798da76722cf3889b6634d2f8c94f2ef82521d41aadc757f00632a48bc9587c151e2f414ea83c0460aacd23a496795f9a82a62385a87d9132be6c69dbad6eb8398df80f5480a71de1cf099d39e005c46f114910da7ad29b24689854c8f012cbf97c2db7b586a6e38e34e437afcf81a9e05714502b704c9c32dd84ab64fe0d360284f1c080c0a94a3810e0b60a7f049d5f993e9594bc7cf53ee35cd964d318a7f5f3c05e79c604046e82944c368f72b585973e184c8c9401ae15d57bf087ff1d464c9ebdb21f0c02881ee38280d524b22693bae9e902a7dbd414a16a25cabaec532c4fff0d8b6d5e90ab6fb688c902e7381d60e3a8fc49fab03e4080b599407b31de814cc1b9579f7ea7dc4610afa6a628be038608ed464047d0d531d8ec0e8264c17bdd463055467df7b2686ec6e04a8c59027aa86c46a9e5df0482f4323d20e2127d7e6979a550fec363bb19e570aaa6c50668f7cffc3c6a36a344431a5a8eb9abd56bc2dfa77de3801022d20f1e4e732707e796f83cf8f17bb68a825212575a0cbbe13653bd4aad488cc43673534c84de359260bc38ac4ef4cc11a7cd047eb51887044d75a016b5cdf83038f6521aedb681aa0de57bddcbaed920175c1bbaa86b67334124c984286bcd28ace9dd8d85b29f06440dd18dc35233633f439df75def2805dfd0799cf9f87c8d493448157927c073c8a20724d72b426c8824f1b44270e289d45171f126a8bb0e763f97f201fc640953805ca5ca681284a98dd32f10ef3bd62555a1696c45c393aaa0dee02f0ee949969280ce93c4182f2e4db8b8bd51d5a3e9f6f85c34e5086cb640dc7d2c8c439660b3eb3c58ed956d07b000842e5a572c6739db672943b6cafdb7b88e5a4905c24df2de942efae40a9f0c8a368cf1fb2399fcd0b4565e8f9922febf11cac81c3cd302b903eea1c195afac19f40d7f664ee8c1c2b1c467fe34eca5d2f0da2a0d83c02ce69fc166be5b68ae258680e95c8b73cc9d129720ae3e8bac0028bb2072472c3c68884c6053681e515c1722d7f3819a6f88054b91c65d0162a79255c63c95e39472a384a8605648c2d5efe43141073ee5b6ee9594e91fe7ef6a8201d39489bbdabe6ec269d9a4764465b0ccb274984c9e0e3d3682d018d61225ffb4ae14764d8c0c2a518f42a4384b514bc049e54dde2094514c0bcb8522d8aab855aadfb06e4b9f81107acc9456517d22031cd148d449ef204eb4030150641797512a3938b0bc9c9a03727cd0d7b280d46933b2c8e246b508dee97b60fbd0fd94e520ba782bb5144c36795d2cc86cfbe23c0dda84b6853a4313a15fb3ac058acfa771938141253681036325d84b020cc2a186b076df61b30e2564daf21ed95c5de7514252463b3934b4393a1a69e9c95c15db079148e6cafc7dceb86cc49066589653550af01a94ddc3700031578d0f88846eb75c750d791f70c3ab508c6a43084e82f5849132f2480a10bf82b177e5795bb4e23f96a3898caa3cb90a10bf42cfd9a771c18cb7914859db3406096ba12297f729daef7ef68721c860b9e2ac52aea3790ce907910683428cf9edbb902c3d0e5d71ad4936d855299261cd712b7b92c75250e11af43241e55b83dad873736e6aabe521ab418e0bd34c3ef67dc761d0dd544d1d45de63082b24d08cb2d6268f79d8433ca7bcdc97133f54625e6d0dc9f8392d8ffea88833cba64fd450829bbeb0a2e698786f183c4ca4fd4b7e741f41f56a989bfa9129a8eb79ff7fedae0effbe57601dd2dadda6c886a821ca7f498e4c41ab227cca9f3859f37c1fa7a2159301261c14f8e974b70a8a0efd63430cabbb3348875e3181c7359cf3471b9459e3bb1b37d8a8f7e4c6fd397501a53acea5744c712c5e2f89906953341c612447eef4c0e7b59ed9f2038ff8d093aa6e5ec1b96f1cd03de966b3fe3d8a908c69653e251ccbb09f2d3cb77e8b9d05ab38031f8180149c62deab136254137a07451f5690b2197392943fd0f60cb332c780ac63bb741ca6761c9580ecc10d0df4d7ab6f5ee2c29c3249cde8220ba07a440b54e723479f5e8dbc3f00c3dceac46564191c45a5a75c9e54bf8010d96071769392efd718b94653dad26cd21a68bd39d00ec9adeac6d39353753ce773c7bcdf63407096b7b78f11fbf2ba27d1a153f1df68419ad7fb2b9f32fe438aa2cafb18eed9fe9e2fc8a6c46375c128b53483aa8d7219e7b74ce49ca83a9da719358e2e75f43dfa348cf786b7c901d0b068f167974a71ef85f15abab42d9b613d65796fdb3eb45cadd2ab41c15b377612e7c70378db10289730e717231c6aaf4b1e8410161565cfd801fe77ae039f44b6eb4d5609a45a289dd644e4467b40b07a55f4d199c1d3abf5127b0a64db4418c32f52f93018b3338752146524982810eab0b51be033007614d9ca3c92af5dd449c9e978e69556c30c21f092a653508b37768f25b06742bad8b766921621b098b8d5daf815444c31425a38905e54b562568b96edd2d5e054480e787cfc6a011aa83db25833eb2c0d5577f1a4a66d8997311be975b30e56a8d3c86a82ace881e3e68c1ec6f04a27ead00af81e2374eeef410696de52c2e82ce4785b9e9e5cb3a72754acd9045f5e8255f1c5e8d0a128a9b19b477df0d7c6d40ca91b18a51e36f372c871e55faea6c408b45f46413cc397deafb28e6ef299fdca6f55ce7e6eecd5f004e334f97df96fd921ff6b7a5151d10b8d4efc0e512ce957bc56e6c0e7f721ec1d08fe625e85ec3c4d00bd1b8c3dc76757d6f116c33f6d508ef9e89f665c92d3982efab761496dab028b4c90550d72b112b74622fccc8b50401864c1830c2bc9398611c2dbf5b1e8af72bc254aa268e9bff8ce9039af739298ed003322c808ebaf34480d1c13a6c4f03a9f2d3cfb86253aea5c35825174781b7058531441590759e61a0fc7a13db7cd56f53830d9b9a16cb1077f4c8345ae64b316abacf9d1a4ebca4704b720fc7544e38df566db9c74a0579ccd52dadd0a2ba858bcf521f5ade825024fec2dd486942509838fcdd8f49dd9ba15a321f7a49d62d4861e683e31138a674ef1a2901882d1c289a5de8d830a0ecf55023966dcb0fe0378669460f5d5ebb6c9cfa6d810deca5618d5735b2e8e86744af37d0d4585e4f15aa9767be498985b1ace1e24501592ef0d65beb6f2e5e61a7d3983d9e4f82e463352b76520096610502f45f99307ccdbb2f9694d232497ba50088e3e5e76a5e26fc664ab59b1d0f81e49e8d54bf51f0fa1545f833740b4c4518fa86e437a6b9f47c7bed97840d6982a925bf928657b0ad46eeca8c03bb67f633e45a2e91ca9b9d994b47eec72bb13363eb30db9238db70a977494fe8bc4a878ed4e102fed583394986b7ecd648e86572aa081eb0df5d37e658ea4c90b347490d5bd00ddaa3a901f1bbff322b54afb6980063691476a4753f7e8c2cdde97525c398f52354889b712024e12408b64ef2c424826f7578d346e9d29f0c770798d41c1d28d4e8f1dae0c1bfb0e9d026d587021a4855c5dd8301830cfe21b8700e4ddcea802de65f0288a851471e95854ba8b6f43dca659a3ee2d1083c6087d31d3589b1e29af27c79598c4b7619f7ca63cfd034ae346ddd10ba8d43644b181683e8458ef9a7a05113ad8c48dc330e27864857003371aa91b5acf1e79ea87ff4c50b3042b9965ad4837bd8686985ecb24d89ea5e68f8d0f1d5ccc2c46b730fa11e94026d4f5a603b5c0da8c37e565821aca6f19c8ce5981ccdb6a272dcad28045a93475c62d2e270b38aaaa571f647c16bca172bd621f0cd4144b6eb56ce0d655889847767ed4633c2adea0e61d785fa16d01e1516383a71c12bbe08d73c7290cdb727b6af162020f25f105e6ecea13bec5f585bb4223aec4b8929a91382d6a68318b308ad1643a7789c8e3bcc748df1ef9c3d8317771109311395936e469cbfaf00fb0b7b5f495ac5f25eaba9af7052138e2cc04b5da6c667b222cfd931b6b8ac79b337d028a56e76e93c440f6416e2c93c8d536e6a4e9ee27c7410559964a9d480f5c693bbcad3201e4773b6a13714f253a6c4b3cd9afb493bbe68bfb615bba417313b151890a33372dfe06483875c0066cc7ce778097c66f372476f51483f1a827470654122db922082f6e92d339c60ad357b33ff03a35d4e5e4b006262217719798d30b2906ff1c7d335f5d5e19e7551d0f4c5a79bc3c24edbc116996221f5c4847b43cc03fc798c125c7229e656e974bd48376009458b08392a0de25a82615299be37f4304071f9076e375a597c8f05a1a4545c675b1f9aa8547d47c6d4a5807565c828c409d23fff6e94610835fc19b2fd3a4e6f9b6d3675b9bec26627819421ec1420d437834fdfa3660df53e674aba00d2b7e9567be14829d9013ae48b0aa56d60b15034aca8fa33987dd0eb878c941235993aa13741f3aafe5e1814c2bc63a510e3730fdbf641811359760d6fc1f67693cb0856df356bfc40f030927808576943851220516a2e89525b11adc67ab465c455a490e6defb0f3d8753e108ecc664213da2edb457251c42689f19c50c84b7d2e379309e66a793aa89d67e480de06daaec01f4f8a1444d8ce8a57f7691cd03af6d82c9473245230cb797540745ec5f9dd8ce1f4a0cfa04b8f093c8291314b098a88f4ef8ef3e98700fefd639ac1d97e49b17cc68c9472f6a4bba9de5d510966110fb0d464e5e06c0b27ca6d90910d20048bd3e123b9b69327733f14fc5ace278204696197bb5e0d4d4346cffea93356749be7b69e81f6f5000316efd449942c7da8f5ef2eccd4c42dcc49e99cbc9ac8512d32f70313ba53dfe32c760104baa3c735ec398dd6cdab46938e0afd36c09200f8109f6397b631892339d64f5a3d202dfbad638ecd2ab80e586f485792d4b3119533ededc65c5b24f0711afbd7154ab6f87d86437eef0342e58998a6ed48d5de90ec4aa054e92a18f0161aab97e89d9c135b2c3362cb55f194f0e6708d90b36d12b40c7358b7734e1929125f0567029fb2243bc1b0ce75657206815e31ca62c4635abc1a3917e51c934117e1be88bae0b41dad6e0553b77de79754db39496f12f71ceb7dca23e3e2f74704ac21cdd265f36d83453e705cb5298f4db5694960a48e6df389bb49fcbb9da40d36f43bf53e43eb6e77a0841b9e7a952dd8fecac89ce94b519da14393ccd9c10c107ab626f86d1403c56ef2633ed02e5f527be7c3c7af9d63417b065800aeed4cd6f63bd307b0525743d410666cf8f7ce6b2cc3271f04457644a26cc173c983f69944d03538d662e6d84325c5ca78a881a00f338a6d9287e4422682319c3d2dc38eeb2e37a42177789c9979ac762c1000ca2f1da35e466a73ca5e4928d3f15d83cab21eb80e89e1f330f5ef9b97916064a4d2e32bf8004d40aba154e408499ea528b6f897d1366f566f919077f00aeb712de4f8dd6ac3e2ab7b80d657776db871a979ed4860b357cc02718f1e52002b2088c28fa23193f509e2cd8ad06c41f1a0aa2030667868ca91aa94d9a19997b75d282e38c217e860ad8e0fdb5cb2bdaa56e9bbfe77943f9f30efc8b54ec34ab46bfb2fbf5c84d0d2f62688d84bc99f1639afaee5debef25ab74f7e4bb6448362ea59f3bf249c21b06e7f49f4a937b8a236b4458fe87d9492ae0e2935b45274ac00384758f49a9dc29446641804637145094ee13659cbb623fdb7b2316290e19649f12ac947999f19de34f4caac7f8a86c6d93ef95dd52188280d24e7f61e238399abc6ea75e3aa58dcdc9268838a07419b848d06425f3f5c18b3a6618602a6f49bdf1f3c30d999fdc7ded6d3fd65a74c5dad82196249e85b187d6ea1a58244c5fd3ad76cf7052243f595067d3847b5f6b38106fdfe3ce95f3e2fc5101b46c6e7b98407afa87f94f2c1838253c920adf96f55e1a257671811c6e18752756eeededd804638f45d551fccaaa2c9c049d98878bd3a842d1f68248a49c82dc760e90d5b8c3c888f9bbc6c4a68f1a0d406cbb3ab3314abfec1c6e2e0a4dc90e1cde9688018b3a2100bb6182555f4fdc1856b45d378aaec4be917989f65e9894be3d0878a2a8be32f71efbf2a072205e95793edef304a75a24c6ab6ec1900e509dd49ca7a828865b7e2d6ca091102dfd8886358a42f731e368698f8132059ded9668683de359946a3784f99ffa88cd4a7b4467eefba92f9b829ad934b974b41661a4539f19874509aa21b9e0e53c49f13f775b4d65015e91401e7f5023ca8cff4ae7c2e48b0f3603b26b692e8c357c6cd2f4120b388c40a9e2edf496d872aa14c75aac6c0401926c584e73d760bcbe13296fe49f47c0b9ff38d8b2d8baa69191e68ee0b1fa49f4fcf6e8492581af2090d610d9a2ce9d74634eece9fdea7db0edb9ba855581af035711f1ad5aa675ec070e1930594fd473093445e2e39ef090f30161a1b953f4bf70c17d690a9be5a9a3b2e6c94542d6f285b3e8f4b04604130a6a2cfb9b01827b1ccfe20a6df8779861afd355d532ea99e77bdece500893956ac257358023473e2ee2f2ba23955daeaa8ab3909723371a3f4525aba6fd5821da562edb713898419cb1ee4bb2ddd56d0efd05a04adfaa9780a0aa4d54e6a06b6f1d37b21ec094270185b585af17e832768d741f80592fcdf82276ed40b900003b7d8d69893290f32a2eafd495409af8aa3c28f932a4ad3992ac68e862c3ed8757baaf9d0895bab16a05c55faa752597b6b464340cb3f60c8bb09c74bb57534a266359cf9ca9ffecf2cff9cc7ad976c0a915c02b8e27e2cc7964df3cfd606124e29094dc91b7cc3754768db1e9686ccec4a1278797c2232fc58d990cfc64ed7f67cea084c80482d26ce2d68ee8a89cbdafda9d29092dcf65d5a0d1c5cd1352581eb5085cb53c27b427bfa8ac4f9b98f46d18d8b872b18744844e573850d12240da01477e4642e448d92413c670ae8a4a3f4d8c26e349752b46a7019f3a47d56ea0ac6a6d7b02ae33a0bd8df4ecf19d363a0f83b28ca6ab4195d215fbb35fb44a1cfd736efa4d164aeaa3d3930c5786967753c662eb4f264aeda54019e345d27485726413e812b93a0a3228853979dd9565c9457b08a8cb62e4512495ab044c89ba627b22e6b5b02746b96973346f7d03a21ccb2cfde7570d8111ed27f22fe5fade88cd256aab2bcd2c1329a0778b324b931d9b03fbd9a41ec161b860e82859943e64b28e800b28db506aba65c6387c9162702653e47b3b81653a403f05502dfb4bbdce7be4984bb1d3450326b94770dadec5dbfba5fe18097d7d1e850441b65bf7302ce9cd6b44e7210dfaf88a87eec6193b6431d3670f9dcaeebdcf4a713019430d338244abe3509432dd97d41862940c0c624ccb785f0b2424ddf59d203216ece1de9139c111d5bd3f90532ac23c8e134fd6cfaa2461eb8b21a178a1ea9c87ab905e981f90128c9bab4a4c1c40251fda8143b5f35d91facd50c4481ff3ac37d205d943c5cb8275a15d08076cd972ca228246d8c59257f8ea5b933a05e09ef06e62f108f296848cca9d82b66d733541c1768a5f21dba2dfdfe1b400b5b4efb94e15b1a255923db020c17cee8a8db3b800920cd933f03613de04d33ef5cc1e4ca88e63a6142dbff2c5da9bf3c340302a8f36b4c7521f13c910b2c6a0330b6638b4ce235bbc417ee7be721ba19876387b78ad9a1c9b44aff526d9c965860b404e46add946e6ad1df54845662db207a7fcf121799f2a967160c092036cf1dc73f45eb207a4c12403cff8e66ff4851e449872ff2fdddcaf84f9a7488ca34bdabc770055a34f1f0dce637f39441bdc7f728f0159e72c4a6287bf47ce2f572ec371d04ecfe133f4e1c7b02969b6962843468d012d539b6b51ccebd58db2f342f1b8f44d644a9ab3668c383f8ef4a45b4f67ce006254bd79537fd634978d30a6bb4c33a6839f56301521b05ca2a6d4e7063eb1c2d4ff822c0b4826f4c64f058010402eb571b7f80995ce52348be374f0721f206e39301cd30ef089733f0ab146ca959e9a555d17af546a3326a4c20123554af6f9cabfc0efb8f00408276d5f9f0d4b56a8aa019cd716891f47270d61f5c87e0bc63a036f36d7df1a18a962808678e0ce3a5fb0eb74845ff52950eaae3d5606663d24477e4096375ae12a05d21c6f79244275ada5174af269c7f57f97eed1c66d9be2fc3a6d8aff60e2c5ce3bbb445809847d2d9a99fe85e9cca4f42b487629fe7cfa6e82d10891c538ec3b8027769eac83fa75bb5a479b19d7fa401e7113fa938cfe4f675c8afaadbc9a69ddbcc9e874fc9cafb196c5e3311eacca2eb2bf328e228797ddef29273be545903003ade81e59fbcf2a11d81921024743f301eae1e90650bd8c3331f093f8a375cea55ee8bcfd0e788b85a25fb5e500ab64def3cd92b7f07be1f445ac683190a3002d9212953c8802078455d43ecdd5b0e6e0aa7882c1269184078f8328be6d485351d5502cce6fbd412886548010c32b43f47de36f9a5c2f0d281b2b6bf25b2e7014c60e7a5995eaf96855106f697a04d5ea492d8e87b911e33b9730f2d6dd9c7c44b18d2daf2cce4cbd6d2eeb11f03cf99b153a1d2552eade32298672658f5e8c12ccf50dd36957f0c09668c8c7ec03c56dd5885af5093ad69462fe7dd32f2417d1cd4a121b1b9c010fd7ca4db3d156739afc5956b0b5771e56c59fd67a635c773e4af3c4191f2f0d70eb98c454c86dadaafa997719ac723f6f65865835205752593adc8aea7326c71e4ca16461c8641992c3cb3274109cf515d99cd9cda3d065ff07ffd10ecf589a13ae6aa6003eef4e74070e3f0a885b2ea132cc8383a6f6784a4ed0e912c6b41b095000e2c8388f9670b5254fba4231f0e3f616ed2ca4d4559ebeda285f9f2c66063c27c2930c43105847b22c29000f43303eea39a5f12da726441509837cc9e26ff7e3716cb9117539e7195a7302bcf1b93482357de45b4f77a793c5349ad454e15ec6bee91b6c1700c5860ee778d16cd7a3af5b8a36e9768790440b1615f05a8991271d5d57ef1301464480fafeeab943d7d31582383945f422c2eb0d5e8adb477aab441077b34761da4b9cd730c08a2cd457dcd7cbe2052e439c17e8b41de898b335a9b0aa9e31a174cd9e15c5f091b3d02d4978ff90ab765d26780ab00fe4cc587f5edbf85b067be6759b8ff3212d56d3980616218db8154d06a1d4a163f461919026f3d742b1dcfa703d456f2fa34cc645b6810da965a76f16af533ba70ab78efd07f300147684df6cec0c79c56234794592d3e806fa1ac675f1387d5277820ebf07608eb29e360857391cd83c77682ad8e828017c425d916843aa0da209371b8299ba1f294e6d4825fe46080b730fb86cc060d3cc2c42418c8551241dc39b602354a1202b2df473a126a3046974c25cf8d05836823b3e9c4d843360fdc5fd5ef25cd87062b2b92b0e37674a5d635ff3e58db1024bac7a9ef7c5708ed0ce3d9846e5d1a089f8c31c859f5e656c892f212f416b4ce15eb956dad2a0660b485c43a854f9be623436971defdbf7331d571ff7cc815930b2c776892680cb1d90910c1c444410b0d8409f5725aef7c4d1268c89b6728f01f4648b73a3ad2c4a2c1930251ab8d5a12fd65d5673d05501b7112937e366ffcd9406435d19b6d4b6c7d592df665b0f2836be222b079e4582363586fe9db8dbed08a074c8fc9ff02478274a1a812489c359cde6df5e97481ac0a90500c3032b89027f2861b13ca1278ef10f9e602d40c8bfc57bdacda053db10dd2c6b62e2954857b1cbcb9829952a2e6b00628426f53b22b041f615726f82ac670598cad4d04ca803f4575e9c9fe6dffa8c220f9e20364d90de67eaf407eea6ca4f13cdb48240c9f1702e0a30a455338cbc068666f80f03156a179a98cf65529647cdf26a6e068ff2dae76278c8c85f56d619556ae934ac2cd92385fb5e4f4620d57ba662d1ece5cc2c30209b1e94a07c2239cab633f42aadb40d1b848bb26fef282cfa7ef57f76c61c880cef7a0168f351c8ccd8b425ce616b50791fa2da1760cd452bb59c2c60c1fc5845cb1f1b5e8faa7b0f8c67070fd582d2d2bdff8df6d6421c9d2fb564ca822a01dbe538545392f87ab8cf2ad3be1815efa8f41b58337f51ad1c67d1306b3d01fcbbad76aeaf0b3d9848d9f545a6c6827b4e22a005a20f097eba06089100ef949d17a36f7eb239ee40cb1a35780ee39b5c53e426173e8f2dbc3babfd008e7914f6cd1e54498e148187bf25b012d5c7f0eb2090ddb951f8f36a01a1f09537068a4d072ddf371ee6684d31b22c3f13469ba509518fa3a99954ac55772eca5cbeb22ad7e321fe96b09441a617cb61c157b2e1cc80f67ee1efeb616b2daecaab9707e5fad23dad6dd18856c012bceff01480b7934153f122b6ad4579ee71a362bba2cdf82509a8db4fd67ae36f8023d5b03a7a91efab38b081c375f59220a60c3a39461984f0502a7214cea89852ab33aace085e11a185665ee8e618ab3e7984d722faa67489f79a651a0fbff8035ab8a79645d737f3224104f7b1519133832721741c08b35622e63aeadc50361a8709df3006598259007d577a636566ff766be70844dd47eedbdf6cc0d7269c9e0d9502ff8f486142465ab72a4d7b5f6816eae59d8e33c4517f098551a52b63dbd556cf67ed7c1109a0a54cea89f697a9fefddd08b0f4e0333e6ac79e896ac1c195d45a7ac4f4e4d2681e2317f480ab471367a5995bb6fc6d19610c72d7ef0755e32139d8d8736be9df7997affa1e5afc5728d20fa9c9ca6409ba9587a60d099871cae703def82cd99409866ebc37d9c1b8f510db729f19a3f358478a3eec285ee8cce3faf2af48a285af253458ce95b8f0b9ebdf4d5b8b5d387b07fc6a6ed1f02d4c622206bdea0ecdc5a8b11ca0e7d0165873e303493eb9a6c7da2f3fec31409b6dc5f08641a42b53705bc82cdb06562e73c67573ea413656881620d1414647e1a2216aaf8dd1753b9742c6402751b5e26edfaa90525571f53d738b6951ca5d64530a0f22c3d1b6be0de0d6d4397b8766ba615114c4eeede03bad671feb6b86821d545575e18289d4123cd89faa803667809ccc74449f1e18b089a826379b784e21046f081e182caee05c653225b61f3e0f9abb6afa3581f696d7c0c7d046b3c0182dcc8688acc81c36826c98d534c754aeaccb0e3142fc2a890a36892b17d782044b9471917c90dd3a8fccc0b1cdea722debd1af90c6cdc1735f813fbd429dd75af8bdd77bb6e1ab7ac6ae786bad7027c8f743206e83bbf5060e2aaf8f4989a8a9b648bf0ca68e74695fc0606b276e47d5ffb2da06a6c31e2f7855f92e853c83673cc4c067679f25ad838f2ed690bd63a9f4bafc42006031e8c842475cfe711667f3bed9b1d85b3ddfc9dbd3ed2bb8afa1ecbc7e6c9cfe342ac5086d716572e35280649624866c4faff2692a67ebfeb9764425f8656e4f9bd17c60277486649c08289bc5f8dbe5bcaf443c9e7f8db8e31d9d294ada1290e5345ec3f18af0b598bc155d3f3b2ec75e8833afdda490e9f947e11939a5e122c301bbe0663cb5105a52a570ce08ab1ce5855424a08c0882aba1706473c44c48575d5605f848780813a23045f57a103c069d4d12e39aba7e6b0a9c02a652b6797a060acc2f8850e30b960dbf0b4b438304ab6fa7e0dcc59133390d8cc91f478fcd63ec196aa25a157e41dc1a710298b2a708966b0584e28d1ae1c280349a22c66b2430fbbd6557e233f989614bc8fb79ce5077aed9d804a1a8e7a387fc23d4aa81fb1ce117649371c91704786aa6a65e0878abf9bdaca5d43e20a6e7f01dbd8c8bce09437ee9fafd899461568145e2c37c7a43ac32e2486ba0f138a9670aed744c135d3687ef09357bdd1044457bd7ac52baf13c77c9d6baf7cfd55aebce6a297c692f6e65d77a1cb6e77dd9dae4237bbed6ad75deea68bed527537b5120596f9076d332cb515d877337d1c147ed27f9cec77aeeaed1d1bfce072a585ce39287ecece856464dfe73442df7c1b24a8df7cb7f48268cd9f030fc6005c36eb98810d712b000a3e0b26aab62a81c5f8f4eea2549554ceb9bd7e40b84825d6c552bade7293b768d0dae1f2639a9e9595418470f9cd4e84cb07e7b8af4def71238d46d58690beb5cf1c76134c892004b2ff6c1ef28f60570e5d0e30c4b5831f2104f6eb74ec292e0abe6ad26f2ccd0f76fd2e63e8f1f599a8ad5e9998d6b722fdc26ffc7ae81281a8210ebdfb33ddf2221ea6c2e689c73735c1aee759360386deb945068d65fe2ab3e8a806e3063618a791732c55c8b25e85e8b2281d85c494a34db4ea169249a93eee6ec2900f0a837401b935c4ce0cdafe0c99557ac103404fd07ac0b6f6ac073365234156b0cc3fbb051cfa4b7166caf34a2f0754b433ebb1b364a1cf77862ecd1a2ba063b8cb5b19ba21490daca4391c83d44099751d69ce7c30308acee3341f48f87680cc4c8a4fb84a263fd68e79d65d1f8d0a24021a5c643eb861bbca481475dc1634d4b003f121d4ab539e3a68e61ad53671c21d70a6ed7b75b298ea1617c40ba905cc95826fc6a95c43018d8562974a77c46b1ca33564950bf5dda91de16a40482db2568473973dc568ea8e2748dc4aca8536e80cab8241509e224550f7ecee1b67dd005087aac127194905e822f414da0c7581327fa65906674a9853c2cf2f19b517f5809efce85cc3dd150e5a17660f262dd4e43490ea69f19bea1a5606bf83e7a443a27e1eba1dd129b030f059b380830550c3798e6fa48c46bc99ae39623819129df09a61839ac945b452a81cbc79cb6a4860ae396c39bace4a4398a6108f2333ee04142ed46e59badc6b327d2ddba06220472b7028f308f4e10752d6c1e73fe3a500e11d77e56f2bb50198b35fe1c2f02cc599757a4788a3d2e99d1766b383fc9537c8771c963c12fa85fcaa6099358190aedfb6be394f33d0d352864aeb2e3a17b0fbae9131b48980d5608c22a4b7934cf5d33515caf807081884817013538803a45e7fda4d18587b69996c088ca11541d0d449604f521b767569b0f349bc2c84e2197f93512f671a5dee23f3b86de1ff4cd99624badee75c9d3451e444e14a51fe1e051beef3744edd0e0828beedc371a537cf9406c518b38ac5556071ec6907adb9848885479b4bb354098c07da6d0dfaeff973b528638c7f1a2740f14d06562f8b7ca085bbf373f54e957b956f56ab6ea7029b09dbf0d6c23e21fc05c4ca06401379851700017158edb9afa0ef507879d5109b7e65f0ddbe2948ec12fc6da51018637f00c1c526567d71a0874a5e5239371aca33ff6d1d001b1100ee19f2d8b03b53975e5fdc3dc5360ab3aa7a376ecd0cad9acd735da2562f140fa53bde46763c0bef5c7def1fbff28a9321fe6ef8384e37d0270347f0447c8a9a68c07b606911ed1f284921667fb220b3a6f7b50cfa18b4684b162d373726dd8320e3842709447a0542bec52e9c9255a77e57b9b30204de231f7a77f0183449a899a4290c82afad7199adea6ebbf6813612b74dab82d0b5027a9d8ded3fad20584e69f584271cc1c2d789b6e636adbf7cb780e3615dab7f9b6b245c7bd5ceff621c27bb715b37484fd71545132317a6374871a58d4c2afa5e71f99ae400e1084e995d383ab043037568c5896bdea1a29a4a15f325bb77a3c3ac8b1335715624b6bbe4ce09c2d1052f7f1669ab87809350c22f770d351f4c2531aa333d4620e7fa696872c2af81a426cab900544e129a5bd93df614ffdaea2a460c5654373bf57ee01b5a103cd86ca25433d0555cbc0e710031288d88d5f8dabfb31b07469cb691cd08dbf96be15b7cc7a73e4ea20b3f9dfbcd7701266c492dae64ac3f7121b23116a3156d0d81054be6f42289511992717198cd343479d44416485a0420cf522a38a58ba4a5f625ecfa5364eeeb4f3cb4ad7291a2ce96eda08c74005b71972bfc766e58b8521ff81e0c2f454f14e378ba4c061e2b58f8f4ce5f9b7ee127c567d9187468b6dc874f5acba749a807234b774473c238270f933819c447e915dd5694ab2ae0febd9ecc3f2bd98392700d9f7b26bd401e2a9d215880f28a6b29ee27737d2647399c1a011e10ba66aeee5b5f0f10eb9e87e4bece337119c961b1d73400671f47133060110b77631d43f8844333c30190062d4aae812e8c4f2703ca9fead86210e56dbf9637c63ee9a27ff18aa1946ffc48d5e4b545d739a1dfa66898c436e8711488c738aef4d1ef8c0da652fc7d9ab8010ce88bab2ccbe656382372251db130be9594c5cdaeaea66b4e8afcb84fe10e3de93203c1f4e9d8da73d06635702acf94eead99c745a9e8408b65b259e33579c1791c2686b76cdea8d7ec0f58da99bdbd743ee1b17f7e384c0f8f5538475cb0d8f4a076840abfeec9b306e917b86f85f7f844021476d496a9e4e754c010449a65ec335a3a26f48aa4b59cb2ef4336f270f56a07d62cf6ae7dae8b39b173955a0da2aa69c143a6313fa2d78ac77c0a141f28ea20acc1b16ff469f9b70954bcdcb25788c26c17807987a56b786d5347a54f855dafdd609182364bd7fa21d65af0df75bee0b31e547d007b821cd6a0266ff02da877322e370f379b14fe0fc8f638812e4bb02268fb17af4cbb1d9139442ddeab4d771eb90530f63ae1d68caafe927dad7c1b072a415087d99c7c0e83d2869ae58cdbcaa396c2f961a75db28d2874117b87a9a3ec035d6a28fc19ffe62c97e37a6056adc8e0437101490c678b5980e0df6ce1053c0006c6656e7995d7871d4f3bfad48dfc6e8e1d343961b3b3ac24543bc9ac84df0e482b233e6289a77bb3d711199351e814094dbbabfca34fc3823c9d62f853318b8f3dc4bda2c099843e0b7135500d2023b8b2e978cd7c25e3c32a7ba03e98a987effd21af6c8ecb6ba4cdd7885a69dff224115fca985f099626f4cca8e79e361b23dcbc6605427669645a942be1b0b1dbadd05be990c1b765740f462ceddc13509002c4027d31145709cf027a34ef722396f759352eb156f9ad5117c8e405e6eae8d18264dd40a5f2371f3b227a10e8053156a77d1f00fcc722709c868e6e0dc0f8e8e71a7d836e07a5d7962653a6a448152c470ee9987a99c846fea95a91270e16c5c845ae8cbe52c36036797578e7d187559455c24a9cd3293e88d187f5803870190dc07d0e50520d19a925ba20ffce9b944a6a1ae7fe2926b47c07f5394360c00d15df0df76c460b4bf7fe43e5e305ea26551196503a8d0784b8088a85cf9b7122a1be2c6b4d6b8aac6278121bcb6df67433bb8082486767328026f8d63f14432c8b9d115b229b046467776cc0e96d8593a4f0996da2dd44a3d243cdbc9ba74d0893d6124bb07f59dfaf9b20773325a214ca8efa59231f877de9de1c59b23685ab9c2004487be2caa590f65134ed9a4146e5c0ace8b1b27fd3d878fd076224f76e8c9103278c5cea5e9be340562d778d83d292c932ad6a7610afbd0fda0fdefeb6fa40774d4bce56a84c023961c3b162e5a6f7d782dd296b6ac8136551dc82c37a6948291e2910822f490c09ca8f8cda08c9089898cc00f62d2f0e8945bea351db5da1fe41c4b4e4949e486a4eb18c1e121c38f18630f447301d61e031b5799f1b6b2225b9c125fca7d5c4fe5ec015f0aa06b9fff76a6c0994986927eb9618370abbfd8644bc299df8a56249f290fc7de633187ed07004d7ab92fe8f09b9a28076aa0a73757448586731066c2df202cefb610141fac16bd0cdf1d946284113464d5d6d5bd06b94a12b7999a7254f593e88b4e7bc44d88936def40acd80beedeede25aa815656f340cfb0c092393362cdc23ddebd5478a5d14be8db0b85bdc414e917fd2520fe6a0c0ac02f2142c8446a06539ede84fb50302b951422c7786dd36206b32648004da3ae15d8b7b16682b7eb13fe4a4941554444f78e837afbf6c443436f290761930cefa64dad78798bb1ca3bef54dffb178954819fdf1741e4f53cdfd89c698e3a31c610f89dae29092c8fce941bd21b1ffd17fa833b263bf262d985777bef2b8f7c98c22881f8451b8ac9df03cff692570d7644dc250b4aa0dcf1496755dde8359ee17153309ea60d9d102b1c7d74b16b2651d264f0a3b775a90193266f62c4f14b1a31034ef41d963c736df4b00123ccf8fc008b262cee2317935ef4141c60287777341da011d5bf2ba2d23e2b3dc69942a2790085d5fa65116e885496502214365f126678f1e96d903b9bea8710cab4c0ac64885df61de06e700d507a7d2f22f865e062a590e5e078581a01ae0f58a3937a78611815a6da8cc1de712ec8f51864981d52612ab48413c62cdc885aec5b71a3931b48eefd9d708cb747a4fe35b3a0aedb3e9d7b26a9f1484a47a213867f15157e2403d41d90a3a8ff39e3ab738a40617a164e4bd71b1c3a75c32dbaec4717db7febe1653bb5d90c335f0bb82fc81add0c1fe613974446ce24bd48407f4cef23a60063e19778b25cd3be0af5393a298326c5b12c2a4b7935d50589736b494582fb786fd62316f6b7bba09294bae4a4b8224bf4102eb067e5595f095566c18b94932aa60638ae5a299d8ba8948c71702a356dfca6319d6787ab9ff9db2d196480bcf7483e6357d8776ce700b045f69a3b83c7690c54bc45931ad9142a9f500838c50828dbfba4829f5cb96816d9d38cb851625c397352ac666f2cdf688bb1eede289ace80d276eb225af4f4052a29467f7a24b77106fb2154fb71da3ac694ac3bc46906e54a7f695fd341297e586e5053143ae22600e7a82cfd11a7d7facaf1c23e2dc69544eec7f0e511f7139abed2f6d7d1f7fcbea18497f9484f5e0a38cc2346fafbf033ed286707bc5cc97a621ea77e3406f53a331fd4fd5eb643ec60df558eae1fe4de08adff937978c3bfa4d2f20a4156d952be9ab4f51e9719e9b33d34f2061d3e2f956255b4b0643851162494906402dd17b2c04d68e7ce5a65425914797640ed91e6e3d8d7aba0f4915fa4a355572013fa18abad8f70fd8645c27f86ac80b8ba80331e79f9e1901678656590c93096d635c4d64e09e906ad87a1d7c5a52d031d15599f01b8230f4737edb7f2623ae22047fd8c9074e9b9b8a8d9aae7538e6ecf1bb772545c8cd582f76994737d691ec8372e2d8e755faabf95d01566d6521b1c4001813a1ab0c46237373a21bd0f1236f64fdfd28e5bc23dc404d993227c1f98c202d34ff83526de75088dba4de2a58d1a430a089d0a745a0fbac706f41c9e9793f78b8fb72ef8903927fd72404a82a485483f00a60a78a58f8ff68f798c6a3bcf834204d7f2736bb1a8ed93740136929c01c7b789b867bd82fc98f98a6ac2a3d55ed0f79cf8ca360def2e3d80af00dbd51779ed732fcbd7a8111b03b4ee54e62e55a02e0e5724b8aae7e77d33b45a3d5f9d8aff55c988f66850c27898c244a96dade006d7023e2b4d03154cc7474b206bcd7436619a9fa207788bca8e7135b9918960fa77862db313d6b5df4ecc4a266a6d94e1bc08b8bd534e71cce7a9b96f63de77fcc5d03b177173f37d253bff66292b1f56a696ec3ebb9033ce26c7a5d2c6bd21f9460064bb072e353faad7f744275c230602f3dcaae68f8db2c7cc4c2bf89d486a01e5c51c260019d4da82cfc490f5e785eade26108d2d1b556156882370157483ef52011a157cdac2362ee5bf03ab90ecfecb1604a61bd72f97806eb811c1b6e18613b23548d57098079c3cef0bddac758303b7cdc899a4846b68bc4966037ad4282c74a4fe9bcb558f6c19a662202275dfed490b128b8eb1e261800a65fd32b34a42daee25859a1128a16cf86c9d1cf7fe985f2e130e7083f8b980fc1085d6e25971d84adbfbd179f5380e8a008326f9518866828f081e870ea8c2959051c59d9d2a18c09a5512783089f4f217cecb172b12bc4a6f0d52e44998c2162933044d5c9990226567513e2ae98e73f7de67a951ca41ea3fa2b9f56756c4c7f26e65f6f3eb7c9a20a261a8f3109bd858d012f94bc97c83c5adbae4a60a8623e91ed53b3fa2656c787abbb85240adecc25864256857742949a14ee16573de3287bc82a0b7afa77927aa5dfbb0b24d7f69e13fefd474bfc24a36ab909852a79d22e814b3b1b08c7c2877e6e827640a2f04bb516a68334ea184a91ed2ee04ff2220ea692a3b0ff63327800d2d2e2a8e4b04e60fed3e8be0982a0f8e5d9691b8530c1bec839063908bbd72bdfee210963e6f6d23dbb79cc95804922ede2c4083457fb4a490a9a494281257639a7b03926d4a25d8a92750ad6e0801427073545729249c0883f1d5820ff4609196d804533261b060b972c326e480011b4f2293e1eb847b41253e301b9aad19e34e448bee8edc23a9a054e9fb3f5f72e1d8ab2eafcf2c094cefced2a901d55f8b45cb225627c56787a487d435eeedb3b804d2da19371533fb831aac0f7a7bedd93d875c38bf8f6538da0d0eba65c784184d2adf2bee7aa7ae4455875e6f9c252956f195b848bd5982c58fb30da67e4ad2bbb7e7ca1e6f3a465c4e676a2b93c1085f8a80132bc7b6f7723dcbe239e72dc4fa5862425c7b12a86f53f5b0772614b08c4ebe9ab517cb5df3e0662c1193c91e9666cc3a6b9148b01db6bab5c36690f081fb48a552bd61ab73fe65d4c6192674f08ea8c764c4d993d661f1a52835ff84c6d68bf0599f829d77d4938449da80cf9d8853f9331117d4ce9702a03a196a1aad3085225d0e1546adda52ae909df79fb2658d386144c5d92345d52bb65c7b9de2c973c8dc5ea810fd1259ef7b45e54fb43aa50314931d8ad7abdeb9ce3da950c38ccb7205dc95811c4f0ead999453fa945b5372566f0ca3e3f28ab221e8f848911bc0aef239ea692e142f136b7f53929ac0b5bacd69ce6fe8e4dda0b9acc9f3bda2f5624aad4dffe8cf701d514be8a54745c014fbe8cb22f5177a146530d2de29dba52b2e882862a038868e0deaa235feb14a99950b7611f72d57d57944a7685f34130029d2c2c3d0d5ca9ad3bdc4f4610311e87258c19276bce586058f440be6a48d470f51c19c6639bf595ed02ddc0011b17d1dfd2aa729451092e3cfa3ef0219a66120162b3e9795f55232fc6946b0851c82a80efaf14d8369b9b7ea58d7823515f01bccaad2e71730df20cd8ea1bef4a5f5fb2abbba4f41664434219023fc591030e8fd533f0072cfd3668f2b2bf38a385a088c004c384f3ae8fcc364382f489c06ce60e8c337ae0b60f34eca2b8d124c6d312d8241d9e7df9598b1e147c3fbbfc8f2dc6a45d8fd0c048b4bd6e24b77c04b71e04a954828a5a330c97dddaed9750667e5fe7542920ac295ed4fb5866a0375b42a7575cc3b5cb684fbc0aa74bd0e7b62db9faeb9715b3ae306a35f3aa0b1193ca07aa710733ffd64e15224f9e2d42ca6c21a022578c55c675987dd375eae447a7a97084a51c7cae7c74459df4f873ebede1e72c68a31297ea5252289d28e6155b040509b739e6421143970584ffaef2d56ebcc1b1ffc310cd20a0fd7358fd488e83b89dfe410796aa3bd3eb2bc6d55fc3f2a1e008b6a6f933567bafbecf717caabbb01604a3b6de4e75b1fdac45d55b94d68e2e36aeebac012eb6efc752c35d70d633dbeb3d9183bf154f538aa7b92ba5742e2abe7af501064d5f7c38a364fc6eb17ea66b7621561d64ef0c33407fc65235f0e434d8630734191ee9933846be042e0292ff9d19c7aa26e70073879e3d973565d800d794459521686870de57888251d4f420577ce7ed02acc8f87752455811e46066c82c33ca536dbb89f4e70ff78e5c443f9bee14b7019f7e2ca701b841ccc9316ec08cbc825b1811c624babcfda0206aa85ddbacaa4e9c95274c5f6dd32a4e32f17678a0eb4989863ed418e4c77c5c4245d3a8dd7dab49385dd3e663f046a890c6eed0bea8ca312a6f49e939f6ecb408dd2b2900168c2d0c5ea9e88cba7495d39ffe9a2263ec651c2bb5a315f6aa708e2db2b89dce8160216a0c4c8bd14f3d535315c466b018210c3d31f465f869431c3ca9ec1910028ca80df90dd27cac9eec6c0da18700d025772775c9348f64bef1b49f51e879c4dffb8be41922f2e077f220f95b3aa0aacf89a07f82c9260dba7ac62b93aea6f1e1bb659126337b324d387f35118c0c1162af24fe000b441d32d9203d4106e5e4136d05d51d7490beb06768e8d9b5b2035bb8618610f79cf6a10694af2eacb57b740ecec3d53568a56c5da2fe2431995134f044a4564637d5c4aa0664fb577917bab48a6d1a57104f831b68377b2a9b5a627bc54a4f6b0fc263e4e1529b9c888c02d9aeac42549e8c5a69033ebe45554fa5d10af5336873cd23fe900c95969467384b30e5bea78a01c0e35beb13dea287bf67781eed641d5d8d2526a017021bc2f242818e29b872639ac057aa7e2e46ea196877d18205185fa157834eba6ad321bb5af0bdd536114cf928ece201ae1a2ef5a3af9d0085d7ee57627b12b2198f64d39bb0efd746a1cb4b0775efea93ee4542c9a679faba447c74bfe2b3b42e96183ce537a284deefeccc2bfb085c3033f56739068fe18411f7621b6a236df094aaf6b58f050c3b51f34a8c1476b00bc2555d60206e516de08af27a9ca62d762c8d1a3fe0837404d6708d77802cc1412de75d72e8f083353427bc753addaa56148b484e5b0e9a11aa9cf657cf452059ad3e53037c843048cd7c3cc09474f92d98a32ab9b2706e97b59af0e75d25d516481bafcc0f707cdb4d8590b9b93d77569ab2167620c0ca03bc97f74c249724ca79d174d1c8286f45c5e987fb4f9d9b1063aeb974a60d8d138c45111388298c162ea910f3e216970fa7d83a79edba58dbdea4e72a1ddb83c175639a9f7e14172e18cbb1b6b0bb86978d7fe120f34ca64999a5d28d9da402ef527e39c0a8f19f7505422a780ea8d40af437387032fa206aa3558844b3ebb7aca1f0e2a720ea28749ddb6713294c94e95d7fb2b93e877f30f2bf0b1a1397c5a1ec15058674330b27a23d02f1fc6904faf7eb81df8768e4f024da7cb486c0420131f72ab8dfe66b24b192800bc9af657d048f680d101284018a63faad50d7bf93053cd74842180ba4b844b8012b67553c0f3d6ff81247d43500af78ed6060cc6cc93b54ec6f5bf068ac8d7c22d260ad21e59895f6113072ad942ef1cb74f2cb7c4f54681d2777c9a2e195381b99ca58614a05f757f073b7d0d1c8919fef3fe2c976e69ce8321b7ec3cb7401b7ec0bf85e3ee5bbc857d1a4ea49127357b76db4ed6d55d43abfe978a0fd3232bde81fa9c95759f85d1ff6bed3de8568d9849454e9a695f83aa4a30ee8d1d9cbf9aab470121f5744a69bbd937d0ae1bc5c66196afa6d73720229d8288391dea2387da23bb31fd4ecc820c13ffd6711d07d6a1809f95a5978128c26fe920bf9cac61404d428cd6f81aa48eefd100324d3b9882eedf4e2e612a0412cbbfc4726bde32812011be3a6b7a6a7d9688f020e0865aa2889867daa37dd522de90a8e012a6c8a427ed90ae0127b46270ae16bd9aac28ccb58078e0d921a80b65ac7bfe782706a269fb508ffad1b14581af3f9e5d25cdc87a106a8ec9370e59d164494588a08b36af4664dcadc8f844c622365973505939799f90916c3855240d0eb050ff76e9d86b9945e9a0f4d635985b8642d4ed3904056358310e2a5ede0e17bf2115d3e009167bdaf3e49e15db9aebae09c60f21552454a101bc7a327898689a98521a494d5f4f40a8d42d7aac5efc9a455f324a780cf2f2e5ce50cc707847e7426d8853e26ec1e36bb514f140dc4b167a08656cdd91d5574b995bf402e552f112a1822c3f26d48a05f6d469118bea3afc5099e8c417150f5297f4c822d3e4d2856f87cc3e61935fa52003ee2d9ca2a6daac4eff6cce7d48e7d58b6f002a78bc7894cef7c50c8f8b1eea8c03bd66c8af5944d7f9bcfe4ab951413ae5e446067a2357705c407c93cced1d2eeeab4d8ce0b58cfc2ea586d9b75c72300b99254e30fb9f2489fc2ba69248173d9163c314e78c8e8bad6f99efd61fdd870199786fb18161d93ef076bb2c9b0ff7e1940575158d1242a9c67d9224f727f3141b7da3d828161dfb8d7ba048c2396e8104dc9f185abc41ffde9fb7ac4f14e78622cc614aeb47eeb208263048d0018bb17fefc78195a571c96211fb06868157dfc0263bf50d0880bff8966bae74298941f2055451c9e5e82ed69a1e6a56fb72d70aa33ccb12ae4a06fce06a41c4f4f978d7b9c9eb2ce73c7ea22dc2cf9c2a30b2cdab0aebf9d1ed1a291c898dbf6b9949aa17195f49f4271cd061083c11e153e578c7445769842d27e59bd106319ddb61d9a38bf2a795e63142fd9f38780277055685ac643179b98a3168eccf73d91b34fecf5cf3ff0548b119389c48ccfe81d3e1136cbd14362dbd60e74caa92debee5d50d522cc5e87f43c367b1c4e52c9e04f688736904dec5535a832f700c7899aebbd991e76afaf8400763ac6481d62cd8afa001f666ae5e376e31fceba6a99550e4531ebfb62f933554d9698f34ce45cb5a91290c015a36763bbd0e36640791de29c9110ebc6b1070b70badef78049508f994a296aa943b14a1ed4e335bcf4196dc1437e9a6f348861e10f4806b926dadadddafa197fad8dfe11de132d49d7a3a5922de0973d3739dea620130f111f4766b1dc63e0cd71730d77d5c5d04082de72b36676ae0e18d2796f41e766b6caa70d82c26fc2a6a9921e7e0822f6ea6de0a2c9973ba550a4a33b7b4fb21ef7dff05c318863995491136cd6eef5134eaed321db667e18d57d378c5a3adbdaf68d5a7286e6e4fb8f41641991482bdd056083efeb13be655f5f6afdf65285059c2ce9c994c2391d9fc869fd5700d7fed3890ea7bfedac3dee3edb0d7efb6f6c699c91a7fd66dd5ada0c1648e6991d472a23e936532b5ed12542a0f9279ffbfa569e7676e4bd2b853882d960561124c0b7602b103e31e98acfdea12f85dc222543a5cf2a0d1d416add68958224063163da7d891e682eeeefcbbbd16e6f3fcaf747de370c83450c5ffcc77bc9e746cd2e6b6c0e4af6c83869e66698a8ae686d7cca7032e793b0725cc41b7ba7a83f8b50f15ceb4ac5868d45b5293976aecdfb843e01eb21363419335903d84877581e899ee055c8c9611394f6a7bb5f488d55a630bb034a91f7537d379cf947c38016552a4330fd6eaa627e6fc07854db4127d0e1771e5f005147d3825cb6b9dd4767913ca4960c78ae1880f47ded302b56f77250fb723a846aaae8ebe8be67bb701fb2c46605f318219269fd9d1f936d07f9d0b3d9243a0cd2784e7e32b5e712cf8ccd403e604217f96920fed50287d8fdc28ba12a34f9433479529b469ed41964676ca95c5bb4e3cc5d6072a0265b81804d5508d23c05a7ce011b3870a7949d4ba2dbeb42ea6e1950f154c1d96ea27c84841988e1c992f573e43b33bf162b80aecc39e0e9b44e00894e67b6ac1a47fd73761f904cb9b0a780c695df2935a4711506bdec6c4e7f9623afe6125e104bed3ca06c4bffc59048e680bfc8de6507b20cd5eeb33ad1760a17fd0e7f4969095867c12b22772d7cac632731f3943c661913f6d5f67d3e630ba5e25ac19a95779f7adbc24c6ac86a37685eea61f1d8225fc2169398bcef1f30c5f79dcbef16336daa7b14f208780a6dad0c678395a60d5ae4e87a45ddd02da3f59efe3d5b48735405faa4be847d0f935b2ff2633abec608117d12fcaf4c63df8bacd0670906b87f6e35f49d7f629ef5b6157a4881acd0ec872cb602db9069405f5a1a9f77748d4965a650e90bfdaf92a97d520198bc9d2de7a0c1961691d04e613762b79eae46b402c55354f71f4744c3644c9c1c8f9c79614ff948a33a689fa3a0919676912259ff71853be4e315dd4b76435f210e789d1f58e2738d67f54b5c0a77039a521caa45a5cfdfb8196568dd4a845259d8439955545990c43e1f9b0b0cca4c293863ea38f295c2fd8eab38d9bfd7f464a68afb1e48839fe0d2ad901ae1218cec9407f3bfde259270553e8b08dd9e3ffa32544aff1671eebe318d83d82665db5844308d847498536d018c96c661613beb2cc92ed9630766d2ea9726080aea40066eb27739a317152ca94452f6fd1e41c0a4c3b87aa02c2e7cebf39f0cce2d642eba94542da773a67446b2aab4851c348214c8b0c51ca564909c6851281a5b35082a019b6f43379fbae3cb794d47e66a7d239ada2b6301f69d7b66fa97ca923f8277bb09a7d7bc66a9eef3cd4ac64a1eb22051180e761e324d90a0ce9f32991d10790b8177dc8c65e4eb2bb7474e2fb7a0f87bcd974771b15ca8102e124a1a1c9a3336e649cf8876c49a179f8338ec377252d56904ff200f03f0fe3a54377909f91b62cc9a61f490f4b8c09927dc3405a57bee6bdc9dd7ad5b8292b6cb92b4702f2c742434005ca0177933a230533314b8d7206ba81df857a9ec7ce82f76d8a3ba004e29db5e44f4e5b08670a1552339c5a81e7ae540c6964964e58d870253ab5b05dac986c18b010cc0377c78692728f4095e4acc57d95b08caf318c6bea42f643dcc6348568ce1966b6fb6d122495782850120b7788cb85bc45af115a31d78c3881744831c3ef9a166298a5b3fc0fada51837b0714cca2ebc3605f1bd54161404e7caa0ae0cd4d501bb2640570fec0a015d1bd07583bb7210570decca415c31b02b3580a1b7b5bd88c06e6e4c5849a9177439e16bd525c21a35f122c7aa0da694728b351dc523154a44435c3ce8ad1f0674ede0aeec9a2abb8440f19e5e94e0f810186f82f1402ffb3918d706edf2131c9e78201ef8b94a7ca6443cfb363a2d0d358d13640f0549cb9f7859a7dbe18e84b295dd67438e89827f8c48eb5d6cac32340c619be19060634e7265528459d30021a2233aaf940f45c277ddea98adae59717fe175541465196fe6fde619cda4dbb69ce977004b38af1c9e2f396bcc578d6f791c26eb8a854b2abc2bed57be23662f360dd9f24de2d1b282ce856abf93f622c5f4af7a21d09203467cb60c7dee314ffc52a47f193435e3a103739a05271d306521aa7f00a5b8fc3a9a57970a757d84a586a8d244310535d55120b192e7e954373172bfec598244248814c68b920eae265128935278f6e452a17b0d33a37015ea768ebb153beef89d247d5d48c31d95f905f278bf553ee791009b7e7a6a624aa3370ec58197da1f3e114e4610bef31411c264259bbc7879307b031e6554caa472853a0758560b778d86d051d1040db8ff13868c782fa8a8cfca520738193dbf8559f6865100a06321921d7fc0670c632fda66ff5d619970240eba08286f099891047884c5ea13da3e6f29ab5de91ffa269d09db9947e7ddb49f1ada721b915c382356d7bc10bf5c21701028a471a71a91f2bf0bd66f36a6d27612629860b24f88cf1926517bde490cc2c58b7fb778d9239014180e927f29f2fc29933f6b1773c698ef1cc5d86e756257daeddcd9e7845fe8a1116b273c4889b83fd102522b760c0362f8c3b8dd127f81dd4d50ce37560c3840c6e41608688edfc78c3348abf0a45de823d4a80388bc491d6a99714af6ab074f309de9f172707aa0904cc8abd161e8eafb633a141aabf4cc67c4c9632ce6e3a70c3ca648aa0d199f68aa2c244fefa4540ce9d593afced69d670a93ca463f410c89f6bf20e38d3e6301f4c3040aa52fb85a309a59a503022bd5a09bbb51269988adcc54adc38436d062eaa2f12e466ce94c03bf9ec512407b98e56f2fa458d4da5187c3f0bd59e78f54666733ce60f41d49bb40c85f1e65b94007344d22b0212209d97bef2db79452262903dc0b6d0b480bae769d9f1da31058fa0d6f9c94052e3d7aa63b3e1a94089803b4a6e6364dbc2d85057206293f8f35ef9df4a3b2abb7dba8f20b695844fbfabb381f3f212c9032b494b0c753b79976ba422c03186e43e20889a497f19c73d43deadd04e9bfcee5b0bdb003469e73e9ed34879c4fef5cfb3c9f6e33b92c70fe4affed87cc4324f570f4df9c0f39af34c8782f871bfdc2eb1789a5a7bef9f5b9b94db7f436eb5ae6e204702c22ca21da79ea9d739ceca6e7f98643c66f5cd6808c4bbd1ad7694b07f52b3be772d81e177297fb7e0091f15bd6808cdf7ccb1cd85cc8fd18614c9fd3a31761687eb54ffa0d65e35c59b3c0c59aea1aa40d4615275970032a3634872f62229468d3a0f4f97833bd5b33777f34b77000f23da9d7190427456f01031cb6ce476fe10396acf84fc0a114fae82d78a84208128a29c17c2cea81d20b217d2c1232fa50a3a2c8073a5f249453e4839f36c2e8617c2c3282e79170010f123be420e10224ed92a14da2cd596bad5202517119a6c1a64a2811dda149a494b5ee4c1e58d3f7647af2548908831505cc82020e3bf6dd4f1aeca346f289252d19b5f0f82c8f9fc327cabb78fc16cf19625242caf11952797c168fbfe2f1531e1f7b7c94c73f2df9ce959862b09da09ed7a47c8e2654fe7afc2c7e05fa1c4e947c539cf2ed8e2ac22daac0e155eaf9abd4aeb06153dab5f44d9590b42b160929b52bb4b029ed0adbe797e40cb15df1274d7cc5a021a376fd340d7db5dfa4aca75dd115bf1c663e9ec39b04861c5ea4177278a3b890c30c082687d9d0b77278755e727861df9ea3c99276cd233e7e8b2f4793254d7c9af834f969d71cfaf8ac2f47939f26b126b128ed9a40f8f8ab2f874f94a4a429ed9a467c7c972f87cf149f259f25a276cd1f7cfc962f870f9191d193764da18faffa72f83c393a6a6ad7f4c1c767f972f83435c9699223d4aed9838fbff2e5f0111a1af2a1d2ae59c4c74f7d397ca8f830f930f94869d70cfaf8f8cbe123c547c947c9074abb260f3e3eeacbe103c507c907a94953bbe60e3efee9cbd1a42927c7c992764d1d7c7cefcbe16489131f273e4e84da35813e7ef7e57022e464c8c9509276cd1cbc93244e789cf04869d7c4c1c7b75f8e265294949a5069d724e2e3d32f47132a4d989a3039f969d78c7dfcfbe570f2e324e624d6644abbe60d3e7ef6e56832a5c95293259d764d1b7cfcedcbe144070643d2ae39c4c7af5f0e2748767680da357fde095050500e274ada3585f8f8f2cbe1a4e7a397c3494f4fa269d4aef85409251f5a98dce1b13ded8a2f3f164159f261a61369e85113df4f48691ab744441aaa347fda455944c0477c7b8c4a45dc29984a689bbe2f4b4af9dd2ca34793e62a4d19a549d33094d2118c2a7d5bd8b41606ab0413df2e3bca1051a65d073ff8ee22d8ff44183e3d441a171ee3a1ad410c1e5a213e7b6873f0ed5769d2b8c83666871878f8ce79063e2c29f05b1896026ff9fa759f8e354d7cb5d0b77f14c213431fb26072c06e619306c66d4c466092e9abbe7d356491aff2e5d4914d0dc6bcec9f8eb54f83303e95a9c2bc327550bbe255ba4adf5da794e9349821f9769bc1be2d5368614cdf16066b7b041cb60fec832ed696bc5f3ca1c789181f308704da03660102cd01e31b4825dc7d2c62520379056ef958c4440548e0958f4541344d2860978f454140810a4130d18324fab1e86787125c5aedb458c59aa9897184168090650838c20747d3652b9d316a66f4e0049aab072df03e16158105155293b3a6b26ecb6cb2a89d73cecccad820ab523bb3be75d3e69c53bba91d58735e39b39eb5d65ac339e79c73ce4993d92b2f9ed315a976e79c736a21ce177184c2a94e9361c86846061950a9ecbb7fa78e0da4f04ae6752c59f659292ff0df8fea99aac56566aff3746c208557dd712c29a54b060364950ca972958e6a092d6ae7e138d2806aad35a334649a4ddd54a599b5b5d6fa5de09b9fda0095526699944b98d0a2658a315619da7fdfd1fb94461c7036650fafd37b78a511072ce9d62d51a669aa77f61fb813cd92ac805f3e161511c587335309a7904077807110aa28c2065d037cfa585444cf9c52c4cee401ab3e160535cd2c60968f4541522002a73e1605215101e38f454146334345413cb587cb82832376f00f3b2b38206207d5034be1c08724272062180729d8f1683084a2c243d793748a420ab8253d1e1149f8b02d41ea88b8410f9a12c7c30b6ecf5644440e30c85e3458d2ee8c1556602fbe1dfee126b56006414f92009134846406b0d84e4f8c0718d400a92806db21889d2913ef4dd00e0faa50e443a5c82788221e28b59c98be3443ce9842eddadcda4a2badb45ea799c665de4d96dd6fdb8c80ed9625ec08ecd3a5d174199bd60832075602fb31f1be6623e0ad525a378fd239718431674419fdb3c75bce061ef4b3c1460d0d4ea7323669b8ec5bd63aede7bcda74b9048627caa7632fc718656ab699f7e3807cea9249e7f463e265984f1ed386dab18c80eb8733be85363873c072faf6f2081c9b73ca9ae9a72c300bbc3ca41e01552081d773ce395fd42d3e22c00e90b08325df5ea7b36610964af073ce39e7a4aa17befde221e8a0c937eba3e0185007b0ff7e7adf1955fc2084279c481103a42982f044135bb8e2042b70c1abbbdbc9b715dfdddd4de5aa3eec7e4e9830c6054539a0e2a7e3e92c2012fc74074a0265600421982005195c01083a884f5ce1b3821e1f9e2baa6832850e3c2be0e008df4eef6f4ba0d6b2667b481b05d44024d3370d31a6b70322997a075c2c009ede5581c34ed609a4cb4db970665583dd7dbfdad5daf59ac3e9aad6be3b65f0cc3156cf72d7a0aabacd754eeaa29336d8aa76356e2983745537bb8e9406c1557d7e36ec6fe198aacd00f444f3a5726996f9e8a311ae99b917d6c3613029a594dd63ceb993346f4e0b1096334ca5eb730ae1f661effc9c54e8e1670d92730a5146edc155e2c3ac870a3ffd7eb687c60a1165369fec0748b0ac0f7e7a4fcfcf3a54f4737a7881f85983e40c968928430fa20cbb840863fa84d21a0d7068a1fc74db732fc62c96b43d3f3dac5b883440a2cc929c010351062b4479c552c8f25158f1530a58ba475bb3207c28756cd023c2906ebf1bfa7597b42892e96587fe821b6c29b30a77543a95523e951ceb08dcaef3334e19a65733bd4d17d18e7a379953cf469fecad5a76b2e18ffb83fabc7788d387dab8cebb57de3e31c118e575dc9669f50e69d54f357bb684ae6f105c8c09e9d0b7ec07f4cb738e9ea4807bdce0c37399c3363a8a512a7d8c335105dfd131106790485a3a286d30b6519491797340fef4b9f75e198444ced04924925377123943134919dab34e226d9f98a03c7a11902f542e62e475ca7467caf04c1a1ae4ace6a13d13c868c6e6cff4219233cc26538808a39f05042c3d9c3ede8e57acf2387428a322b10d119625bc5cdc884a88d710d28b442bb4e0c3d2cb483ca20654a078b9e4217e6c2e73cb7abc0125a53cb96cf1d317f27894cf0fc8cc4fe75abe1b80f88a87518af4995b9eb7b0c757b9e7f7bd6c93f21bdad5e22b2b53bae72d8e5a41adb4c81629575a3e7923f5d168b00588976d86f01739edfec9b94dc66f1f8c8337599ee310a5dc1065c8db51989d6fe9e85cbacc3f58bcf3f85607d3951695afc83cbde5f3d152b97c554b956f0022736013c80d0d4a595ac938cb9373a7cf5b6add689adf7fb7b94143891c180e44ec8785a49e50be254c26e9f17185336e4869cdc468b01d072273ce68d70a1532e76f87231254c6f87c58a37d1889987c9412c41351210a4e90a8420c8270829710a4d05112850972b8e2c5e241102ff891c10ea438c10b07e808323881931c1835a10aaf302efd9ce0b3f71dbeddc6051f8b7e64f0edd707831f8b8480e2573e16097124b96eae1481a3f7294726f1232ba0348190d2c7a499d155224de30de520699774b96488f634f1a1d4d188b0850fe50e118ca4ec7929a58cbd6cda351186945e83f4338791c9595bc051de1065bc0c8f6d9033d88f4c78b2849c5718937e49f4a68f8eda01962e67882f9d075c8c096b8c3fc819eeb76f728621d40db0ccf7dedb3344cf0f228832ed522862058ed72bd2648732263cf4183c555dada9691a1749363b6cb82105393ea5a657ba299e51c1ce03e131a46c1bb41b81c31a6ae79c53a6a646073b2ba0028e53686003534c310515173502992377e69c9d67e3d429241cb202a9e7dc05c7255183201ba23cc54212349062838de1d89d96a1be72dae29ccae9cde1fd9c62df32d7282b53c5b6e54d09c69f0dce5b3538540d9ce7dc66a2dc66ee40c91da2abc5b387d6054f2d0f9f87d607f7d0cee0551e5a20de3679ea2e3e9d2136d7c15f2edcb1a366a4b7c32a168cc754db61150bc639b73766284775bff8dcddf310f95f0e3747659ee7fcc7e62b49a74859bbec73d506da0cab58301e9375361d399a44fe8c303820311b0aa5720e6fbee2be244e9deea03245d22e3c9f8043b0e55b39872987b9d5571341ce73f6b96a73f1c58800260070171f974d81392e7399f23448398e9b91f2b40be5ab9619cf791e7a8ef3ef8b9173ff62e45cf5c5c839003e28cff9ddb6d557d320e7311ae468f464a0c0217dce8bac88f2a1dde9796984a7564079f954c9731be7d5bbd9f24c835ca63ab2d2d9d1a29c3e2a87467ea2dced0e50bbb8ec3166477d9e671a4465aa835537d5792cb262ca879487a75d21d551d2d3ae70c692768534c9fbb4abe5e10d312b5335a879b63b4c5cf8ea13221f678a24890df3b67278b14b0e714b0e592ef28cde32d569574863df41d4592c8c83b61cb354d7a0e6590a37d839128dd22ecda9d3242a850651ef084505a6e7a206702c9a02fb9f246220f49b7349c4e0c96fce85e06f7369f3d878f388f2cdb76df3e8dd6c362d009ddb5581e3c7a601805fe7993400c8bdd28af00c1b1a2f78f46e60709b9e4972060c4594114574b9d3fb2cfc2c0f59cff21869b228b339cbfd9b11dc1c7fa86f73007c9bbbf836e73e946b31a01cbbfb5b41ca6ff207c7222a849e737f2eb512131313636388c163dcadbbe75052e758dc51284c03ca711e22df73c8f9e63f361456c2f16444edca4e3d336a1a174efdc5ed4e121eca83dd858c4f83d463befe69907a0cb4b2d01cd84079408d408740811093688ce280c630a6314c6398c6fc9dba639f396f1fbd0d6ecea35d2a1aa3310a4483503936b8b582c8e77c08fdcd83a07e430d915cdef28e06a9cfb770ab645b82bfee6990ba7fbd24eb78b42bc4ce43556bfc880dba0b914f81649cc0b1cf3d87f755391f0140e322002d8820f5b04e9a00640b837c0323003079ea3f04cd6077ecce66c39fa3dde613c549af0676192770188d7e08918ff30fec42e457a0414a6153863aceb0962e56ea9ebf56d220f5f91675eec301c8e703f6e5f052dfbc1af53dcb588139df9cc6da85a2e17d95bfb8788b539e95d395e34c5f7017be50be0b8fca76472bc29cbfe0a1dd497576c7ee4419495e50c2e2e15df110c32451a08f063548bdf3b89d49332fcee1c52f641a9ba9ae41cf4f1e52cb439411442e718e92346cce39f5746c6ea9db2476730e0f918f73a8b90beddb379dfbe2c37c216e517ff9c2aec5b7c3816cf585d4c5ee3cf5962f54a968eca9df983433284b0eef4a0e716a263df5f6ee74d6c4197327efe3b2efa30dda4fa5c2c996bea64105c752e14e4a29a52c929d529a0017605054e0e921cc5bdb0dd9af934aba5a6bed22102a0c9640a930613d3a5767add465a78f8aa458a14fb12229a2d045522475d6dded954ea1ef763ce79c5ff696815101a63011059b137ab4a384a1232a9c6c0c38e2870a5708a2882b98783961084faec0a28a0b928a909ab0528484254746141df93441937e778ee062cc12eeeeee225fa907220c99a39028a35f5a2964468ed1e04c834998f2a88f454990c17ffd1129d2209d3432470039e0ead646cd4ce68743166a6c441ad47724e911918aa288c5a228723e8c52663ca9e425071cc6219b183b5a67fb0be9b4088a294f1d7c1169a451488421dda6332b5a11d83c462a46d0b9387faf76efe4e15a112822a7107b6daeb910f91468495f05bd8aac825edc7741cc92890dd625889650f24b48f958b40414181f8b94a0e2712ad2cc04f1d860083de80009545eedf7de7b1de763bc454a44211ec599a9848b31327a3b7e07a97449b34f19f9ea8a80b1749b4883c364a7a987209648dac10a2fe937220d8e1142b8c24993104028b1e2556bad3d3bca97a15a6b5502e86badb5d65a65ad35b662942f39b5d6aa0492afb5d65a6bf5286bad5eabfba895d2aeb4d6eeee9984207ad02e2075d7eeaf35c6d439e7ac996d89cd39e793a49f73ce39e7b474524ae99c34b3aaa539e77ce2f373ce39e7cc2ca594d239e7a4954e3ae79c3473faf438930579944a2ac252aaa70841330a1592c46048be0e38223939392378f98349f5ce3929addfff71f25afde4a78972ec9daa3ccab93bdfceeeaed33c87f2bae73387d72b4eeca35783d3380f42b53ca4c56b6eadf470dc0f42bd033fae9ffcc7f5c9518eba75eefab5dfcd4134ea2b2caa54aaa565ba8ae50b8b8422f8e9a92fb4f979f2f89ce38f3be57c8c3050393ce5e95cadce799dce19f9ebd3eac42caabc5c9c04b0ec7504e7e5926d0e6f952cdeda881f24fbead3e757e3a5dbae03c1897910cd43223ff39016ef7938efcc41340fad8b5fcdad5f9f5f78e774b13988979998c0c58fac56ab95912338be5aad565c8c050845f0d65ddca65d562e2e2e2e2e2e2e2e2e2e2e2eac1c63015c327e024b0f79bc7559ad56abd56ab562ad56abd56ab55ab1f291951b71c9476294992e2e2e2e2e2e2eae43cdcb251f59659cd8dbbc79fc4efa9d34d2e3e6ddd6fd686fe1d07e3a90ec87ccc791a9823597553ebcbeb9f46e364b4356a54a952ce0d0727899b8787b4c60fe72c946e2d16b88160efad6efd33fc2f4d26f9223485e0ec94088f31ac8de3aceb787e3f4360b994f43e7e4e4e4dce936376088fc2cb770dca70e247b9b87ccaf9966d9d27989a31e63b6cc294eeca5c7ef3cc8f4eec7c9739058e599600fa577cec92fbfa09e80a347ef9483c8a779ca743be28b5066d9f170fa85f7e9ab720e627d7a57432f87b7bd1b1e3ac857ca53373c5499042a2f82f3523967bd1a416495a71e5b28f706a98d160ae5284a5df378f43f824c0f4f3e643e1310a83cfe75c94395498093f3224111235580f00415576c018b2a2f951fc179a9f211163fe5205f8813fb2eb7ae4fdfb23778b30ef033cfb291ca593e233267a86707a693c3085e2488555e38c35abc54d9042a3f62e4088ea752a9148b04b2ca6b88ea2a255ebaea3b62040610a46812042a8e4003a457ca71982c21072d08d183931120e1a552a95443d01c238c5095f113d804abd791222658bd8e1851a552a9542a954ab1a452a9542a95721d6a5ea97c2404443002063e80a20a3ec45e2cbee2478c1cf980d2113f55f8208420b0f04ab911553e825d47470756f352390c7ca95c07c67aa91c765f2ad74131e5e4b05e24c89a7474005f249859e4bc54ae43cd4b958fa45890405194d4c31150207ab5b37cb865240c8c48820aa028a20913bcda531fd6594287064f9aa2387ab1dc809bcee5bcf493147a4418b2ba4d57d7917424a4a42425298d3ea4304a779a4a33f552eb3438e7abe67a3465260e60ed4232854e403c9132429db5ce5a6bf459eb9c73ce7066e6db5148c0d1efe760adb5d65a6ba5b6564a69a0b1a047b7b5d65a6b6da552b214d02ed306e92907983a5d41ad55e626f4ac2d4ad45a8764adb556f9d55a6bad95de202c12a594524a29ed224e2808abc7a61e80ff51f36db0e3fb60b2f9cc87510aef38468a5cbf9b96757480f1d21cc6f2ba33f453852c9f79f46eaa8759d54094f2416648ddc6b8859f5ebf08bca4535a9988c0485c62e715738e4b0f515f7348552f652e129d3565541fafa084073ff02096745dfbe2ebeae8d0afeb3005bca46b1f4c01af5bc48629335dfa1035687e73911b4586a8e1bacc456e9e353f6f148ac5ed01ffb0d7adb5d686d676cf69ad9cb2670eadedaeb76f6b53497477370a093846afd189287bd8173d5e488df9fe80ab771354d1ab6f21c61863cc818b9ff9cbf4ea1e0d35ffa8be65b5d25ae9f4e961ad744e1b16f9da56083fb2a7cde1b63d37bb6ba320620a537c183dde1eb27c1bcca69d76ce9a79cc6c666dadd46809e50d5d97f1618dd5638df1e5f404b787d6d61861c40260edc72590a8fae48f5cb2040e553c774e97b019a3df9f8f7e97dc1ecd49d57ebe3f15ab4807420df634c771425a2371b791a60c754e7309a47d5288096b1e24854efec28bbfc0e2210bdfe7b81cbee4d8e00b2e788b1c5e8d2547cddbbbd1a8834893a67bdca64780639480575209060f675e3c045b78e8979b81218733e04b0e416f91435f71560e592f646f19ea4286c9b0560e59cff2cdee9934f4e690e54340c1d26bdf6cba99541aa4b149d33d53863a8b75354dd334adc8d3eea1ddd33ddd036355893f9726951bcee3a4d2c414a314c6aa92058e455124f9b07b62394f9d73ad7b96b4fcc41ac8c5a9b7500fcda32943fd65b26ad09cead09cb934a94c269cf79c7ab1416f1ebde479d422cf23569e47b87b220cea282bf02a87118ac7bc88d1aeb8720ac2df4f174ef5b17cb741edae7c21eb531f4651d8fcb05d76702887421ac3113de1b28df85090da2593e88bba14e286a064ca87ace7244f266574498f60da004e82b82ba43f2608b87aa51ec606a35f1f1fb66a77cd0d84f5d6bb57f59b4320accf9c3ac1b1bf968dded6eb57cf22d3d38fb9d5441a9c3ce827903e367d2c72e2e839d4f51873bb1d2db85f68fd3a57bfea8393420d4697443662d3ee1ab24cca9aa3f51cab5f577da11c8a992e2c90413336d13d66fb58acd8a3c1e9493e7a8060cd8d18345eccccb0c97a90f4f4abe31660da8e728e76403e8b0abeeef9c981749f4979fa6ed651fffafdaedfcbfae4132221e090555d131a923e358730315618ad9c13e40cf2afc77b93b67661150bc6634e5ff44df36cdb9cfb429ce7502894db888fca2dec3f324779e6d8bbbfee7da190f73cc779afdbb66edb72b47e6f563b698b6083241b2c3d2a6a3182b1c17be517e13c666b6f23e07af56283f8ce9bdb7160df378d83e42b97d4ddb9b5f98f76fc1b0e5cd484298ff3b67e9bb59947efde0f48f759e69c2065a02f75a48c214518526bda9343841bece01d7007e21d1cbd48cb7a7c5d9f996930bee2940111d01e3da7b55aaf5996f38c182e3c78a65df66d749817edaad35560b56930fa107ac0f363d1112ca689267a607d2c3a32a20918cca08a9a38c14b3945ba0a091fc2c02cf133869f4aa8e2a7b39640f4d31de9053f1d449a62866615f4ba4f70dc044d0a25377648268e402f66028b2a7c302f08c5e89b76950541507c62caa78d4c3bef26dec4971c396d403dca9843e3120ec738d48414906ce10947988003093c11a5084c20f184931ac4a224347df4c8c2d98873768843ea2ee05eb0c0d31b7cc9229eb89726bcc2d196a15485733cfaa2051cd21570d1025362079a503246d93e240338df3ac06fef0cbac4b55d9e85d1c7a22c827e001f8ba6b0f3b7cacffbc7a22a489e9b2e001d6c2737bb168e8eb4669953af0e88dfddb1dd6670a2808b31594cf9d4c7a22c90fe5e0fade6f58b1f2a0b1c5bd5c1cfdcd60c3618e38c5cfdb22cacedb2653d5b079f7ef496cd3a40adbb0616b87aacfdd5876400e7e34b0f87f51f36b7ac570b7ecde01402d8e0ac6990c6784a63e02168b4cb86dfd8d1ae968ce15143dfd0ae76a0fe621aa4dc4b0e382646d2ee8e9436c1c518638c20e0a20f3a549448f96ceb3c1c324fbb4ba95fc7d7bbb93336f35a83e050bd0650cf5838a867e057af19a6c129ffde4ce37a35fa33ffd1366dd3786427a384c35e12f6922823f3a9f98fead7c3fe0982fa3a3d1aaed3eb54fb9c7addf1f1f886c8afb986061bc9a97b45e7ba29bba6a710e76f34385dc60a4c9d46bb5e34388d7aa29a70d8dafc7a6bcb3a40eb46e047b1146ec7aed486a8596687ee0a7694cd492cd1de4e4619b2c53b5f396bd5453943124a765ec6c7247a80f22ebe72ae45ddc6095c3fedfcc9bb93774adec5bb961677b92e9300a2e70aa28f47f8588485a5e7629c0b404c0c31842efeb9bb10b35ab5388c77d3d2c24396b7b4b05a2d2d0efe8bb35a5e5aac5a5abcb05afef2e2ac2f6cbdb4f8e4b73c1a5a5a5a582dacd56ab562b5f06858390bc6abd1b26ac9a10b003a292305a31efb8b8eb6ab8bad9717d66a65af8c48545e8d7e568b5bafa9e1408b7739c4f9b0d5b9388b77e3925b5dd6017ee71d27bb2883e5e1ca5d9ceb3ecd5dbe95cb5c01e3afe5e2d8a357a3e5e2e0b7f0556eb9641de0b7607d42e8b7d444182db915447e8b0fa1efe24150efe25de72e9fb74e832ebeca43bc1a2dd925374ca6b3439386c5c2d8cb3c9539ae020e2994cf320fa90f479d70f999a73c1aa85b28ed9a48344a94113f73ece9a06e5de543e4ab72a8f216a74fda1576f4e8e49953a376594f870422e7bd065c3fb9c5d1f9c9a3674fee33334e864e7e7c1927e0b0777cb2ec4f2ea384b1f79276fde8dc25ca6079e6ed4394d162e59973ddf7e22dbe20a8f716befac297dc22b33ef9288f0617ef9288019277f1ceb9ee5a7cf15961e7bda3c365e5f275df10f9abdc79236957d8e28dc400dc0164e8649411fd6a2e18f5c93841777e344f8359276930f396ded1e171f9e1e22d9e39aac54f1e0e97165727a38c1a3f1a4983990b91dfb0063397fec9383165a2c832ef2599b792768529e73cf3ee69d78f944bcffce4e9b83864e6663dd929ddf5ebe2ad0b2d6fdd8b343269ca646ebb28e3e4d666015ba7f625bb7ebb76757e9fe55bf9427ae433c7d7a6a76748680a5cf099a332eac367999f3eaa136164d9cb4ab8dc4a1aac49df4abe36985469d267beb9d6cab20ef063bcf4188f5fe22ccbeecd31dbccec5abb9231394b4f3fe7e9d73b0d1abd18f2e9228df59932b0498dba259f4923955aa6fdf6c5392c9fa40f2f351ac12379991b49157034d29122a9101d6a1775a16fa73c1d63e740e9a40a34581da5056c33db1dbbd6aefb748690f2e8095116b02462c2d14349c4235dee849dcc014b4fd260953cedf4933cf7933b381f3f943b3bed4ae27937d7a90d67bc8df1f6451863edcea457f36eecf7a4e92b112889583c92087505b6117d48283808a9324742a981c68e7ba3babb2c924a7c65e2ab5fe9c4574944d439a722c48e76dca84e5c29be56e1eb15be4220cad41a228c5a6b751bf161ed42128976925031aab7c1ec8b5fad95951d8932d553f0d5b5eac3d72875228ceafdb125b81853eb8976313eb6100f44199d479f51d3ae98301a355218956c3e1d94ebe02f541ee274726f9efa74ca5d96655996d9747b5ebe0dba1061f4e57278318be53974102996d3a06c6aa00e62612cc475db303303828dd4488dd4d80672a8652a876d80abdbae85daa50105ad4481fd45003af8877420335abe9ee51fd72d50d3dccc0a1cbfe60e6a705aab65ab63f3ccb90e85c8ffd1e08f5a4fb7c19a43e99a5f29447ecd2dd4400dce2a969870757bbbe8d56a33efa67a0bb5ab851a9cd9965b9affc802c801b7b68ca3baf4ea12e79b4a834c587ae640edba3e9b4a83424dd34c3535333320e80e04f4b997ae9859c78a98f08121d949d2aee9cdd3edd3ae56ed9e9fb37f2453839369d2f4d24b0e58324da9e4920a4b4e9935c69de9f7eebcae072a1065a44b9993a68676a17f8c34f405a485bca899345dc51cc1cf69829fb25156e0e9311386894a39a7a44d2885b7e3baedc4e97cd8d9af818830a4e34f2e913e0d4a5ff9e451835064142539452e354845f650ad6b50a5d1a0e550f27c96659206a9dc519a342aaafa62cbd8cc8532054f3d942a784b79d08c0a6c352d1f4d24da3a4819a8bff49108833a8a0a0e1b16ca1d9e250d0b1b565d228932b81444192a90336c3498a6c5a9d3b8d13c6a6897d7f98ad3148b83dec4a282a750ed4600394c67015bbe292f616c41bc74ed0b658ccaf3681a2a75e453c733a03fa5a15cf20dc4cb9e06a5cf9ed30da088f2d4fd6733b15ee38c33fad33c5f3438b780c338a5083fa713998e838e2883c60c25e0f67046ce3441ad421ac4a1c13a535fb4c801b770887fab3cc281c794a95e6b787968b9facd2c6b057e418b504bd9d2532ef55c94d172c1e9d7e75cc699f66e60e02ee7b143f06f58ab1c82d82f6b53da563e4ea6a71ebe44195cd2eca09df006c0431cb2029077a6cc0e8bfbe4db9d9d18616cb45d5ce402d07217ce5b3c009f8c3201b28594274d841f25529044d1243dc67c97366e3ce7ccb280e7fcaed75dadd3d932f35e8c592cf79e892daf16617c97091b71775e88b577e3cec56e8c15cb2bdb452ee6331213f30db1721b6b90ae7c738e5badb8956f1f277d6870d1e8431b933f3120a1a7edaa31fac99f2c8624611226916412d62e942522d24c983d4245394efec0268d8ce590857de6ce53e91305ee3cd39cc60d1a1965564e35a114d1d316bff2d03ee988f8a0d8a476b5df22f0114f3f8bf434b451a8671e07707cbf729567de4de6ab0fe4be0de7f87da1fcd5cab9d975b2bb9573ab2fc8c62c1086026b7e6dcfd315a7fae2ab3ef9b6674e59a262d4347888286968680a8fcf9c3346c913238d84b9f018b73d93c6454c6c7a37ee12264f2051105d5f8c8bcf454c781dfb09853f17dffc3e9739786888c8e8fb88c4fa933945b05dc2d99e4c4aa614030aeac1fe7dede2eba696a1cea18686888cb0fc49c5a48ff489c169629c3a456a575325fa8446a152fe884279ea315099333444644424d632a7c884090db56b23923932c7b9542c0614f4110d310de55447d4e04a478de850cb508f439808bbe09e9fb6238bd42e179cb63f793a145aa2580c288849e614913f34168b01057d1ec3d753965a667e72480ac9a09896635f963e43b6c7f6d81e18fc85176230786863b1185050bb60108a31461a09f38f1219c5268d84c162b0180c990ec5246ca888060a9a33d6ae060a9ad7850fe68b8592281693b0586c084b18914913a363f8008203b6a1b1d118353330c6d85b00406c5a6cd7ed6ed6ba7df4a54733cd33cd6e0d901e0e9b969a474bb7acdbeac7846baf5e8d76cdb7f656bdf2e2256e078f28834764a91c068c9979511363e8a3116e998e9416a159483b8f1e38a81a0cf1b766a7edc27b3dc4f36b5dd75c4583cdadebf5a7e7d9d3a7964315fe1ab2eae3cf5e81c58a2c161478649c4bf209d29c89c19244bafdb2a2741d70599679ce5104643e3dce3a10d6739cb56ed354721ce736e273b9d5f90fcea7679fa6b9b5dd17b3edbb1e3d0de7b56ba9dbcc4a7530b21e63381df5ba4df358f1c801995b3aea671c11da02bc39e7b657bc3ab7f2e9acb80efe5ac94374cf0ebbacd56e3d0a2127f6b1a8ca949ff9585445e83397ad2cfb0b06e6c5ac69574d7df2655bbf524a4f3990eeafdfd891caf7d6c43458dd7e30355f2594af2ea14499f1b5c624d0db2cb331328f27b76939f4622646cd0c1bcbc9cf935ee7ed7e925476a7bc7329e5951fce5f6c65ec35b7f6760d56d7b2ad6af14583d56334585fdcf0516bb881478f6a933c20242979555152926fc7ac952778c664dba677e4b28f4d49c9c71438da56c518715ef320d96b1d3b76f4961e3fe931768c339d64e5761ce9fc28f5ec55ac982161efdda8d5c16164623982439611608ee36c8c12985a29734f4c667289cf4f9090b54d99dba8f51194def95aa791108165457424129a7110b8e965ce0b8b8a943b836b58434715883452f63831f408f858e4040a9e93319ef4ccfe6919ea442ca1a714e82952cb179c043355286f731f1575f6928b31fa58e484cec725708da94b393939257855aa13c178e3440922183f9c3fa77f31b5552023d2ca1370d861556699703e3369b8ec0ae03c731b1f2d235d2addf898028711c9864c3a47135f92c85fcfbe2dcbeed558419c94525ecfbeeb3eaa80c3eeb32ccb5e482dcb315bce42fa32f280b7b63ee0ac4b89f2a974523bae01ae01268a30a4b3ac80b3240da5913a4abb3697520a4d9491aecdcc482469e27cc051125df2d4dbe7a96fdffd79da4a61f63787528a979e25f1205f3456d12979f00f587e28633d27f5592b95924ad849545e7a272dcd9ca6764d29a9cc9e43404c8f31d185053864b500677e1dd57ef2a9a150a8e83c6ab0215aab9d3479e3a975ca3aea9ffc9489ccd99d58edd2bcadf7e66d61ba8de3baaeabb5ba8df8b5baad6e51aecd89fa22f53c7a737a9c53ae03db15b950f25069a9c5d9a7ea69c364d57ced1733944eed0a3e1dbeb1c3da1b888ca01cfdc4848228ed1b7c08c1214a0f2ce0248b3bd9a23e3db32d9a75d427d2e0cc7352596b9535c8c8677635d8d060b7272fb7f9ad35f3ea35dfed9bde8da6759601617dcdde63d2742d533fda32b5557d7a763d6bd5498455050e635205d2bdf41eb60269b0fd063a054fbfc1519fa13d3bb620e9b2c00eab603c2629d584a3ee0385ce4b263e1625fdfc9d1ca553ceaed6c448c2d3e012d9438b2091b079830f2138f40e1e15b0a1460d0d9bfa33cb1fd615d8a6bdaec1d99a4e7dd2963541463e5ceda4273dca04156384cca10f20ac53e652c887da8d36435b46b6a44f3f6539c441f29967d3bb6151c1d6e5cca489b154d560744dbbe19c524aa957ead5afe3fcfcc24ec894b29b191314cac1a641797b4cbbeecd79fa4c4b6f15cdb4d658bde5d987bb191bb0a3c1e8f4931186d72925208902e56351139a3e94c1bbc13d387ed83ab72955c5d41f70c4e2763845ed17661edd5a6a53dea35dd86b8a13800ef6ecb381abfc1ada1581b01ee756967976afcdf1d2cc1b4ce59afaeea76a10c7d01d60cd0dee7eed9a20f862264637f86266864d8da42c1856b594fab014acb91183c68b991936747b42a5ddc9fb6e5701bc1263c570cc465abc03968e7770f53cbeb0cf4cd9398d80eade47c41b949293dd9eb7f5aa646ab0f2e8e17d3bdad57da3061bbaa50d914963633d8fe621b0c6a12d873787b8dde6069176c5df41c3263669624853a6fabd2cafeb52de8daac1c8e231259198a4944c9249ca5c244524261403ea5877de8dec6930c6d8b1e4edeeee9eef31b1983aa761b1450e0f8f121eceeb4ede8d97a592259fec69b0121165aad75a2b11be5697d5b9163918b35a8c403221b5773747e77af546523daf95c894a97e6f6579b4bbd9d33bcd934a228ce944264dc52ca62f9459f82a99228cea282b7018a17ce7d369547ff9360e79776d336958bcde15962f4699f6952fe66009b4ea3cd66075c9d43a4dd56d3e1d8cf3109ee7f6e4799e976fc41cdee25f4ffbe1bc9bcdef4d3add8bd234ef78384ee3384dfbed5ef7fc5aeb1c672dd5bc3fcd6d66c7f08d5102ee1eae657cc0c518aea5c4e22393583df368b0df5ebdbedd8b0074ac676d6d56a54a95a6cf3293ebf16623dd6b08af06759be556addf93524a339e55e09ffb3e703787403acef6607be79ba793344fc3d8c631c0d5bfd6f4ea767a7deadd9a59477d9abb577d6318d91d23fec431c074ce49b3147a49d41f8d021763f00e4b600edfebb776d7737aa29852cfc321ce9f3ae53f6eca39af1ffe525fbcdde18a3f9caf28cfad77f276452f94d6b3ecde7c1b9499c7d78a6fdf5df96e236007c74a85dc9bc31996629fc92c7515cbd638746a5bc5c2d4c419b1b3acb7deb9746b61da45ddbabff56edbaef671b3cbb42ccbb2cca79f3adf3eef0b25e7d1eb70bee3aecbebdb759ba97d99cd730551e2111f8b9ac0f34d807d2c6242ecc30570281bf125cfc7e80d25ca9050a4cb0e78070f1e11c674d901872c3a135b3aea530f65d3c74f4618a15422efc7a728cf3d8a9ad2f33a07c27aaf3b6559e67954f3d97d616cfaceed173d0ee7b97af23e7977729b9969367d3dcff372ebe43f3cb77ef2dcbae71647484c3a7db1fb709efb8074bfe5fec2bbe4ab6b5f88d3f3d56de6955b57262ec684f4e8a97b94513d46a49d4913693879eec598c5720fc12a83becad54f02352885688a5c18b00babc687428f4d47ad11e0b07ba83766f9ccab582fde4c8de492e3110c39043ff4687473e82b398c432f396421b1587238a30239240d2953a197432c39a4658a04438221c16aadb5d65e828434699a24b5d252f760016b9244143655990251a1068390264d0c1f520cb9671065a8e726f723c27311df3d58c09a906047485192a428f594a5a6d2ae8da969e6b40b069b50e651bbbaf07ed376eeb3113f73d55763d42a818402aacfa8742d3685492b999a010000005314002028140c078422b15038265484553e14800c92a64e725219a95910a31432c61842882100000000044064660201903e38eb3de841ee805adc29813239aff108b46aea00ee5368f5c454e1dbb9f50d57bcacfb0cea2a528930e4ed788a6b787fc9a547d295c13fa05fde8209b1d1b4b31b18c0d2f84e0074d5620afc02960a45834b953129461ec535b9b1ff227ea5b1b1bb0fc451fb139e3d3d7cf9fcc46621af7bcae73b2319fd2bfa5d579c55c560b119920c73ec05e6835f382d47a515a0394627efca95a3a41737868418ae7928a3075bb2fe2c3085ab012453ac01db100ca4d7c8479a082167611a8be607032d8e5d117cbd49a36d1775029e846a01ffa7b7658d360ae4fe2abd2224c09ef5e4da55b4cec642452c8524cf173a9534da093118d68ddc9333db22947833063262a5ed319885111adb9162f426de0ce1f5fef2f23e641991450a0c124102f647988cc78b6105367df783e8c18a5b2bd388dc6dcb96ae186344dd9846bf27d2ef388449c5bbdd88d4e22df253bc5dd8ee7668ccdd1eb90185799ef1f7740e709f224d1857d3d439b181bd49ad035a53a2b3b0e47e95bbd6a191ef2f02865bf808df98a664379f71e3c86eb00025952c91c0b6ac075ff33aad85955ea153f5bc27393c92b17bd315af0b98953d6578109979f36d8c2d1722902b6fbcd6a4a8c6206c1840278c3428f582a622db744a8a1cc590c062f9dbdf440be6240dfccfa2521539a5eb247e5cfe52b864755b016c4ee18bfc8b270dd5cefe90e627f775d64391800cee56576087f178bbf7202ce7d4dbc043c19d2b48b40e1cf07f078620170383aa926ce3f20025e035e8eab2937d6c7904db7b318654666cd318db12d5ff2ad4a7ab686d200eccd16804ad693a20e261966c463c0976c5ae388b48f38678aea05981ae55fdef9bb285ca531f962d8615e67f17ac558a2941a0f65d44575c2abcd72cfdd87151e1590d20d550492a6936104385b72c5f89120e414899551d3258a3c207a6c25a4ee824e15390efb7891f93137dc1121b3e888813f6f4f0b845c4ab4e5e11ee44d2396382101c4483bd9427d76579ae292753cb6220fc66ba96f25c4af9c33927d71056c214a98e4cab3641bea0245629bf827f8dd66c00d51df8ce5b8efbbeb9c46be5816462d20206f32d6724d5d2cd7fd5a142abe5e8d92bcf34df8b3616f3d6f21d40c4787a0837841e8412b6f300332cc9ccbff8f6833e9af4030e7ace7cfc0f2f78892aa31432c0fda978e80ebd95cf2c1dd492526f8b892b39bb70e8d76ed745c660dce61f0fe6513b1496111ca7d9a026293e724083133e1fcbca73bdfb96cfccb32028a01cd60c039638e1ddca8d9097ccd9f3a39c3c81513958f47648acfcbdb14e1c29ddeb727101e17c26cca1980e79595fa783e2cb7acd20a3acffcdc755fc5e16cac8fa2d780f581301f0933c26d4651ac6269920ebd9b4af47437fe14ef793b1fe33a216a4299cd92d70ef5b52707be2b0617d1bc2fa634dfbf489ec23307dfd29b0b0818a810e7255f6f05a58c3ed06780b2f55525f9fa32facb71e8fc95792bbe9f5d78dbfb04e864ef432a7050d8c896ab095a73a27957e85f559ef02516a234d68e6571b36bfdc3d6fa87c70259c4c1ca33af5b637342cebabd875af18040555fd6dc4f7fad8f5e6d6c5a938b94f69d17c87f76b2c9ca4140f314aecd305a28f5b599ada70a21e4b49028075b2040fe9045a15267dbacb7bf50fdffa88351f41172ec77546a6f74d9f4e3e3f8a45d959d04dd9c69004d298aa3de3e5c9544883b16560b28649b2639a1fd96ed15f5e4a5c05f57c5e8a912ace8410673565071e78d5ec2cc4ad536bf3022c4b7927d134c412d18385adebe470c510a352a18d94580ed352d0c2c4f854aa124dcded2f3db54e520e1fd49bd2bc063b05e5e0a48b7235f75ebb6dfc72da5bc115127e3c28e64b7bbdcd95c9dfa72dc6c7c7d253b43f6ef4b1ef3345486c545c5eb6d66f7f81a9d302a5f5f2730b40442f9d943b1138b11757fbb0bf3836cefa40446c5d2437f0040d59cb562949544de36150264e720a9f41b8b795ed73855d61b095c08b3d4194f62879def1961c56b551457c921d693edbe7c373fb39dd03ed54874dcb76a9460056aa0a9410eaabb8e8c79114c106a71b5bee9a6b817cb4f2384609e740a3a1640443a228533f0d28740ae804a4742d97eafd3787e28d1febeac80ac7e84e94b99645a1017a2793d1cc1315991eea22a85bb90e9655549a3e071505acde8f134da212f6032bbdf83a948ddb65869943824b3d7b8ad3d97dd09e1fe67db27632a0b55709335dc225ef195e42689cd145afc89c438ff4f891a421846edaf3264544e2ce14d98be57f08318c7cdc85182118b7262ec99de23f427ae18439596f182cc71602acf8613790191ff8ae078548cc08e38930d238a5b8631047b93bb21f5ccc405a5f32ec76ca8a68b808b1baae45c2ef8757af4111178219b7da8640ead7e71a910af8ce4d154d0092c56180600d202c9bdea24b63fe169c8687b6e4c453ca7ad28cd268d4bba08f946c69b197ae7b34c082bce29ab20091aabdbdfd12b480186cfbf3ae13f1a6914dba65890e221471a8baeccad6aaa1b52800925cc2ba9f43e0792c6c0805838a679eb1281ead0cc88bca21e426f912196dd3640210e1ba90a4e139c26d1df367aba3f7cdc9125ae2c98835df7a5b1a9338fcd281b79f23b846acce9b14bfd864b23bd08a8ebac00b0e98875812c564ed89b9274597bc248cb9622c7f1d060f3e7ea482866bf09c00538e2c2f99fbf15a272e9994e48b39bc0641943ea055b823c30ede930782df48b514d36f7cf13373dedf114aa6e26169d255831e9c1886a73f3a9318fbd60669138ae6991ea7e9e5ee92352de042ec0929b3670a3cf9d8b0e87014f1065c3d5ae2c7a85b86c0e4d1018ad1d30e2a69c7b6fe22ec02d63103e6080efe139d827da87d504c1031f9e6794744e7124a99dd69ce2c524f9c5210baf1de46d238130d9d6fa0c5dd2a050566489dfabcf8e7a31f49c7122ede3eff0d22ef77e5ebab1fa73440300f5a5d2776bbeee6b6556c72c62da6ad3db5bbe97ea1591a13aad729da07506e8c7b3ac47d6bfc22aa650c019641887bd26be4352bcd163b9489ec5c2afa56f4fd5d47a8957375de08a776aa55aaea0149de260e3d3f758679169f7a1660345fa4011fd8a8a224219fba8702a1a36b9cfbd49af5b0cdb1797f4a51c97f25854c75cada1a46bd7dea0f904c9af2735dd6c89067ac688c39a8309a304949b44530ec7080004c4a1b79f5fac1c5e1a86291d578e38cc7e0d21afda0d385693baa44d32cecd50ce34b5b45ece47220b55c577071c037c1d477922208cd3bb51e99eb02f3f1c9106135a670db491b17431ac0425b243a9491e24d2cf1f280e9319d37fa3ac59e56980bf8824f6866b15fee8c79d9eff338d83f0ca930b75e2612728b937dd1fdc54c769c27db36fe13b2b75ce034546b3a4d15c4effb0f057920f801836af97181d27ff46cea3cf6d4a79e9b886ba37240bed7a2b6cedddbc46c55f40e2813848f9d6307f5a5dc06d91a901211492fd76836e620caad77d188aa030a1a48a6509444c9a009d3bee5a2c8f487149890deb894a3250cba83e712afd090b22b0e4276a00c08a98a65563339c8ecd4fe49267bfbbe23a6f3acd030a7a7676f429f07525c0dc4ba6cf709175215b3754831d8a010c78821239023c8532429b047472a35c468f70026a932e77bf51f44034cc4f02da70966be349ea8ac74d142aa9c3e871b7f4a5b20a528c071461e747afeb39b51ad8945f7249e3955065dd1cec8f7082d53d17988abf76522b2d32d121757cc4a72ef8b7eaf417b363d911163705ace41e79dca7f3be205ff86346a95431ec4e708188443e8edb3a2549f6ce625856cfa995da85c7f45e8c8d9c12ded98e76f9c3a0681b8fae6256e2b2013779fcd9b97e2a785cdc1269f47a19f5621b16c69f5091b77c3e236edd46ad3178840c2202dea1accceb9d0af46cc8bbaa05079c15213f8417cc779c130ec11f98d2f2b8d32c1638462dcbe498b75da2cd627a04048c5e14756b2680dca2d76e7865d0b774e6623f4a654a6b369260ad8b72ddf1398dbb6e6c3f2cf841342d933bf2e98e4982e05293ba5e78c0e5e49940433cab9394e3a44f9230113e8d5cdf23262b6c5a10675ac26ab014301e20e990b618df6d0a04da623036e1da8c0ee677a5b2b904d04bfaa51f8950b7240d2d017712d651a69318880200dbb3822036d4f84c574965a91ab2a64a23aa22de87e8fec5b29b7fe22dad4207fd1c1a80feb55e078204e1209035aa2fa8fdd28d7f33423019654cf338594f746ada6d3533f82535c204d6e95954e870da628dd7146d029cb8473211e40fdb62d4f3fd824431be5440227c2f7cb0e015e2e10d16cd9b8eb6f23025fae1d71f25b70cb36a4d2c64192646e3152c85ec2a08cf62c6b7aee33cbde6dff633605c960347574e73776396d88bc84855253776d802812d21c669f977cd93430dd4640aee7e7d6f85536a7fb478ad3b23c42d7c5496cfcdf02f8f7515aa07204cb92e86ad02468fb3878826336b3064725aed1cf71209098b05e22c418661c1479ca243d2fc672c70b943af5012d709d60ab9310b9a4630ae8dce9c019071cc38957d82b40f8f89eaf87ae8ebfdde3c89a43d408a06706da48020680a02e792117e7df646b261a987b98c9a6f05403b33d6390728d84398abf925e8ca780a88a88cbd02dd3ec3be5d522b488a6b7cb7331667202e6b8fff75fc81024dce4a8a116e29a2570ce80897e28a3adeacb85821406a0f7928b2dc10704a6e021c0676aed92d89cdc065b4bb60146cdba5a44a769c44aa1ec3d87f6b961580f40e14cce69664b9612227b983210df05daa481d22138b7401bdca5adaa610de5597ccbf1d35443bd225b30913568abf490d455b34ed66d11ea0f1ac73787a45b206f61064297841b4b9c562703773051fdf1805c20acc9278b95a0cbd8680462a1bf762fc99995f17856d90bc83448c5270c687575abf8eecd83d25c3e1d1e9fb7802b660973561cf65c93c39314345db02cd9075239dbe6076c3056e640dc3a41f1c6d96d65deb9895a237687f54820d52b679c792c148cf03c2900f75c05a996b5b8487b9e95e02134bd37ad339b3f71fd5fadd0371d24932287e964e366c46219edd54a4f1df60436f51d5cd824325a2c4e95cf46681c4f496ba8c1ee8b8dfcf658cf34bea02e9c83d9edcaac44bda1ae62ad07d72a36a7b9864e8d0cf92c72803a2aa8207dbcbcb8f7a6e54a2f2d3e7d41e6ecbd90ed565c764e5d9d1695372906751e4ad1923f762cfe0ea5aa4b2c638517a3779efa0285e35700290c98b11c253b6ae85de98f19a074e62ccb1d58b95f7981a2d7dad2ac739c12c5bd2ee9f2dd8047d1996c0e95e7291713bc7d11f9dc20df43896b8ba7ba8a6adbf37735db4273ab40ecebe0d46cb6a457bb82a5c26477d4c86887593cb7578f2d27c1550faaae10bdf206446b1f0566e3b1ca9ffc06e7b7218efbbb15d784afa25ea8e634bc6835209d71735b778da5592e1770be931e5c5ac4ee7e72f90f65573019c48212a10a97eb7fc398d616a4f898c0e6e6c860d95d8cc86cd24d860b847e11602d1d04738c7961a85d34b5c93ec0fb2cff5dd9e8a188a40a6748659cd4c17dcd5a54c92b23677afc295be85d604cfeb4dbd392689e2ec8424e50f0177a1f62f00e42147a78f0a1571eeac31817fd6e5471e04c0fa9973a335d82aa45926c4d4f8c5481fed060312735b7b3aa00e32340453fa57c69c77c175203e836814f9234470ce570d0efff966ba21e5372d694c5d89e7dcc0b4ae6633dc3eb1f57f414b862ba4900dcc6170aae1d38c447f51882c0263db00d5579fb0c0814380a15f403b90b98c5ee30e2bfa896be45fb5d01c5efcc42bd89ab7622492338936a281970bb1d4740a7f069e24c11d6ff089becb221a2ff6c904bc289a3ae5dc438085259ddfd35a4f20c260db05f561c50e2fa3384e6261c0e45ed7c5219c585facdc3cd3028503ee85d13761a8ec9faa64180e530cc989833a016d0b786633ef29647212894d4979a9c5b736bf57c0e8105806debf20a31856463d310af5f3f349bbd3efab0647d386b5592793f62985d231b33ed5008b480bf7588629ffbb57edc3ffd1cd8480d7672993bc9d0316235a6a8d20a67f433ea9bb771b6a2d1980f77f465bf30887f00954a564e580c0e6b9cfd70737d680c186e57487aedc35f72b5bc00c97fb491efcdf5c17fc16cc33e9f4d38dee7d26342f51d33702b00903cfc4932b19f8d2884af85b3e58fd298ea59094a90a31d9cdff14b298e6434638aff029def133714e0bd4e2766669dd4274abf3826c50c9fd4ffb6936d4248a19af89ef2056ea7d01514d16c7ea0537c996e269e79548ae0dab245f11025a8d1069096915f966614bbb796b23d715c04da9160ba217d19e45c729443a6dc330a05e32878cdc000ff6a82ebcca021084c56069f9abf2ad7861455091ed0ce27d47c6599b3bdc992124e57299a4fcba04f3cbe6474b6c132bffc8d33238147a820b638ec08ceb4daae08b81ac302b3ea0ab8a202debbe46e0a9857243041d95df293e209666acf00d20cacae1cca2cd8e96045d98a3703d0cee8cc3433c87f5970f20376fb8620db21b76fbeae189570bd5c34e1d8da8848d177451033e324a8909e5de1950cef2b9d18030b57229891caf3517a45fdccaf43ccf025f724054e34045e90b7066fe24de80cde5fb77fa179a020394575b325274d6d55dc79043ec1656111eba75dc6029be9bcb6d3148115b72999077bf02e13fb4bd7ecf20cc97107cae742582337f35fdb74d61efd0bd1e5d82b69f2b274a027fc2ddda40914314b0ae803ba5270e6d034089a1ee2f80ba5dcd7d0c8049a5789eaef22d563be404499daaf98a721683ec92aa377c01951632c7f491a0aa941e4df20f92c01e6917e3279cc276ec03438fa0be22fe3383258202c751fd2643d55c106b3b06c8a3725d0ac955ea4b223e05fab0bbb51d9d5910bd5248c476736860d11114ea2acf3258a4ac39d3e548ad58d41cc0c237ef6d5524d5b728513d29fae1b79f46c2ac8f7ee0816e18386ca748fc032e8f6f006f0138004bfc07897a6325903b6497e48cfcb360ea02fd3ad32f00655812d5bf20051e8954cba1ce1086bb450880a4193aa2e53b296fe4699269c19df1d255d7fc99b0cdac5820bc5fda4214c44c062493047debf217ea25811ad9b5de12274c85e7c4900594bd82f49f0ddddc4e79abe68047e5cb13f286b3e8086208de8335c785e3d9c20194275f88258a7c202676d6c89f1e3241eedec0df147a9bcea1d2c39d1a376b3e79462bc59c7fb7e501d83d7f22436c9247ec506a61c7c912c51a8f6a282f0d995516e7c0195bec8429968655a7d86ec5b4825c01c70dbacc40e75f89a77d34330e5432a0bb70193106925a0ee617c838aa9ea5cc5826da6052d0261cba0c724d8dee16dc0a25f68ff3c50ac7e55c2d8c16eca86ba75c0d6acb6d733ce3591a04fc40d5a87038f770e9ef70867e1c8aea175f8198a93238bca4929f6ab887fad563ac50a2cbd1b53e46ddb0283ee0409a57e62347ed16472f21beff3726dfba25d1003839704c343ed46af7c362a3777e9802dbec426e0edd7a181351cde384ef375905d15e0c0292261aeeeb7d3e67321ecf79169c9fee1aecc4d5d72d53e106c0734cada0591b42b103b012d72d82df4b24351b2f05538dc87a2543b50d344e93501ad43ef5097bab87ff2f6688f953e9ce6166435f88bd3d98de84f382751952ca2a18610c4725909eab59f19a4349d2f1077a944445ad7e22bae368f069e4e03c7bdfcd521f8b2d30f613e03214811ac9001358632f44b655a3a1d17c1bd2883b89f0fc978c02f0216cba9e70ce2bda4bffb117c43eba19f9872f3773c17d715ec7b7048cd5754277887d1b10d942af51a7e39d589fa690533663665efaf4d039ad86a687fc211e16c49ef0653e9c01ef0aeaeda5e0b96af440c9dbf4a764ab5768deb69599e811ac58d38662b0e305626c1dbd3e82562f7fdd1ef6bf5aa7b62eb9c9cfb2447265951770cef3c638ef63c68e5163715fbf11cc730792ae34f638553744e1734a86ec1ef1952591c383805086a51207f444a91aa1a83ff4221e93bc9599b90e7d771f994e0d566e411a44e707fcb6b9e8e0a56df3ab4242289442369817e6b7d8ee82e963ab41bcdde16761bdaba60817701f38112c7bfc8fc354b30a3f9ae2bd1d1a16fa59a560b915930aba19f736996ac079f08e294b95022280521e6a0e4999067f2a9083e5973e7546c1414b65b1836ee71e5bac7e6fb51fa5d2ca12114ee93cc8c1c5134226a4fb89f993d4cdb7c3b865e19c88e7d8d710492334b1cccdd0804784aecb7cb621b7c77e3818e670132923eaf6d735be3e1fa3a12d1a42708bb84c0e8251db1749eb5a98871795b78608fca6b908c384362b2c457cfe41f40990075938ac53c0a90bb2688246ea7347be3dc17a147d3928d4e05c6f64dbf0144b4885d6bc9000df61949dc389b315d527b7065d33e4c5035ebca4b20fca91c1a5811968d3dd93c758deb1da039ad3fe4742d65fab3b3f9e3ed61bad9148ecb9abb7f14386830e6052e240113aa01e69b7c1e5a77f857252eaf9de860969945e86b66e5c1f2b25abe5ecd3d81fec3e71032a2afb34cccf5f6a88aa4c67df0d8475cd3af49f2008edada2c1b07e4dec3464854aa8fb5b4540ded46b634bf1bdaa69cd8e6c9a1ffd0e2c21d50977e75ab83baa379910e30a76d833aad38a3571a4374ff6e2b895379a01879e99195696caa14087e2060e04eef40b8bd72022d705f171f513ffb46d31aad4b45cb78ad4368ba83031147a9c5717d09a9a4456c07c11543a3d989376d3cb9e5ff4576aa110aeb28524fe5a27334328dc3e04f3e322a20ec8b3856db81c4a2a7ceed2773f4468b54a98be4e350e578367935a628927f03b272dc565191325cd2e24f45b40a2d43e539412b937880f0f4fdd5cbfc39fb51a960e3ce0dc58428c455a6799f1d7b2e771eb5e1a58a9d8906212a685ceb17ba216fc671f39f82fa4accc98a08f5d269506016c9ce02c73ea86108b140c4d86c063bc458bf99becd8a00f1fe4810458d26e3bb301c9bee3069e5779e8e2cde37fdee0f456a87d497c4174ddf0daf7c2599cc21d88fcdc99c8af725649bd8da315d22cbe6077583c4f6e4ef5600901ab106b4996b3fd3abb023fc5d960db6705d347124608bf2ca5e792f3c820ee7e6d5d47d10a171ecdc1ae200c2024ad581684ba999094edf385dc1a0fc9730184e0c8bc0e571c0554f88403128fd876a25c7af82a81acf837c01cbc7c0d09c0926372d5cc68fae1e36ecf90288b000fc48cf096ed170a86d2cd5914cbfabab6085f217218aa5e182f13d4d81e8a20590de96521df74081abdd675ac0023b0755a7334d942513f549b37fb8d3e4d0f2cd613575e5dc6c95de63f8423eecc744f6751638d577359b40ef1790638c7c19517f03c1a57c3a86f5a7b4d9a315daba8aac758f707c0e472d7a0ebc0787e5fb098ee63001d91de1cac35de41b9718fbd2ba7c5ba6df40cd81bd6418f9ec28d275ef32efdb55949444d9eed9c3310327c520ffb04c58bea517fb33b058b2078ac69b7c828ca122566c15f0e51f4dc1dba2d014e0793543f95f18ba5a256e4c0eec81ec5a4f5c5f0419c658dfbd898d42ba1c0854e059f778b4e32b20489dba8ec268eb0ffe9f5195cc308ba917eb1269917ffc8a5dca8ebfe2bae2c6770dd0904a2a12079a39d2aff55f58428496b0112afa6c6370d02e3c56b82adf57f7efb656b2fe3b9b7359c0a52061bff34c8e3c657b0d2278cc5e929858ba05f8f50cf5b1be15584124319491f81a4709a95057c6f260659ac175d47f8b2908cc44d2f6dd841e16ebe4e8969f187d956fc0321098662da82e8a3449c3b52debfd5718704e2becc8ce0067104a86520a0d4e15234a25d95a32fce03e0203630e49569e43cb6e75c1c56ed19e7215363072dab6e27ba3fefa409090c07990d332325dc16ab9610d9881326432dcd9d2da44978a5e9b75db46806ee2581978e3037e2ddf574452543ff541d8f8351bb784a53d4004cd2b702c254c93c014e6fb813d4cd1c8e52e88fea8a0f02f8505f37c5aeebfd4341315b627ea55d2e7359f97172495c9efc4528bfd056cc353698f82ab26b9804964b1312de60632bfe26eeed6bb08c9445e0123adc6e381e50c8b72be779746cba3710a98101d7260ac981c53fd1e7897749ac7521ac5586679e97270fb20dc75b5f5858a0f8c62ed82ffcff3c9d55f4dc2cac59684e60d09cb6f12971647765fdc8c0b79b13f446088298b8444915497486d7604c30771af05de1128ad89a008763fbaed40c53cbc93fd64f0a293a39c02bb9e26f740a20ee7a4f1538029f9d28dbbf2e9a4cee8a0576333a9be2efc284b189fe0ec3185792aed3d5bbae3004c4a8d8acea8a5da12a544ac0149d310e72df4cf9cfdb83b70cf27485a05750a430b8722d06089d8f88ecadce181ae9e20bd695f4f50ed74d45ba8f7e3cb4189912e9b1b40fabb064969b06d495d148572536ad678e8ae1138939a375b6b40ced16300ed497b5e5d3f766e547cae9c7ee97c1fb4aedd63e2ff3ac1c5f7b8f6042b13f9567972d38ec4ecbbad75f1117b0f915d56a085404d0e369b660bc9fd278673323e50d4650c109d2ca2da8b28a00ce03a78cdc121807bf2e38c5cdde0b418d76689802d23d789c4cd0ff819554c969b2f257261da6cdfbef7c1dc4c0883b707fb63f024a20592bf67f866d3f949d79de4ed9dda990acd60aebd473e3f46938c3014b6dbae91bf6ee02019b09e29c0c8b876d3fe67aaa700f5824760aab8b82dd3deca1cd9318a65463630fb1fbf542e72468a63181eb5523a7cabf2ecf75dd8856c3d41023d7e1f6146fba7c5993d0c92024b8b7cf1b1f53f0316a955c68bea6928636d32073a7d8a4e3e9d29a32263f1b3bcedff0724f457cb36f861f67228102663cb34686470207fb09a77a13e593dbf8622ca3c6f00e95fff8071a6d7ac400671ce24dffe66ed9dbaa33a914281833e4904fa99123f068522eb4050fc9bb83cd397b9629ccc45d5fc83245f10bc9b431c1fcda4f52d8c6dbe7f8432c39cc7ba4c7555ad11b43c613653790ccd3529ca4964900891759fd8392098428753ce1a80ccbcf826fd88f58591c4c7f46e848823b2d3ddb504243700ac6b3daa0d13f02b0864f548af43c1453671a1f8f9cbd2be96d50bcd36db9bd0d62746f0fd2f8e96bf223bae2d2918f4c05586b916387814dbe0246f86df18fa87a1713dcb60ef2518952c7082d80809109364a82481c7b0fc12e054886e1d2426c63c12079a01083cceafd083de16f9da68ea6c1a2e4050494fc2fe4380499332047758cd6356513b4b3d88bc6a5f83ad7aec5b9239df0ac04f92ac368988dae5d295164dc1869a11d209278d2fd3e7e585e15fd5372c59c36d2133c4c29f1879b384747effcd32df8db46ce852e45892455b55ffac2ee519666bf316b1bda30bf5a836ed4c87388fccf4ea1dd310a8c0627143b7f10f3f0a93177d03b79950d6b42138a68e22a72e6e451d9e0694c45d99780b9b2d74ffa3d18a362a803b4c920532bb2afc2f1fd8c8ca8f3d29827040ab453c8202b09940869c01182bc3c3d5032c050c15955b2e1c06a424ff9ceca9f6eb30d27009e51930a8c958771f541f2fa196836b69c871d3bb4a8f95d21337ba39786ca75a39da62a512a226edd2e895053cb6ca18d7dfc2eca7278ac1088b3086a3b5a66ac120c786eced96877a512479c2c3ede253853fb8433c5e7bc91b495322889c6852b484cb2dee10f2304d3be15e96cf99821b8c9ee9f2ed43708af47238f7812d66f672ce85ac4b1eb256b4da5abafd90a59dc87eb5da4c3a5118649a24343f83472f7af1afc0b62a64bb650e22034a696340b234bdc24c5840bf85f04a21d8b89ad151e4bbbfd4ba8cb15ec7c91e203d89cda6a57e4a00fc1a441523bdb4a9ef087f6d8a8c30e8ba0ff33194ddf446b7118c5618eabe57a2801b8040bccc9d3589de4b4ec320a8932e04b69a8a6d316bf7df251c356d7ecec115b6f97888358b5985516f800a9d2ab2cfe078f1d5514ef5f2243b5beb71455d5d9cbec03c3edd5f3d319d05e43e46b9ae75687b51179e326e4cbf9c6560328a3e24d573c1ed9c9e006a117c258889e98458f44205678a3d29ba242248817f794a9141268930b6798107bf759ce47aac9f3af05a358a31b03464e556dfc21949a0dd2939040e853f0ab9039e5fdf17fdbe4546b58df09712571901cf552d04df4d8093be27003fa00daaef74cd7fbbce66a5b626d050f999c1880a4fb42ef703474337bace2b5f1e404c04ea18fc2f71a7ca935b04f17d2d45241052a49505a33a802b44dffa5f4b12d46c8c52c358203b2d71d7868dc97c5c0123224f7f2d63e24a3c4c22a1335e5d2dbfa445835eb8c3e4422f5a2116c5d90cce21365057c1d8e58ea10ac57679308d9342b613ec1e14e00ecc8d5dc0855f377cb22687d858cef947df73d516ba702d1dc813ccd2175004238cd9615760e757213f94dff00751c98fe14ba26f3982dfa78dc2941d05e2c46546f12cef5c793031b02d4cf042752cbba659381c14e3de475ad90dcfcebb0d28b68a284455c4501851d0e486bb59addfb2a3cee89e6d611270bcd49fdd66293f16d168995d1c4bf83ab0a838bef3517122df71613e96b12210d1d2eb1a4a915b29a5203634e8ee63c16c07d9e7d1b60fc6949309c241682b03d79fe626413727ada262fefab412ee3f4162c8905e0d6018bdb7442afe4c9a63c4b2e4761138a63dcd8e164b709948a525a8b7b4b2a4a9b6692c1b4e63d7584bd0f6b5754d107df8404f3f357228e86cd77c7a96c5a117969026f5f4b91b0919c3a276759956f54fd8ee8919914cdb6ae2919b02cb6b7525d57d1c9df44afdbd8cdd1cbaeaf9349b222c4c7d2b35d869bd54c7cf6c431488eb852ee24eee7436bcedc0d58ee927dc361b2d694c86f93cd124d1af002e4e767b203c30c52af7ec682560fd9c7b1b61eaca2ec2ecd33384ff759e285474538babe673767a36fc85ce44687962439ce2af1c751254360ee422950e95a7099db855edab2a73ec4f3696f98c5bb09edfba12cb36dd5673d5f79bef34c24853f8a49f31896dc9e665308454996ebbb0a5881a4a7ee01c4d71e418664cf8c2ea5eac24195bd04933138d9201b4d4e378859cea80f7886c6cbdcc69c154e90f494cfb7f63423a5027acc7101eaa0f66cdc8ebfb70222efce8816b8d30311ec6ee9252bb175c11da1379354b37a106178839f9cd9e77c4008b811641694ebfa38b39c77927d010c4c184c5a5c3533761195ceb8f2eca99183ec4908e70670558ab4dfa2116f3883ae3c845b9c8dc4fe1d46dc18460cef89e91de09840900a98434aa31696fa3c488a76d0d3eb4bbf1c2a8bb96087d84f7beaae86af999dad6dd9a631320a87ff9048532e45018bc6caa561f0fe6070008137d2e9912ceed4da6da661dc86a09f9bce8cdd2688b892d311cfbce3cb455733c105e5f42b86f8aeb546bba3fb3883acec997c03906668d05d99eb1d00724045a690136c0f995140837bdddcdd303393055c2dd36b82b3ebd374e40c3d02005c8c077cf489534b5e5cee1b6b86d059518ef09b9bf29caa5df4150a7701951d55349cec0f6662951326c2d583074b1e96e2f86fa77cf6a2835dcc02fa648919d398e8fbb6df2c6cca1eb3e5eda0b650ac6abdd8e8bb6a03cf459e53ba38b807bfc5d8c435ba1a5d8a5b5e9e82cfe77b275e7df3e35ba545ad44839d2180136a5b3f3273c494b96aeb28b245e3154d9ec80fd0873169d6b675e354c85483b42bc13d947839e505d82d134fc0c4d3098d1c98f84acc4b4724b8a0186cb0537548fc6312ca16a21037d6139c8d051cc149641ebabd8272ccfe83b31095eb5c33ad23f4cc70daf9e2ae1b1fc6f060fd316c323f9bdcf7f1992e2615081ef832694c8961b8d31bcd4eea5312d849974e99dfc5c9a7f2a875fe8ad706fe18a938ac51b78be09896419e9f27f4264c877fc90e619633673b75cca253c268f3df8be16a70d2f6d099a4e19f3feede03892d18350e3716e2bb1eb144b9f432b27ba0c305cbfecfedaaeb2aa3df2ebfd7610239a07ec2a3c7b0578a10713842c9674ba3111cac0428206e62020ac3cd1d5df8fc873cb0ef7adcbd5551f8d93a788628f49f573e8eb78bfd4fdea2a2bca5e2976d20af7b01fde58b577db83f6fb38be3ea6408118ccdc420d76f6ceeeddde4058ad0b5f73efee007af8963129ef37ea84bb97b64d3e9bf504ceea4412dc3de6c73fd05f5bae1946ed7522e874c45ee7d306ce51e08a04e8501eff2d70132154176bed0ed9da1f1d523274918671582b5860b8c33cad6adc2d24cf4aea12ae7a2d7010d695bf7de7323dbddfa8a3bf4b5c759f07c7afe5321f31a8858b598e38c382b9ec48b4bb19a8a0bebd32b648a4bb5818505c2066140c4cc9e8818540ccab538945b8404c0360d6dab78cee409482295b801aefc4d8a431952551ddc124a658301bb61079e6e6de45ba567628705232dc84b68d2bacf44ba01f4ee2a412a05097408706636cda827e32c6ed29b6cfd97132e5ab76ef32577649b1f8a1ac2365f2d217c758de4117c4d5ef249f1b8dbf2f72f4c637ed1661cbc6862e5a8981b9c8e6ed27e86070248eee6a25d4663074ad0bda0e76a1c468910e11256200814a2321569ca2e7924325249c43250cd2782887ca20fb49bf50c62eb74a8ea48041d981788db8fcb6ac88ab11a7bb1f0aa07ebca07ac603e3bb17502d532682b92dc306cef387f2c8470bf93c14eb1d73e011a25136845cf8592d2a4623af13e004eb2a017c2e447858b519fabdad879914441a0f8bd37f339d20177e9d07354c27777e46741d8c53daa6ba0cd57f39bce8f891b598c4133f6b2b06b8999b86334152e94d5d42f97e9e09521c24e6fb74d5f89d4b85b08ad91ba91408c3443e9cb05389f1ea6ba46438b76f17233c0d0a6e5772456d5ff44f9aedd086a4bca27c6717cad86f019e5a51c587792ab9b2815546f81ee9a60e39357de46b9519d9cd4e6e7094a0255aa13d407722202e9fbab66cc3844faefef4622913528e1b204921d14b5a5fe85c14fa130763b73351fd2ca8ebd8674628992be6b03c217ab0a5c1904f66608aace9628728f24ac25ab863d869bfef3f0ffd928cefef82948a01058b3888a51108308ca6812d053b4a6345194288b8bc3b473003e75b0561bdbef461491c696d4cf92e89b5692c066204a34062e92932c8ae8ce0122f6874033a4d620ede3ed8c133ee7902b4fb00c9914ef42ceafd0dacc98ab9aaf50695d053d6b9d989d81a2aeeea49de11abbdec401ad8551a8e9d7b9c8c474eee5c261781c94aee458372a439300ccef50808a598d960ef1d3a7f449d3c46fb55f6c22aa49d9577abf0ce5def07f4f08588cb749ec24450a3453b7a73e9ec8cfd53ba8453daef4970d698dfffb49c8984e3a20a93386edeb05168030bf01c97a46cbcc4e217d164d27eee6445e01df8e2a64537e1e3396ac25a1da9634b81059c70882d50f0cd3282221022fb85b87012aed6acb5053d914c7d5bf079b67fd146c93e33f5dfb760aaefb95db728aaf220bd0f50db99cb8d6f140bfdd983cb22cbe5c7a2024c2d91088347a35aaf7345f2eba3b767d00fef22eaa103181b39ca45c101fbd2c6c9bb2a219a9ddd104fe4a46df44c96e7c2fba80294718dcfabf682c1336aac7bbc7142bc7159a6ef068abd85c032789642307c8327ecccaca471a40112029f7394c09701a68395118cc2f803cfadc4cd487d49c475f90a4dd32ac0cbecb3e820de2492df9bf789026355771cc202cac66029c9aea716b98ea492600e1893bffc5e3034e327482171f7ac36cc234c6bb4795552f18214f95c9c1d8612532573ed2c072786517cee8b3f7fe6980b30b917d32382411898f2e80caddadc72fbde6cdd3695dee7637c5f376740190b168bab6ba0121d59ceec09b47d3123be4ac773ef4926d4ca89782b00a7da6345df7553ecbcf031a72ec3f6de42111b7406036de0013f0968adb3504d2ce960f52884ee29dca7630d2fc13e3ffb95ee65b2d61c4dc9cb86eb89913aee8a3783e1f270333ad1ea07dc1b02a0c7a997c9456871320d4a985f81c22071e30049c8c52ab11c305c5bf52f97e2c7256afa0a0f7a1db4ee7d684bf82380d5b7f0dfbcb794d545e23e661c18bec6bf4b5718e89c1e67324430e9ec73fad09279d47b668ed1f21aa1428307843125390277c3b36a43b4d1b4d4d201aa7b5e0b059455e7548052243c662ab1c8fa630356462395f473717432d2c79d1891c3ff9260a2d0069976da555ec620e2900479ce55f29fac45ea06e658389dab3e685ac41b513c0c88b091f24d1956816eefc4d14daf23d5de22d5def41553ee224d29f292de124851729796f4aa37ea6fc5748ae2efa622f51ebbdefa4f49006f10248d05525beb0b3fd05e04c608782e16dbc1c2a2c2383e33c29dc214ccfc9e1bdfc153e5e1369c13c94e3d33ae7aebe02f687cf56be5c0bc81f95df89418d21ba9838b842a5a24736ae892252f0784d4e6f2b83dfe65d4575784fe15586434f8b31852308157369e812e3981776fa745140b12ae1a60155c662fa144d75392b139fa6d46ae16874bfcf0827a279e9499778bfb05c368c7b9384e9b7c7f6972860a34f98272d1804e19ca8b75a4005a0d3f2fec8be19697e7b5bffc0128006ff39a87593befae335a987034f4d55765ce8790672f8ce0fbed078de18c57712027e902e7173337c4564bdd27adbf5105aec57715faaf702cc655d079647a2731fbefa8cbcf039743344747177247afbea54a4f367c58a6e4d6fda233c663582d8e5f9a939091d6307ef889cac7175a1dfdb949a6eb87b8defad0162dbc900b0d04fdcc29f78b124c4ad5433c89a578c973a032307ee59a5a1773e24a545802401adcc64a18beb187b7267710bb2b91127bc1de5f465e370a7e66c2e411657565b7049a72bae06357cf4b880412b5f8c3c5b56ab735a1a7f3d30af730e502f6afcf2e990acff136ece9fcf40de3bbfe2e01a89e95f14f1efc40a4f04a8ef4bf127249202b5ef741183975ed2137d6a7b72863609d80f2eb6e1b6ebb9beacdbf0c1fbc7de317d729d767f1a6e7c42f01255ea39b48e94d11cd527547a9005d7c00f64b9c4daa9bdcf96a46512f6f28845359b4a6f2f47c29ec9df132a584d8d6f4540531b8ac1cf6c40db39eb715bc46328b90c1a2f63b53a7dbc8335158c525486c05778d277097863aee0e00327bf5c2b7539182beabe4afaa8ece8dbc265a5f1f15cf5d121868af3a648e6f260cf7aecd73097d21442d25cd348003ae6bf3b0507301c3dd3b7969ae053938be1db8fc80eefeaa33f6516851f9a6c6542d82375c3e11078ebd6cc85104279680c27c70a05c467649ba41f4a569d46dad683cfffcc28aaff5d0fa1dc6d29d7f9aac33b0b986d5db8b05909935a98a52db43c2b942e093d983302edf0b71742b8481719c260771ba365f563263e74231e3e28d2753a8f207818742ed5c39c5e73da2f082fcf4529cab5e4fc2a33e2390f4d2d4fac22fe80f0c2f44902b3924fefe6be2f260a96756daa5e99d6167c919052c9586fdd74d654070bf2df71114cef74fe41b092d7dd74af0a2570b1ad6459433e700c2c436b5ba8f044bf55704889aed445ae162ab7257d4b48c9ad16024ef26e4e229aa273c89a6519ce204d68eaaa826cfcdee10fcd6dc194089e99967120a7e5cbe842a990764a9a6bf0180fe479470e244fec32b096cf78fefc5cbedf4c293263d630f5995e12568bb603a4a70df9279b8ebb1c54cc414e23006c5325cd461632d25a88d62b259f7488f30670a4f64edf75f05b33bf672dee11041f08d6313c4052e9f930bb83bb211401e9a6adb019a436cdcac837ea95503246ef3fb8250218ed30e140ac9503976e0a89470a34a775401c1a3742804eaa3f882dbc6d42bfea95d0170ec799531793adba165872859b131b5fadbb03023c3d31e36603403557328453ccae9e7e27793422be638c536eeaa7f3b2fff30e82d5a7e1f8e2ce0e2735ce4dfb4335bf8deb91f526e72c4a9a4106ca8a4ff67dcc557c40fd0bd90df45da5bb767a9c366bf68b28efd570fec9e513b2098e0ce67b7f8cdb67ea6079a528c2a128f723c323d378400aa0a58000f111e5022cdcbb0f4df41b2ac6932c213288aa7cc83b0ad91e2e22780b20d472c544d638a22464c8bed5bd15cf255ad164643291e7809e71d09ea905893768806b5b847a6991b6bbfd20f37a4ea1aa9caa80379b49f8e8307fca10efcec6b940f2722b881b05770c335ff3d5cc06861ae9f93d67f7e4ee54cde185d2ecc9bcdc0b38ffcdfd1fae94d76987abd7f3929c7d416d5a32f60bc15f46f2dd5346f3844e66fc06594c55bf5a22f38c9b14e6c6a73fb9362713397f31b2eefe9d29d730cc5310004a9daa7385c69ada6796b4afe986228ab36be87cdfc0bb6f28cadde4420d6e00f54216bc6098572fec0ec8e8f2ffb05e8d39be7f9f90af0be562bd755f275123149b5d4d64c7dd5ebe0d6f16faf0bbcae9e5aa158797eb21168330cedba0d978a8152e9840eb4fa58855973265f94f35e37fb4203ea73557d5f8f5c17b2497aaa26d3794ec9fd03cb0a183b1ddb6519b673ff795ab283e09da99c7c33ca792a40ca13f3f82c28415adc9a1df5aba35067ea1147250e9d5642a7868f7b3b66e629016f4e3b6a95e91ce67e46d900dfe032f1d6c18dc55ed8258b7397d1b05a3b40b13550c328e4223c5a8da0669bb42b85319c08f45eda1c8cd449afa4a24b72381f13d1b9801e7521ecfeaafc66513b0d1d7b407447deaab0714a9cc7eeb81e801722b1de891d0cb00d2718d89a27989105fe19938e88f55b8f8d5d12fa1524dfb380aad489dbad325a7e7c0af5c79e424edc90f8263e1e821dbd208544e4a6c5ce65ac66ab457e42e8d74598bf208780b0043b540d0c6aacd0a39e532d95b7aaf35967cb372dad63b499b8163a518b6ac97e5051f7a4f2307c4d4dc0588358c90cfbe819754dcee96866a6455180bb1f81c0c1b4bf46977f182ec7cc66a6dc58f6d65d4ca4f043c47335ebe9d91359656d3a697d4350cbdc0e65533b901be87b6d18c67b3c99f192e574cb3e44d6a9fcfca7fdca1151bafb68bd86bb5b32ce9f201c2474eeba221fea82ed544dc628dc4db2df93057aaa863cd32b9f15b75aeb246a560d199eca647e69cbe73b4159832c33c210a5c2c776b96a4290b0755ac4079bbb4e14aceb5df64c0cd6078cdff2c0b9e50966ac032bf31144a99e574be358658c73eb520656d9efac08081f5b535114819597659432c87c761762e44c29e53f58cdc0d5cd75b99a2dcefe1ebeea660c43ce24cde9128b60970ad2e1fe9897c72c9765b3755d18e3923dd36a3efb2e21e95b67463cc68f99169b0428b4b944c8d400c209cba66b998ecac651488f1f2ecb780800244463ac719f60f9ee22617c11bb847797d48b1845dd285f8bbb965a1f8d48221bc6abbca41f1358d4f10f10df5414428820b48baf75df01d3d2afc6f7e0297e9aa4d403e06c04bc9d013f89cc233a02b9b5d22973032ee0a39ed3f9cdfae3573d3ac87da8c49e30773493e00692aec8ad0dcbd87c121a9c14a8d2792950d1df750e2b867126d67f98da3a616eec81234496ea2f8f367f783d766ccce7e783af9c1ebe7ffd3a3111f1932bab0b177d1649ad4457ccde092afad9cca0711454b5ecef7068ce226c9a520ca41369abc0eb5849eba0f4ac41ed48f6726adb8a1a2f22a458c2589c02f49620e5316efb43fe934ff6cdcbedae0ea0d1ec3b34eb88c9705f24f2d391649b506b74d68d210d61922b2a7fc0a2f219ac10e708913fa8e69ce37df99992052eb61d1ece17f32b74eeb2843ee910b7e6020494c4507e5ff12875494f9aaf4e05bae115000cedd8d2d3f93068afffa2d50810ae455033ca3f138b2bc312c63baeaafb4ca4bccca5e4165c8c83af971db673a41686a1b467fe0c392351ffb0467bd338d0e46a16d461aad178c49cd10cdd0435b4e27945a39e0e8bbd81fe6df23166af606c3fbb35c6c18998a8b815d2ecc7a54b20dd0b82080973609f70a4e8c617c020acd6cf65299be45e4026e2d228a11ff48f03658b641b202b8e52400a514e3e65ee162db4f9fa7a18e35a1f5b129f8706ae4df36696635b3b9c8979dce32be6f7fa5ac65547e82a22ba37c1d0e4fd8e5e637594a476711530805cb815ffd9fc1938bb0debb789be8fe0ae11a7a2c4ddde2905a12207af901243b6b303c9e6d0ca907e0b79e0389843899c66a90276f217097d9f07d2071039e2a3a1ff41b4b4df66fd0a1938ec3845b7dc2f7f50aa8da45c955e716baf9c6f58057aa5c6600122069a59f3311ad24e4d239a4ae5043213728cc51e32a97350100d74709d31eac0a1ecdfa88a6d82b61b1b95e940732add74adfe9649e15cc74230a8087fa11456dc33acfdb13d82d4b6caf63024fc8c7f5c62150eeba1ea69c8a98cefc82f3f7358e7efa3029e25408c336c581e75c97aa5f4c12d3b3062e6db81da74c15839614eb8039204383b46514d2010a02a3c8eba510b5b35864125e80709711e46082190549e70eba5bfa5fe02f19db513e37870a05ef14d7c65e8a32044299a531e81875bec42d75953405a21978ddb6a193e284121f5f2648d0f525c7a8ca3625e7d3c0ba8cacfceb66009c846f7676ca71f056391bb84715015ba100ba235ad10284431a66f9494f841e020e3f7cb9115c5380067e5c163e3a969a891d47bac102ab3c00d490e85f2bac049b5586f092be9966441159a21ebc0f16c79b22bf352c7c26d6adc0e996655f4608f85eaf3c866b760689a5013d280a196d6b023c708a8e76cd6bbe9479be521d2d055ae3515fab406b3f233c8c5d01174c5d50223cbaf5f3e794ae879404b0f3391ee1c249739f88a9125641b566a017219619c425331a23f516a8974728015c8ddc52b7bec501aa33182c07867e925cbbb017fe8fc3f1efe00d8829f50a320c561c5453518302428687bb6a2cf9e05e54e36284ed3ce09d07ee2092f79810e4ea11208e9ddf0832a8f628c3974bf2a80ae1d212e3bece1847fdf008f857a28265649600170e03c143fb17c68428f49e6484d7e219fe2b681b1adf08c474ee995995f395e28c13875fa694a98322b7cab0480b590e50a108a14d8af5a012e3c63e23592d56006fba75f1ed5fc81452bdaf19792976a2f7308f4bc17335d2a4df5fc9664b6606506e103151b99c75dc55b07fb23f79bf13a58eeae143184125e447b8ac6561a0f86732cc0daf703529ca269a5a4bf513e38e4b1715de08bd3237d54fef4411e3a6daeaace7055aab3298ae6a5351d5747865eb22e7cc2b22f6c7ec887d4ead995b35a676f4cbcb0937a19253c99b199d11066d5a29efd8aca7556028c0e6ad407adee589d6bc20161efa03988169799393cd51114d673bf1ab593b92df6f6dc59dc0eed83daca3d06de7051bd43742a16dd52b370a8b195301ceeb564ac511b3fdef93888e178280e5cd940815b8e1a7df52ec1bbe0058909e47122961b463ed6e73ee8359d06c420fe03fcea03ada3c04cc6ea205d939979d9a640e64695cd27ea10a8a60397d7119b6b1b30cc9d91cc9be78475ec1a537dd1d998977c05b65e8538a3b271c309d7bdd4c32229c5262dd700691eb4507ccb6be94b29d33310bc1bc2be2c81924f8e09a2cea055aaf11319ab99e9e4ff9d880bd7e611ecf56a4aec9ab29d69e50c88c576948c4290d52dcc0775e09927ec9fa414ab348542501726c47064c3ca3b34f8f0ef8bb6f6801e6293850b158a8907a2199b3c9fb341773c424cdded32b756aece6661b192dbf665e8597c33ccf8b98da5e731418f1a29e5b20cea388d4e1a61a3b3f66ee64663192d2f192590624307ad8dfd325133861310f6657308b02fde5afadb67bb98e5228a9732889e3c70c700033f725fe765fda503b8c3edb9b810212080bcf76e3a8b7d6fc137b1d9f2edae758eeb5375224fbff93efdf2b908704cfc52573c5494dd818c899ba8de144fabf87b2efe52643035fc9fad9c5743c457efad1cb2caceb029a6e89bf2117b25120203b21def16d510886f46cbbd536f05bef05cb7ef5df83b861cdf37526ec88aa4c06d3dbc36875b39481776dfc1af464ddf355c29e53d1ed76035038aed5f78374be7aa36a931376ec179655244f398c9efb35c4cab56190bcd97c0caf5a3cffd8526921c325da143c83b28e0843b03c81b0add5be411a07e123cf3a303b5cd5b06f745d31873dafc1002c0806f91b210e687a0b58ba13fc98da9a02831edb881af0adddac84eb2739f62c2341cd5376d865ee980a6b8390ad248008215f873635f5aad65ac4909ad482171f9f26c55b82874848f9ac6ac8d1dec49f6c4bae677b0a54f43ba8dbc31a168ad7415df2ac7079be1ddc23139eb852eb203c1fcdc883a8a3c6320ef5dd80d79824fbd0566e0b347122a36daddd05a0440d46cac12d2d6a7a664a40739f0de5df375882ad5b3b2c6d1372eae46209268163402bd77df4725ede28d1bc89d37db4c4485a086e0fa252f93e8f2203150c080ee0ee2c7ceba8a12c15aeebccc33572482d844d2bc6c408975cf3b4ddff7fd07f1a647845cc26d5a2edc465846ccfd665e62a59477f126b3a7d9b9e5d10584f44c4ea6fd0e7150fead5208a90be8554beb8c6c28c14efc08dd87debf043a822055bc962dc9fc753a4553c62f142d811fee9c0c105b43ee60c5ef495c880fb4125f456c896c91633720e18ed7159f0e67c702bbcc8b01984d1a0b7c245953af82093be2a3b48002f8c49b26b37504d9aa780c0cf392d2b1a4ccb7fc96c42cd390d23e29d6187ad03da5874ec70b57191627d07f500788daf429208f4d60c817b8d9a9490b4a93b4ac5f1a90b2dee478d092c9a61226c4fa84bd976ac7455fbb5fbbfdb308475b29494ce7aec458eb22fd84212a7a8ba43580d64278bffd05bc1ce1d0c435710c72c65eff968ba446b8695a7bba16603897b4e5f418f42150e6b2c29a2c15abef3e3cc86b1d5a9df043a97575227bfe67a83348475c11984e09b80f64180241320096cfc6c07749e2b1df8269ef57a2eab36b31d0692b46723ed4a6889198185754332ddb0cef8d23b1612e3b90a62b902b0e9739042fa4205cad924e67dfb3468b9feed7c8ffc14c3e0edc88eaddc353a3f76a33655e04c079b5c2e801c092818aedbe910055b62aa5be14c8d85a07e392a929e767601f87d13fb29dde58bae2acbf862bce29043add479fada22e43c78305065e2cc563c947478225a003f27827a9ffd2decef6e94e719d5a9ce303d4e643203334a50ca8b4868606da2e7a0d7e4a30e5922300eb9e08460b633b36237c817b1419973cb3e6c616d71d0be8b95eccbf7a580a6940fda817268ef9db5da70f739791967ed172f35be4bd1516189f40acdfa22d3b4b203161ef6d8486c5bdda637bab67889e2c24a8f414d21f70df1e3b3218b44c8699943a8754b2dee5c5a2f3d76046b58e70da8bf932810948fb650dd9cdf5d8f9947e0fb2ea2ea8cf6b9b81b1c60199fd0cfc0f3ab73e2dfd9d860b4a8bae844bd629b824fd876b38143a26bcdbd3ba669ebcd515faad665653493da5046568d7744c42316e3328a5230b61f37b002bc1dc335f82806446f4ad2d344e3130cd395024b2b6a3c656fcecd89f03ffb01b3ed5772a6e53c15f7e06c0c37305a43c8e9a4f7043d9c581156c63905ffc283c40ef068289d1271f58d8a10f234680d439e3feba3c62dea692f325a0cba55145931f7b54eae9c1f4dec0f31fbcd611eac0955f3a4ea24125b121165d5d4c07b0c31d3df228967aeab98af2888391a88a6650e655a26086bde9e131842465d842c5a60e18ab727ffb05c77ae66375277e57c6f11e904d95005fa2c65a2339872a3b71c7796f91d6421bc7c6fce3aede79bf992ca6b62f2fe48a28b4251671fa43e4025958d0af46ee16fdbe1124915c20155dade7c5ae4b1a1653427199959827540c8ebf4db7e168ea36bc3549da119233a0985ce1a83b277dc18f00b2858b000d641b1be45e2cdaa6cc117f377a9e34e89bd39b10eead1244e8b7dede0224ffe3fd606b97ccab952c2ec609dd56b5d2c87e0e1f4463598f6792d6180b97d7ddee6f435d9252223d965684028e56c1b59f85c11b2d9fb4ac4aa94cc89a837dd12ac4a8042a52823ed2292941a97e57d94380f3eef3265e9ba4c6daf799d352a57dbfbaf07ace7322880530b0a79a813e456b0ad45500aba79556bec40d686b3317911a6ee1e0a2dbb888dd7fc80ef27e3a8124a9fc8e10052e0f2610c78c6eadccaf0878e9c04a7d1250b2a8d6f188996a8afc367f83b7c85eea79f3d118193c7ca8c5f934f2393293c20478ea0add79bb0c532f058178b75019f430206aad6b7b20484e9c024318770896ef95d3c4065507e9c04df8b705a5626553cf3962d42ddfa79465bc9fece3c9b6d71f7008c05c2057ae8e7c9a3f0fafe98b2f2ac43d91db2c8fbc27d4f3cb96f6c5de5bba7809fb4c743ec1f96dec72f92aa2a848950db3b6dfbfdf0451443caab9f1dc7e06363dfccee3f326e11b9aa6cc6ab54ea9b66d465b281812402cc144403134cf29d633cf9546eb6409a5bacdd265fd0ff5660a2f338f9d5bc744ad6d658c2f299bb3c7c193c328336186c6ae8e288959fea5fba057b734cfb90c7e2706772966f33585fe94939a3152584c4a4b1a42b958919844d16fb426c517aac399b0add412b284b2a593f788bc0d47d91de2b70aa33e1961b1fe072180d4dab7aeac40998b94195054ed4f89f5b8593625ee2013f2929bcc93a40d920bef6deaad20aef3385a8bdcbc7371ef2b18acb15e082543f908277de31267300065a5edca12f2b58b934ef173817b2241a280b49969fb6f2172d617dc0c7fc2778be061963618298cebb2ad88b02328d08fd6f75bb9a1e14b6bd90d69e4ed0384ace3860f39756a279b4b2b1fc4b2377bbfc48f4a4b1e779d6688260ae4fbc9f3eddb63d7866eef8d5a2855f75cd308e1ac27d6cf19cd452dda5b292ffba75796c381a4355bccee86ac98fd430492273e625c4f16933db912ab4bba47dff57091b973d91f4dc343fc8f1b78384680c030716c3fceea5b4a1f90a956d0cefe1835f32ff250e732590d98cf7e74dc61488d613f6569141f0f7dc89d32ccd76d235a04ce3a4dbca7ee02d5b5dcf6d120fc5cd428a278bf7c27479caf94cbb0e32e710fcc3d5959157c9e5e283773dd76aaf0a6d1b612942a6b4aec04187fd6a2bf8352d9e4f56e5c43e9be84e68daf39c2285b451def3405bc5b00f4ce13da6fb67f3ea68f10c4b155c1ce2ee4a88ace075aed8c984ef717a86070a23a7af26eebb8eae8d1a870da02318635ddeda1bd93c58d6e992cd8a197ca2c87e89b32e732271640ffdc1334572b80321d771eb11540cbcec678918b118207836696f4ef7dae0e67f8100eb8bb79716c8a5260b408ce88f950df1aea000b757b1570f3cc3685ac8a03f0055630d47bd3a8e9a27fbe89cd19f4bbed0975691cc5b2c523c016bb88e2cc72481b9ad07d005da50a5cc17a3933423fed48c8643d8de74f8120f611efd3c9d22f9c865a7cb692ed2943f29fbee544c9907f5e662de3dd08aa707ca3475cbedd7c3adffad25749ec1aa6e6e1ab27eea1a07b31f5e2d823015fa5c4d4bfc9b3ce60faa70a15e17208aefa6fb800dc6c79f058cc31772f4513893e82b126f4ce129f911ae19e121fdc48991f12fa483e70173879ae448baa02e73367b9c1eafe4cc38cb212c13fe91f756f56438aff2a5d3813214af9c5a5acd6c6f51d12453b26cd6c2ff01167464f6d6c5de9e125020f87a768f7d81b6850e2201ff1f78d891e3fb45c39c77fc7315454cd06861016b8d1405b612e393b7f587182a15a221ae9c17e2fbde401a3fd15c26469e5ef211615c677cf6c7a05fb6601af8fc68a47dd1dfe608a3bd00caa4997997d83fdf1a502e32ced71ffdd6681dd4b15c645aa4dc833cd0f79770aa4ac144bfa23661c5054dbdbb6ecf2157679da0c047e8560c9e96012e55550616a240619f97fd0a86aaf8d11c7de03ca3e5d2efbfd9144e2cdea1cabe4112f3ab5bafc0031693a571f3468aacc0cecbc5b926f3a77d3cd0941118f50f167e7d8f60cdd3e4c482274419691c8c1b1987081dfe02422fa4452a075c1bab7b2dc80141c5d1800b1dd4a9b5afb2dd7318d37ca162a25fa72c4f3d61d2efbeb431072c61b5efa7cb6850dd2e538dbc80a3c9e30ee7535b45dcd786a199f1d587c59f99e7dffcf21d2f4758f90a8167fecab2b59216cf2dcea6ce082d0f245ae26dc76f730b76e6c6bb6de38c35ce683efacb13dd55e2a3ac1fa84abb993dd8f3e86cea7fc559c1c282c968b5c7ff883f5816412ef36e331d03a9b1de23093025f8db81f922843300c717e7e6aabeb5ce30545dba59709b2c7d96662eaf359f3ba13f96381e72bc144fae38c06fbbb0154f36e1d804639f7dfd8ff0cff61f4f60b694a56b29cc907e88d8c00ae6ffca7b08f4916ebf008aa7e2fb5e8ac8aa6e0b79d477a851cd394cb6f549aac2c7b39a0b5b6be46fca819b9bebbada382ccd2b33488a9d6ff23c497b70538b49b02c396cfa44e17afc6487f1efee02057b1f6ad10631e89884e93763f3c84001891e522f8c7d9b0dc2c2bcf7dd6bc3fd05e2d8eed1c2ada6c5796da48a38def25ec96a49da50b903cbd1ece43f76b65bd70d42caf4d2a6e8273507fbee33ebcadb874d1cd64eaf9eb7e605c4d5b91eb6bdd7eea956be143f304248dfad597c12c259159a4b0cfd6863a206eda5c107dfad8608fc45cf3f61606e5182ae753822942eb9a98330dbfd8a0f9ba7f9db3d8c9c229e4add97cfc257128181732dffa5c6c78db6fffad2d058f6209a96b8bd69e61dfcfc7f53118375eeb9f9bd206a071c8d44290c19a0759a4d10b6e08a5bf597398c34b4e5cd6e73e730d44de8003a47940d8d05eab85db69948f14a889f1f49e35d51b319991961e328af1e18e942c0ac3f900bd736a94aea17504a85d2d03afde1ede3908994644e1277cce83ccdc595c1a1e2808c7f0b01009ea1b0244711a7c72a07bddf4f1c3dc22a28e879099a749dceab7acd1db6b676185c2059e0dc08704378874849336cc4c971a180001fcd1578367e932b102693c2bffece4df0be8e5832efcda832c4d9a33b31718a5ff5213300e27dae1095b2044208d8ce859d196102841e433a536354d1ae0310d42eda58e9142df1eee411ce5366f6f8a2c8e06265af8df8065e012e05b4e5ac649eeaa246ec579c6bb36c24d600d3182238b47632ba49e264e43948a849aecbdd6d499cd856201a9cbb264136ab03f9ac892851d583cda6e3194c8eba893e77f8c5459731ccb9f76342bf376a9bc590129688de157b2454d498ba62822128388dba7af0d2d2760e6bef1c07aacab830d7ea594c135e56ab939091c6b2220840f02b0f110eeb11e84a8c181f63d3b95258ca064e2b2d72b15eb5b09cb0cc1d003d25832c3c42fe4ab8584ce24848d0155aa16538dbb2cd294d5e426c0625655497b06a7d9abd106c6a1cf01f87d20be67045af148f9243b7916c11325325512970e7631a01577b4fa1ea4f81fcaf54b10cd20c24154cbfdc59d808defa5de967a3eb8860735b273268be8a234085803db2c00a99d0a5c8eee25e61e384a1eee286365bc610ef460e84371984c5f3f38442c567c208668c1191b51e0b153a5e023a92a2b5b2965114c9b369904284be66bebc0d48ec3fecf3cb63e3b59b7d8787bd34bd432cb17c5240c1af29f9893b29aa6eabc52e1751ed041776916ed5d005d0d769b2dafb2125f3dacb9e59e25e5acc5272b436a46bc482a40c81f80344029b746e591e2549723716e96f87ee7a6ac9958884e0aa0abd8b9a40a53484b2518ae6066728c16d87356d6b3f83d5127a688be801169dbea85ab9f86f612c0e8fbc744390357ebd4d25c28f9d65b3090b8b1d125b4a5f325ea1c03f1aaeb2ec1e0ca36e8b76f1980d1eb10857fd557b6ad7417bc7e7584624682ea1b82b5175387b6fe333ea81fa0dd58f573f6a352b66e537d8972317fc971b658e609b195094c6bfb0d75b45a9b59ac89f122a7c8722f8a14882159e2d2dd6239f01aebdfb1a8ca3c90258aecdc40705bb88614622fdc15cc918798a1beb3e250234cc93c9b5d6696698df9475bab19e8cde596023927f748084dc8bd6e99760f1ba21e330484a9d0ca7e16f01600a956bfa14e2031695552335c940bdc0f082b18c1361904e554fc02b7db7099258b3d9068517c11b7a35bf054d450a38207e415556e73b1de589aecafd6a42f21588cf0fb7a1d869442a85e6d03a3baa9acb8dadb14006dc014f6dc1cf56a6839abcea3d8009566b27ab5fc3c80fcf42e51adb08c5fbea870b5b29c3e57af07cce911ae1c90ef6580e8df94ce0ae83fff5a6a981966da3cac3f973ac6e3a54348b2abd4d1c80e07125a9b22eb29e1e1e092477afbe780d9bc4aad8fe2d68dd853df15242af9b4a76f471b7d6da7a3a4a28dcd1de08909954eecc772772093522e31a6ca745fa398320c3489e6209ce53186cfe5fe4d67faf7f9d2be7fa333b910fb3ad15663537cca20051698bcb32654a9370a2c766b4e91ea7446d18234905c085ef7008e678ec9d68a62f2897a558b31eef28a3f39a2a4273d6b9209f6f37099389e585e6232b9fc463e02c2344afb243d79b1896bb6894314904fa9e69b7002b365f67545f629fe0e680382ce6ac795a4919ccb00d7c2cddb9aa47d431eeec84bb3cb5fb44def90a7dcef58a973d415b28ac1f8d968afee68d9d296f97710a772abb201e11a4d3da8b15dd5961e3d28d17cedcae0739e841eb90547d1e642d3cc0b9f61221ba114d9aaab60ff237e8e74843836e5614870a57ed96300a67eaee6717fce92468fb15a4b74337a68173211041ddb80d8a3186d92ef9dcbc6411110ff396454a7efa8c70fe20c1302c4a1fd29a66d02240fc6aaec7baaa3ad86ef4998079924aea7747aca4ea6098001cc8469c494f4ed2762e11b5a1d86610591bef158b29188ca4ba9ed8d81cd03a42faa40644464cd2ca7e8d2cda6cee7c33f24e10df9819deadcca14d4826946cf0efca58d6ebab789afe512a03e23a49769c6595878dc482ec5c7c5370be00bdeb695fd51a598982d8b1bb7000c26155b51df0ab80cc77de3660be454117b2a52ef80b9318065e8655aab140a3ad65f4fb034620a9f6ae7c3e4e66bdd9017c788328ca37d4fddbfea2c2f8a6ec45feb2bd0a6e7d59d284576359f4d3047d32eb082b575d9f007318d8dc7bc46a33d91376a912e0523966a2a7d6d715138438c635a8e0e46287fb78cbe154ffcc9d22f45d9215ca2046414b98b9c3023e837c87f08f8a64802a11b9edad24e571c27b73e1cac9cb727bba68e44461263549a3a820deb64e12fd35f5f0c48a7fce69b9cb32742cdb7167efb4d5752aad10442c444532c7cc894526450e114b751ad4fd684e7a9993680c2c0e794e33ffb656c7432b9796428248ab6c637338e1e4d4e5a02c9daa7f303670775496ea0c07192a25f9b484b9e60a369d91931ad91466fc0c9fe974c2562b4b34d0f8e466d276f768fd44752074c4646337582f34bea81138b30e3d302e583c9b1c08b40108ba4e4902800e53287d1112b1d20b6bde3f138cf6102cc6b5b653666f31918a0f6072d3c2b60dc3ee06a70a44f9d8b283ccb96fe03bde6578f9674b1bc9c37222a8d3c4a3575a6c395a9a40262f0ca26742a436d2f72c02f534065f965f5e61d12cf651772bb861e7f89e995661c5f17db517534daf4cafa81421fa0f1277521299216a3283c89d4137f6351ad9f96fdbda547146b39615adc945045dc24d7abdc44fd8f2f4343006cb4708334eb51254702780198814ed0889226556607893922733767c7b763b1a43c9a52f2478d04facfb02569f115ee2ae4b978316c1272d9fa461fd72958d233168600f73621a5ce8b11ccd6977112845d95631b995030cc0421c790d4e8b546095cd55b3567bef81b870dddcb90d87d4a9e3696c878689db63635b18d74068a7452a5db462c8c721151f272c1c90ff1a4c3f04b73b9421815c74375d2c0baca51785bfa2b1fec977944f1dbfc1d67d734206ac42b02f16697a05a9c070ae441a10d354a110906933107a33179970a2f81633be1f4224e7f8c030ce4903f39d9cb6992e7577832f9f853e94cc8cc9216aeff130dfdb36b462e6e08608cdfc83b2e5d0fc072410709dba730d51bf5f51ebdb6436e1ed59c15c1cb75924056ff34ee2cc8a5fa7c6143b17aa4381bd09bb831c3c91f9465b48383f2fadce7c0838dd1b9c1d99772e6cf2ff4906e33fd12167033ca4b4b6c1a437cba71f663892d4888a6d252450ea3447b41dc10270a02896f2d94eea68a2620b4665f7211c131aaec49a777cb7ee5d7cc102433077c97cc2c633de847d171fa5d51cbc967b8065e14cdd242869490ce708d3c287a35f5199acd4b8a8ec91745c3dd178a0a7f2ec333fa42d1b9fbe60a5efbe0a2f01a1b29b3a7322ca30f149debcf1505db978a7e6028c356af51a6f60e8009ade4ab5565f6d9ab7c52bb84a23e5dcb9becac8090e0436ffecc4a916654e9d64682bb694c4ebd181535fdac96b271c7846ef6465f32f82cdf8d821d5def2fb5a7cf5ef83c1a940f4df0c0c65ac49b90191f39de13bd8e3e406fa337749437756b5e62fa98674ac9198effd54153fa6bafb42550fef605ce3040b78ad18beba4eb9399687f0af5755ce81dfb355fd05b67e06a5f880cfb88900297c367a93b524d8d8b0ee8c7988e0084d35722c242c7042fd5886a1d81b3519bb49b1d5d0a0b73f5001e7d5a912b1b922ccc63dad8987019f68ab58fc90db48abfa62644fc84025e8ac966509a84271af2bb4161d6dda6f9039c041bbe902ee437ce54d4036094fa946418a3414bcca4e8bb7a3a47ea785e3d96f361c2147529a7bbe0c3a7719706b3f4f0519e959aa4113028fd2f686bbf07e2f442db85b0001072973d6191da197010476edd973aaf4ed5c042b5f790090787a2aae7a5745c424190404dd59abb717ab77a96e4b711c194f12aab6f068c05a2c0c4c6ea759a4147f743bd18316af7224970efb540521568d1f392ba8934ff1f15343017a64ced05d847e0228bb3f38f74b237c6043bccbf6bdfaf6b94aee9ff220bad548e22ad1944bf3673c0770da2c76be9d1b17d8501617fce2e17944da10fcbb9a1310af3f6d944477c0313ba19b66c1f8a0c9ecb44d0bdbaeb9d04532f1c1daba7f1dbbd9d61bacce7dc269f106276472d8f2099f552eea40406ae30107c892a16f09272618ad7373ff414bf8134e05e6f2753ef94a3a66af3ae6aec7296d0a2455fcad6aadde2885ed05bf16ea317c006d27f9130b140d3cd3840e220d83cb18100e13f4865966b2d1fc176b3dff8ea1ae21d19dbd257425c8e236e7c0b1c153d2b5e6b2d3e92915ecfe619dcf873c840a4db7f10e9df1b263a96f1f4957c991c8be9ac4363fba6c039bf97f9913f6c15f3597ec6166ea97d3fd0c38df737df2d91035e8b6e76e66f31bb2c9099d484ffb4c0f3450b6fb1e319b19b15ee6e64a64cec724358e00665c2c465eecdc17aa733ef0b3a2c48f001ce9f22c63cea55a48030c968a86ddad167674215f4c8bc43ef083781dcaa1a2d6e807ac9702b76a04cf4d715215f4c45df906f17717334726f8f3f6cec3843ba15eb08be75d2d2e29298292caf236640be8e6fd01f49fed2058648d2cacc6c083d2992e796a5497d5d1334a1cd9c19d285826cda734091708b7aabc43e14126d697047534a43a3bfef9ed1b0b8a698fddcb9f5fb3ece292cb4fe675ea8bf64b8cc40e5ef732133bb1e2cbc4181ccadf5f3816b8830de276d90fd532ed671f9d6e739a1b4c57d15cd82ed0d9b3aef7738ecfd468ce85e3787da7e926d1fca5bdf2e0dc58bf7d7f2c23330c476c131e5587f8cd4b0d28fba4c7452714bbddd0a04c21925fa3e94c6951d732d86dd7697453c0705af50f21a95481f9bc9af99e74d135d1b243bd8b4c18960076d69af3e582faee091d755e50e9f111f44cf95d7cfc5748f8606ee03154f065f0b7e67c7ea7cf918a08b6479732ad5ea72de2e982f3ed073f46078deae8ef31b5655a70171dd55f600d277b2e1e474eaea34245b82414d050088abc09c6913a7edffd797ed8a2946d82788088175e9fb1b4ce3807fd7916ce06552895b156c8d9df64475a524681f073a1c735a38ba111639af99d325435202ad8d16464233a224a81de51db791f9d64b3e42fc69d37cb0f5efcd2b46813da628d5d16c26eed4441132b71392d43163bda691ba821552b885d5a86ac847557949ea5fd3281e513ad4c9950d36f96737fa35c98d110db6866cf85272f7e1ada75b2bf97bcc4c7c4d4b5f432fb217e469a3f0eb03f47fa58e6295de2bce73635cb11adc4ef3be9b40e081c87d40f6d996c99adabfe58bc00b76adc15c35e96a571f0fde85103b1b58504fcfa5a0804ca5e371d43e83c5628929513b2f58f9ab0a131fc79395414857bd59027b94274a865a094e4ca32852fecbfdfaccb1eeac976e850383d023d879c88b398ee18e2697b20f9d503a3263028fbea6fef18e0f32d0d511435411abeae94a506c7ad2da7a3f13c3fba024de214ffa738592a4abb82ccc0dc36806a91fb9ebae89097f5771c643dad4ffea5c744e43b4e2735eb79ef96d965f5e1a1d8b6b8bcc79178e078ebe424c8e61017cc9f0f480791ecdd50fc29e7ed694123ebdae0bc92c62c704d30187b7547af6c6779b147bf3d756b75cbce4a67bfb233037df5d123078df8365cd88f560d415eab632de844b65b85c507bfc263268b645f8c4ef5205cdbe822ffca535d06b6bf896dfa40caa59e06b7e291528b7165ff94d6b4059abf18d1f950834752d3e8fffcd2a83d6bfbe4e373fff24de2d99eac8905a2ebfca0250ea8ff8df3f6818a794bb32eb51312896bf31f6cb027c2dd0635d5e014f09893eedfe0a7cca48f468b757c0534ca24fbba7029fb2123d5bc0ca08b070828ddcab78853b264afca52f04d58c3250dcac9b93a00afadf98c02175419c08366a53100fc7f05635f9a1f57acf1a861f52946ac853ffbecf6772b18319c1af8d16dc631e7d1470fd973ec9ba9f563137d5f8013d83f976a64193feaeb778fd563072f300e8fe6ac3af46c9bbd083fe34ac88a900fed93327b10e082e4b8db5dcd1803e0452f9555762de5402e85e3ed38736301858600f44e23aedb2834c6007b23d8fd62e94e73fd989280b40d3430867e8ed25e011e2d66aef8c29e6651b0a0e2871c0f50f1867f139e325d7f8ab2677dad43ddd1bb65821499e01627c28d53505a4ea5515f9a611d1490d6933f2001982c417bb58b5ada4e2d916aa16f8830d95b858528f39c12d76883fc9298da61a388c01580e6785886affd20b0d7fa88fe5c3531039e7deecac6db090c208b63ca9e3adb937aa45b2118446956ae141a8d1e28465411670e1fa7d442250862992d1d244c8358491c6f80516f7f8c61417e4e971f50b069c975c3c31556724899befa3c0ddecf9ceee3970683a5b2288d7b8cb113d2c7853153fc631ab0216ac14900778398dd45549461909924cb3c60eee9f151c360cfb0edf14f0ad3781c4f651aa173273dd869bfd4f41cfe4da02b66e2c25707a353a23901f5989822c01cd5de92bdb44698a84ce5203f295abbf032f1171d894188eb94e145a41176edf7aa66a331ea8f62983af71214fbc2286186fccdd1b270a8a2155601c11e31e45a9c6bbf52fde3a8c00d7d6aac367ab30268bdeb8b23608c5fc50646d5c4c21727b595638c2229fb07ba834a6c6ed79d1c8c3d618b34c252bb5dbc06346c44a7c1c72895aeb726bd024aa1a4de405ae5855c9310ab18bd2aa696f6180fd07da5bba0dc1286480917cf6dfb42da5868873a536c785d37800677dc9136bab022b295dfe485b5e226842273194ed3bebd5e5c78279aa761eeb0f38b55378cfe75b0f57fdb49deb7e23554be17a64fba87970c13b2610c72d65e14ae9d95ec23b99c57be100cec4c3b8a2286096c6ecbfa5f5fcc66fc4acadaebd6c9335428e0f957c8cec508a97f84241b700e0b8384fae995de0664045dfb4f47bb3f5e15b55d6d395982ebeb6ecd4f9ba5f79f74ee983f625756c0a0275f0ad897bb2130b0e2e82c2cf5e4f9c500ca0153049dc7700235684e6c66ff7f9951ae30cda9547f3a20685c4f9f9819cf6c974390a29bca20dc2d1aa8ab8f7271af57ba63e120bc5e4371de520d534dcbdcd9e3c4572fbc8a493586607b430b5816e52de480cd687eb6c231e7324fec496b17c8e6ba4d105279ef70ae3234fe095c4ccd1b3866b11e9ea895621fec67a2c932cb3598e6b1d1d3c5aa34d5e9d8488c84649e6ec1bd8f3f8562bb442958c87ea24005c70e9b5fc4bb2ef2a2bbc154147678aabd14406c8ad8074fc098c93a16f2df3340142de2c3aa973b1a3c062523eca51e95195e3c00ff7c354678929594869e17b8fb3fdd7553d02a1f25b832fff7a915c5aa121289205b1b2b15c422edeb24b2826f65df4d81a3232bf96c5c197299c6fc305dd9a4c05be4fd53ace9774439e0602830364c56489e2b4a5f8273157759969cd938d0ee0c55c4a8892eee2c623fd0e6b37969882e704d402950a1e3b0ac6795454caf80fb74aacdb017082e9b4d05052a44c5e971eabaa39cfc0d074deb662e5066bd70b8cf27332d2998c46f95b105cf26cc1396bf59b78a3701f122d33a30a40e62af7bc51437ff00d75e0481b01b85528bdf49210522f3b01a24a47535e0786f686fe0cef0a0c10dba804d897f4939f4a1c2888cfd5f99338ab1f0926d2bc474aebc12da6f84c49edef6138d713002f6161657de04429fc8e921292f65f1d5f3400173d73d611e9c1ac6fcfd270b7627f90000f9fb61596c985e64aa6a0183eeb7c0f7344a04c001110b22e022fabc5aba3edfcaae7aef2119d2f0131b0fd70807de8711989481ab482c16e289cd4c2354777000f808da821061021506b4e92b1515af0154af698b106b7f7d02c5adc2ec557791e8988c15fc4be0a51ad9de5b2c38730c09a2dcac3862c446bea45b503210a54d4e9817a2604faafe92cff0a33114020e7bcc8c6aa748c7b8e9950f21cd9859461a7581cf07078833d9e18ada3661df4654aa9eefd59ef99cd8564136f6f1ff48419a4c2b9ad313104653b3d4832dd0f94fe1211314865ba9694e12c0cc94b1d2205351c3e79da61f15b498e44c2041c470fc55d35f66006eeb923fb90a7b8ad0f1321563f37a1d2a3696d53a402671ca50dc4914d0cc6a32715f5a8b63f68db51c114b56d543c0f5458b311edc0fdf9e0d7d71b4d522d0e661e3885196aae02b180c64e63331b35158e5e360936a175012742ad2b88201dab4b4f645e4504c46956d32336af9b3a9e11f3ca15d070ffde2d55f087087337e8bb42c6fc597196be1a9fd21d6615287010b1a211bfd7d2afdc0af2ea0b6942cff2132095d6e9db15e11aee91f05ccd7511ecde462b34d3a26d67f6dcf9a2f1a99cde008d13d632bdbd0d6af1e175438082075e1f21217dbde1191349ef651655f4a2c6375b7e249fbe032b7081cffb2acc7f6c5aec3d8b00b97682b8c20c547c0dbc0464f3cd437c960460d6fe2d03e689fc3363b97b600ce185a277953eb7c809d35015ce854779e85614a6ba15238181c0e970294ae700bbc8e2385cfee8aa7bcbf1346eb3facfd0fdf73ebbad77f8c565e8afcbed5a33468993fdd1a1f4985747a51141028598459d87ec4b953b1e166ada21515cb3e07e1e62dbf4e2df06cb29b61f03104af823f00cb01ea2de7033c4c11f2a98001ffc22d986b0940814d55d93404c945bd116337270396f3723c767ce33813d6fdd793534528ccb4ae104e014db8cd7ad0edd8dbe1c20387772973b6c2ac654a4f4cfa1b1412d0b278a55751a7fb9d628b357d5524a957454f9d58706a1c6aa33fb970174456013a143ba3dabd72c9ae13fd64afa6dfac7eb862eb66e4bea6f31109463351107d7120eef99fb054cb0506afb4c2e28f366688a0e8b82ad329583622531b2862e10f3891dedfb50608036076af302288ba034789a0992b0aeff4af4515f52390d33af369ad0d91fc6110b825b3c53839213ac454d97aa4261e7c5b3454edebca5bc082dc111c0888ea816fb9585c79ba2d675a6ab4246064507b9feadb52a43e3e41381a0114746572898a58f7cef60f3e10e971ae7f9db798502340d6e4e0f62cc5bace96a3a0000e9a35405c8d128e4c132c8e5e7df082fa1148ae267608c2b87f4e4e46ffe5567dbd0d05014c638031e79c35c666a82d645d913cb866eeeb82c0d7522d4b44cbc8926793fe30dd2c751bbb3fc1a6a05726102b07b6002680a3f626b1fd2e16d8282b5f6dee81c7e0412f98cd856df53facc49ec286707ad8f4a83b288df6e313da163a0b81b31cc52fd896ed221e95e84b2ed5e936500543d10ac823161f5ea0d3e1e565de9e1694f6f3a11ff94f552d547eb67a1aa98256fc6fd4adb2872f0317480f50d7501fed312a3c26cb0387b0a47881d643718b80e482f1b5a946c13a5cab8e5f01add6584d02b59c61fe9fc41ec06da668c309d210d5e4d65d21960b78fe304e3c3efe14cca44077541916a8ba1e9d577a3347aa645d56252359ad52dbe233b52e37cc485be9f478900671599ec28b607a14a14d82d0b1cebd8b7c329fc30a056356ed90cbadb7ebf19f9ef831680fea1b41e14d15d6de416b9c7fa85b30bf1217cc13a8cb4d7736c3c1132e7712429a7060ca934890d151732f2e5c2de6b7a78cd4bd33bfee91d05772be0e29fbfde0843cabb5156fc21f51180eb3c482a9ec42e85b6d3b5a2bb4abfb63dc0fcaf902ab13bcf9d7c0fb9a0f2e7e61561a62c59e56e0e28f309875857f418dad4fc67a9c6bc7da3b894ceec402ac69f15c52657448c314cb2d3aa48677a744c037872a74deaed3244d72617a9c05368e8a13d1a78a9c427577bb8563166761a88d6af90f2a41cc6c4c40ff218e5489b4c678262c3df8e214fe9a89d03e7f40cabfbfcf116ff235e2a3c3d3795a197f52f8537d12491e0c420345d25e04add32ed440d3426264395782179a929a482f50d15177c9719e3f86b9212787076cbb240e4a04506693c73aa088d5ae0e7a4d8124c0e8c466001f61dde738b80a28f462340d0607eba5c46e6b20c2b8278708e99706bf60f5d323b0e2618cbf163483ea983aaba076071e0ebebd37e16173b16138d21e90eba08a43ccdc6a689685fef627f1ff2f152bbca78a211f802c2ec46b24cbc8ffee871a848226f8d8587c9b5af44aca7f6ce99cec8d96bce6c6e2f225b6d61fa1b031fbaf1c4677f9f514dc9cfaf234bd90e20fb1285895b1d4df267542de5044ce4287c2af8dd9c3d1f188127e292c757a578b19b20281f50f4f87e5453b3d0d7476a180c24f7b0d9d986a7ec1537c4db806de5f25b628e829222ea52e7763b0fe12a9cafa03c821a178a9e0a2e5c0d73a8ef5a87ee9f04eabbf64a8a86ffe9a5865e53d37f37cfcacaafe2c2cd461c0e983e8166b26cc8cc36a6304fb4e8f9685bd75b433de5ce23ce2f6a4ec20722be2a0e1d12ba801b957b5f67debb6d7768a2acca295b80c0dfb38612271c658b4fbedb58a1c6a50c2b9862533a0363ee94f77179f1cee6657704a4691bdd2259ca2dfc4688f2a1402eb807cbb8b12075f3077eb54b5a810378f8e1e1478d0157f4cefcb8c0fad8857355029e2ec62a210d7015b25a1acf8fb84f288361ecb1484254f9036bc0b3102c5465d45a16ca1bea6c6d9977969a047d926ca903b4b6c4fd8f8f52809ca3a3f82beaaadd02699ed2106d48c945f10bb4614a3c8a4bda4c38331d1e47c418f74c133959cc18c6318c360bc5f60f23ac280d21de2e58a7e7b3f42ccc72e01f786fed75f887b161525202ebb5481291a45bd74592cc99a0d1fd33300b9c699204ee2521aacc7f8877e8f42311a8d103ca47262573803eedca89c392351f24c4bab1fb2dce0defa4193e6c992648ac1e728d085d6e66f19b7b71f9f5fa40a503a9ad0dec29035a99868457205e515107a9e5caa1a5bc7a8f2e5e8a57614fc56e1f6a840dc0e699b4de01a32fb0f88b3b3d57001cc422f57806c0e245b49e297e031a3d0c260397b66deff559c28798dcacc050f66361addc7bef265bca2da54c52063408cd070f08a29fd6ce288d8c8c8ce8fb7382d5da0b661f5f94b21a50bb5c9d2be5f2648abae64b769e3767360541ddf33d107e7efafdd420f880b80f525103db67c057be0d14a78ea7e6cb55dbff6973519719f2f7e3944d2d7f06d11c88afccd9fd74959d6eb2d78c35b896ffd89b4cd1973e415a94fe8e8d27ef9db299af8cd3e5b2d9db4f9d29f3d98236bddb4f202a5e7d84a7b639f293862f652865f8539cfa886f1b4d976cc43f52688f12b6b3fd364e9abdfda6c446229544274c53d663ee4f67c053dbcf1925e80a4b15fded25d577fb23601e2474184c4871c59d734e8fa635065c6d271292a529459f9117cf39e794e27d29d1206b122719cc797f5e2c25e6380ee73a591b11ba4c577a54628b5e8adc6ff87aadb5d65a2ba7b156e27aadb5d65a2ba594524a691d43809b1065baaa4f29a5dbb63d0a6ce4d8be0912d83f51d56cc2478e4d37b13dadef5a529500043092404a1f640fa24d4bd983fc59459963db61d259710919c37943133e405c8003117290807e757777777777afb53e0a6ce4a8ba09aa8920d2269d761fbe04766f0add694bacb2a5775e279dd20e749df431e74a0f0fce4d0c4bf6a3e36ad1d084603d4070646280642f9d9a568de60353910feb018203247be9d09a1acd07160488cbcc80765e43b11a5a45d2904ee98bea5e51c31b7452569b5a33548bf9e404a133f447d08c678735f41373e54c9a2d678492e80902a5cbb13c6d133ae99d74772983ae9b2e250d393d9c3ef78a2cbfa33265e7bd225b946d3a383f20e89e2faba441c110456dd3ffb9670dab7407c2dd81d09656d801cd1e5f07339f93524a3becf8d613aaf4f6df291974a47006182e797225a79455573a67139d44b942a7a315e6c14a30140c6506a6e53a6463defed832345b7e0d72185d7135ae0683c160dc908846f4c3471413e386442cd429e67392d527f238612bae4655fe52c4e2388eabd15595fdffe9b4b99a57ae5681ea50a5056da74053cc58e4aa5f7bfc4cabcc53ce93c7cad5b6bfad19ab6cfb9c3bbb25fb1f248b6f972cee2359bce6e5403240609426595cb2b864713bf3a2dad6e5299fa9964ab6717aa1780a65bc292b6546431eb34fcfbf75cd7894939d29d91957398adfa837345b5ae126668f5ccd0a3e88edefe63637faf30b42eeac3a43aed4e5b4a0148d3831676de0d9520a5b0a6943cceec236e419e4a9191479c6f658f7e88d796a6fbc312b2ca32aaff6c6ccd98da154b6fdad9e42596428f20ff795fbcadd9f79fe9ea9a9f29a41aef2c73608f7e9b07bced690e7d71fe9ce6c656de88ee7c206b136945a1c0f73f3ce3ae9d76d6d90205fd99cd59ce9b09bd33688903c35edf9d106a13c9ef21984d2207467b4fd48775c95091b86fb7a77705f3971949ef23c7535be3367e8f215327702560c13ddd3ee69f7f47e77823c9f5eafe3b69f3f62e5f1f735f99efe8ed4bb776f77efa7aae3bff7512e7d913651edfb764e51fd5ee877a207c1dc223d8308aa25555991b71be05c29a5acb7459b31d259f5471644298fd4a1f267eb66d3b79152fb0f4b9dc667ea4b1a69fd478da6b4297518783fbd9fdecfdfbe8bc9d253f31b491515d9be64228fbf3713d004d43b527b76ffdb6f5bd6534c558947284f715fb747494996ee3739dfb5896a6f6fbf21e67b262ff33d6dd2e91da9dd7da7532098bb138944a8acc8f2e584f91c90cdfdef2ec463c91e477bca9506a80a5feeef9dd3bfa9018a3137c5f9b95c6e6eaf81f9c3575a1b902b1b54851f633c4a366cccc4e6221262063c858ff0147e49475e69e3b7f86d60b7a1e337f7dcfd51ce342059e8c6d509ef4b8e1f7f4a45a36c3c63e369f3864ea28c13b482fd6dfb5cecd78a31c694e2fa9bdc94528d040d42c5460d241c63ac9f0e81b5132620427dd1dbd862904795ed239652b5abd440dd15967f859f3883cb0c71a8220a1be4a00184274c50835a0dda40cbe1af624345d2f094a0e25c0ca6538fcefb22ceabf23d5145e27091a86235112917648c9f7e4e9cb04c595bdd5224580164fb4fff4da7cb3ceccf5977f80e11ecf91ba554a678ccf7ff5ea69dbe0293176b7596eff9d856a324be184f3cefbdf75a91bd17dfe771f1b5fb3ee57175a52fe98bc98bc98eca62edba6565b0389750965e4a29a594524a29a5d7524aadb5b64afadb532cdf6eff71049bee7b37d156ff4a675d77f2159317ff8dbe2df95f2b6af6fdfa76671edbbbce263b7ccbafdbde9fdf0e11ecfa57ee39c12a695899b27aac92477daa4fced375965a5b7760f98b8a3158a12a74602b9da215ac49e956b7cd0e0036ab800511f286a01921704881102b64489badb358e90d405e2b152b8bb5e7f752594c6cfae308767daa79506df2529ffe10284fcda76ae8c2f7f2e9515223114bd5c4d97f7e0d01de5d084e99f2de8e6077563aa520a871b88fdd7df79d48f4fd89a04cd5277560bf6c9be20fecf4f8814d52073d49a3d2fd612c53d5ebb1bdca1a82aa25e87509b552d4a954e9a44e88821382ac200a55c8e7c3662bada162e38657f2445b0aa982662bc20a9c10e48bc38b064d610b59a8e2892a0cdde08a23a220d403254431030627d49057c084d88615f4c07230e0a15c1364ac1456e004e7c31652450afc25078727a278c2cc0d40f8c20c0f0fdc1094022ccc64c1832cb0b0a9373ab08196a1d6c052d1d5f001ab0aaec0d5544cc51aaa102ab2286d29848a315001863d9e609d140010430e5a7110c2908fcc909f2139d8810f0e6ee0832b365ab3b193e52ea55c354bb2868d1b282216fc10010b6c80830d4f8e31d32253b47c8022085ee0420f1b803003191bdc5208d0149e0ddea33fbd128fe9a1bc92974f25af943de5d5cb35973caa7d6ca76fc0abed2fa61b2c4f8fd2fa3665ad88b26247931ddb6e931d73d77fb1f5ae543cd9f50933c784207ab9b2861b6cb206a5a7df95440f84a41f8923e2dcdf07c33de61e8b1b634ee4c13d7e9238729fbffb76848f5ff4bd808fb3806d89862d851001da9dca93be62e2491af3e5962f74e32454e89ebb7ee77522cef32ce8987370a4c7126c4e634f811ddd6cc59e02bf7e060235fec47a63d3d9ae1c1ebd1ad949539cb9e2c746d952481a66b12db7dc767274c23ac93d91473c775c355f723c9987ffa8c890c72a622177d6c39eaf3aff2a8e49a4b6a5d87f1379d4efe12ba308e52b3c44bfbd64c14f5f46c7d9bf5664edb593f3cd4e69c198ca1ada9e56cf5e79ebdcf55a5badadaa19962cf7fdad64c1eff5b71fbd4ab7a7db53913e16e5a69b288260ee1933e84bb9e33715913ca568427f4c6d4b75aac70c4963ced99dd2fd5a708a2b6ab6dcd5576aadb57ec557a4efa27e2e96ce19ddf5b353d1d796099b29fef5a59e3f9eca42421336ad9eb02c9684f26ffad933cd9bf5acc64e1f5379652754836af55adfd24929a54f7350f9d12b80ecedab8e2cce28cb483715a2aaba69d04cf1076184447acc41b365ce668a3fa783b6efee13af4c89de133b99128552259a2d73e66d9f4bf809e163e3af769fc5b93f710a899306bb53e877f75368e6e0b4c8abddd70f88df9d16c93b1d1c7d6ceb49fc24e1ed0877d5af4da938d67189ede39b1f17b830fc54a4949be7e7b6fccfc5f49fee48e2c84f99111c4e12e72ed5adfbbecfc544ee292359e6b6530572857b92c4f8ad8cae50f48451957daa6a4155a797bef2917c37855eccf93d8b72bbbf8ba207268dc445b9391808e3fcc67c94f10c9ab449bb3ffafa70afeb33e2deddb5733e6973e79f41599cb4fce30cda5668d23a92163fe2d3891b11e9abe49c47c4dcc247fcc568846b713a9dfee4dc8f90bc2df192934739f722d1f6e5bd38cd83f4fe237a243df71de805b9ccf9cd6f8a3368d2388ee3384e85e4df7123239fc5d1db233f47de4518cf891de967d0a49160e8a01f8496f488abdef478a22e9e937bd4e2678f5636379ed63b41959c449338f6b09bf42ebea32e9e7e2e262f3f92894884101f02c29138fa1171f4b147de3f9791cf39bf0eb9f3fbd82271fc50fcc42c8ab8971f69f49fe8639374a5c14c19fd8cb4e782c3bfefdb3e9238aa6cd277178b4cca5963d197b990bf21341a6d87ff21e9e79611a8013d0be4a9cf45a1688a8ca0dee7223282ba25fdc8923ed43246faa963a4cfda66d2cbf74a7f1a7553fc5df238bc085fb12fa51022b1881b872cfa51f610a18bb0324badcc7223a3efa6fd74d8ef48e29c69d1e27329a1243bbf1412d4b3e5f742921f87ef50eca6777a1c6e2291b028faae54c2a5984d267cfb9b25d516242dbc162d1e977cecfa2dbe5289387d62245178496fe5a3bc25d1238ef19e1eb349d74c8f82710c6d32fdc8a7e33718866f2a8523a1e843b1fbbc6dd157b9527ad1634fae905ef459b290bce8479f8bc8db212693fe7efe2efc5c3249bf7826b960d626d724ff7865df0be9af36c91affd017c755a6b0c35ec99561d19742d3bda50f4da148fa6b0a4b3788166f8eabc2eaa96679ac32f5c77d1ea5cf6fbfca4816efed5715481692b7df91881e08f7df2f79f9492591f4243bf2931e877b250be4a99915f2947d936869b6a46d90a7ac6f91be37d4259d151c7d6c2b233de947bc495a7a0aff20bdcc55f6311ed163ce233d9e4e3fa636f7a2509b709af4e18f5636a2493f1249243d7f78ca7ee8914824128944d21f989fcb94b12257192b93342cc99642827af6e840eafebe1bfbabb1bf19fb7b1454697fa4ffbeef4ede6900b98751c5c9d196e22a255a20195dd999abecdbd6b62a237a4ca5467a7c59d632911eef6c5b20eee32b630a488eafe8788fafcc20977d218af86a6a868f35f60d999df0b91d12498b7ed4cd3689445ae2c7262d3123f2c0123b3f491c296c8ff2e3709107381eb7d1f194bdaf6d85a48167933ee727d1469f479f45b93fd183388fcdcb7ee2051addfcd6494e72d24f699ff452b22491daf9af0e0c49df9dac2f8f8a8acca3c7dc2a2a32e9555364d106d999a75453e429dda905adcc53f6e90784ddd253244dd2a38f4d7afbf47371e1863c639eb23fd213e6298bbf21be97fca49f1f0ce973fe4cf29b59267d7ed1e79503912afb222c72e1863cf2a41f53db021922bf8c3efffc60f28f463fd26ee3a9941dfd85796aee91f69be9dd1ecaf1f80a2d8949dba4e75e9ee2786432e947eea583dbd11c8fa7aac8a32591c42b85a4615f7e5796a9c8a230f4d3fe52564655f6bfef4425fb1bed8fdbdff753cecfa5e7c27ce549a2f7232e8ebcfc488fc32f8faf640e2fbf7d0be42ba3b7ef228a4c05d66bf7f0ddfd954916dfd647c68378ca7e32fb36d423ce7a4c89f4f8243d9eb27dd51af2e8f35f99af98de6ea50dbf83266d65b6a4392179a479907e947fb4b2d11381c819742040b67d4f1cafceb6f6af28936debdc8be3e160272e840a3ab9bb704f1494999234a60c5d763041f61fb10e2969ccefea7409eba697f3697ce9bbdbdd87e0a5fb2e0ba92feceebbee71d709f56c19b4a5102c64bb9b1fd6638d3d6b583add4abb81262f1bd6e312fb7a7854128245cf1e6feceeeddfe856ba0fe2445755aae6775ea73b3da23af7ee15793cedeefde4a90eff14fd287982902c74772f84afd015d4a9eb5ea2741b1c94a02b9237bdf792ae5a889e449c5245bf8528a5ca24be5adbcf1a9d9dedede71fd9e654a287a7b6efaf38ff1375dc9fb72ad1c357c0df7e4a215799aab63f59c186bdfd9c18dfefee8828ebe9b46d23dfcf5ab789bd128a678b5c6475441a29995a909c4a5c98a04e5ec0e8444cbd6e2a3352be92dfbdef78f8538feb5483baa8ca529f9962696ca650576cb6842f9c631bd3e9e1fc90899134ec8b669a9029e1cbe6bd146cfbe1ebc66eecfef86a860cef7910bd1bcfc6532c57d9dfd9f5f470c258cd0973f83094f161178bc56221ca875dad16c6f8b073b95cae507fd8f9f884e2875d2b0cc3f03fec727242181f8a6ab55a2d7cf16147139e7c288ac562b110f5a188460b4d3e14b95c2e57e8e243918f4f58f2a1a815866178fa50949313927cc8d56ab55ad8e243114d189a3ee462b1582c2c7dc8d168e1c8879ccbe57285a40f399f99627dc2d1875cab15b6c2300cb99c10c762b1182dc4b4307ff84af80af7e12721593a97cb153e11bec22eaab23e21f6095bad10b7c2ef43fa614e580b6b61f8229c1386b7168698260c657c2e610e1e2b26430d394489d562b1586ddb176380ba73f974aeced5b97cb6fd17bfef5a5d4ed7ea5a5dabcbd9f661c0f05ed43a9a5aadd6d16cfb2f4e3a548c168bc568dbfe8989c885c8e5e372b97cb67d54097712b54439a1a82512b540512b67db3721c12db89a8826ac7d4d44b3edbb30dd1217e3685c4cc4c562b46dbf646423712ece8773712ecec5f96cfba7911ab2dde3ede15a5c0ed7e25a5c8bcbd9e1cf5318867a04676662b4582c46dbf673f71c49d7752eec835dd8855d58a4ffb5a0b88573700bb7700be798e8cdf4c6668afd9c69178a1e8e77e3c978319fe8b1240def47ac665b9a0b866d3f6b218fb8866b78c8573bbc17e3a97ae3ab2be3c70a64e7f063cdd1e1c7aab3c1af36be927af06b0e64cba5a1fc5877b0c1af359225c6831707a7cae0d4199cda1af29567836cb93c3c6ea84916fde3ab2b0314448bf98ac805d9726f6ebce0235960b87cb5cd66af19cf0cb641d4834f8f205b6e0c4dc2069f06912c2f1e7c4ae3ab8d8787d6f0d01b1e201b3c79f0690b64cb06043efd21594c1edc70706a3894854365827ce564856cd97676d030932c2e7a7cb5c9f8c8fc006db044886cd96e6e9ed8912c2438beb2b35990990b7cbff195d283ef3c902d5b8cfb6083ef3692a5c583ef31beb23cfe830634bd41b65820a0380c49961199af2c0ece0c470807247d41b6d89d9d30c424cb48c757564666a7070c1ffca904d9626f668abd59c2067fe64896fc609dd980a297c1067fc240b6d8981ebeaa3c433c3f36f81c902d1568ce4896f9e02bf1494816fa3319f844f8aae2509505ff45b6d49d9a6fc8803fc3575546882024cb7d70d461830f82ff8974cb902df50645b2d4071ffb8acec09fb2a5c6cc14fbe0834ff2b980af9dbab14bd341df580efac664e81b43d1b46d3f86f6589286500eff5e9efbf7da1fd1a078effd7b2f09c687daee6cdbeee88577b26d1b6adbb66cd29db60de7ed872e44259bcc76b3c99c36994d66bbd9f64112ae34db6266a5d937db62b6fdaf0536f100813c251e1ea06ddf1bb9249c1d50e3e0e0ec6cfbdd680bad8cbd397d5a5b999b6d5f94ada8ce6c4c9dcdeaccc66cfb9c50ae7bbc3d95078887870768db8e9b60ccb6600cce0e0e0ececef630b5b52a7373b3ed5fb13e768e75ab1ebf201da6b86d17bf999783fc9362e8a37d6690343efd61913dfab3edb33c96cc27fb803cf5cd5c651f646dfb557ba63f5446817cc5ff0baa619a5bbb354c5382431a0f5bbedae10b029a79e29c214e208a9fcee80ad7688d992dddd04cb1a7d9ac1baaa23f16bfa0981a5d7932ddd06cf1989029f6eddb6c5bc3355c63f14ed0c161db17698f8545ae3f86af6da594327c85afdbe32be327bb3e305f113fd907e42bd5dbff823ea18f663775d1d527a32a9fd992678a7d8f4555f63f99cb457d30567df75199dadec52b535b2755dbdbcfdb290ddb6e3f7eb36db7ace3b7bbd83c6f3e6db11fe64104634490c5a2ab4fe62afb18e7ac1a924727547fff3e28e32bd22795d882f47178dd9cb8833863aeb2e30c9578da7c7e9f8cae4aaeb2bf7d324cbd185fd116cb636dfbb44529b5d976f5017db24d7f9f6cdb10c53f6004dbd994b4a8cf6cfb5352fab26efa9b68777d5acceea8376772e549957d4f0865e50cf9b2fe4cd9049223fc2315a24292657a2c5f494969b8ced8a16d1f6ff473c12a9bca5cc4d0509a8cae68cd55d635e76d9ecb749c97ca986e425bfd8dd26476a7a990a7ec27575488aa244b7d5b656c5b59dbfe78b2ad1e4d355321ba7299a9b27f3a653a5542dbbee873a12a1e15df65660b0ba4bf40aec0b73fbaf763b8fa7e24b93fc2d8a3cb5c4965ea3e15bffb9b785da4dbb66defe27e2e147fdb06fb600005c8dafe35b54e204f39cdcad7d0ec098477d553e62958ae349bac523b6542d8a9aebae5ab4fc8a3f115ead1c83c9aedff09d1d504927d42734e4a29958fe5ea4a157d2953b445ca14fa72d6cdc3be9a405b86ab3f0659170b3270823f3f0bd996506c298448cc6611f1d979e3f0c1f041307cccb1c207b9c7e1639b1393d147f783a73ceff3bcd42aff49d25545f9c2ede334ce3d458fb979bc4099b870311a9d484840fca3cee7488f73dbc0cf71a33f7d2e23ee39d28fa3e7bec3a27d6e64c2719fda233fea5137b6a19a22dfa72fbd1de1c6df71e2b6bb18e00445df330cc31b8217bca0078ae38c5d5176f51cd75aeb0c7cd3e7020261f70d4fd11fdd67530f63fe2359f2d3eda11e02fc30d426398723df0bf859db0f47ce61bec2037cfadde8db31c32142b9f3f7c23d06c1c7bfc1ef72ad2fe9f79dcea3fc21e638f0479f71f860f86016c110673087a3cf8be18122f654178abe41719cdf0e70e39770e32ab7efb10499eefcddf7b998ec486dd086a7c0ef429148be26f96ffed40643ab6d4c9a3f4fd2a83736e9e9ffe02b3c463724cbc8d327806409458a142952a4489122458a142952a4489122458a14e9428af2f7af7e51fcff3f397914ea4b4afe747a93e94ba51f8d3ee7ffbef7bce7b8c7f86b7d6bff8482ea019d99b2e5dacc14c7d766b6dcd74ca12f9bd985235235c56002d36162199e94524ae1cafbf2c7b0d76cc133259423323549a48a5e2230aa0e426a3afb0af98aaf4821b59c4d1ffbdc19fd1be42bf3d236a5bb7ef55545f258833280810b5c0ba02051010a8813d0474820020f8118463cc001270d78510403168052000c2260125032840b217620e0f46212840e204c2e2d7e38408ed226c16180028c0840bad1c29207306263b5f20920003ed4e801f442951400d0d88187199c08ab523ae420a34b65000317b0001215a0c0048e9040042060c4031cd0802218b00005100193802184d8818097207400e1f2c301726c1c062800016eb4b00cc0c66a4500357c08400f2a2900a03183871d54291d72908172efbdf7defb1206e55e198ff23250f4bd3d28e2dcb7075685992c7605193278304c76effd7b77f860aefefbfae25420f50775a7ead49ceac4dd28aafbe2df981819266c5a33271045968e8d8d8d4e4a260312031da20591e1ffe785e83b860e3a3a2f26c4603c4d383971e5d06ad5cc2007c84d0d50281a17a26f183fafd7eb47068d5653c38c0c6b484989908e8d8d8d0e0a0ce60385073990ff44719abf4d6200011224083c2fd7104c261c2d23f3a3899b1a9a14f826f979bd5e3f2210d04c0a564dc80ca3914cc7c6c646e75faf1d27cc1f9f1e28f81e81515363630357101c1c7c5feb454d0e4922343f626411dfe1898f4f2c0b4233d916380e86fa603056d9546ad0e8d9b99182b19eb1f9be912b1b18f8b102b9c08f35c7022ebe178184ebf7af2cb932a2023f56190afc586726e0dee308f7a73f72150209fc488122f0230d82c08f94b6fda94f1a23fc854bae66eb013fd297037ea43c0d709f3945b83fa591abe962c08fb466013fd21b0578c9fbf421c25dfc6ac6607ef45a027ea4ac21dc274d08ef91ab59dbf1a3fb20e047ff79f9d1819c26086f812357ded2f1a30701e24777b978e9dd737e70b972d7017ef41f397e749aeda377f7c1f1e38cc3906421c9e4ca6306f871ce0af0e31422c08fb3e6b41bae23575e6bf971eeb0fc387b06f093d2cc146ac37fe6cc965cd1d6eac769b3f2e3c411807b5fc3694e0f49bf871fe550007e9c31db0795e7c08c1249481651ca8f7206801fa58cc61321573446557406a5cd14faaa1fe5ce0e3fdee0e1c71adb7f865cd11a152208c9d2a57ed461eb7092e17f5b2892657b9f5155f597a7cfc56d500c5cc00245cc960b84841e7d1b518118056213d03d66cb951da147df21a0ab8b25a0471c010d014d335bee8f1134d7e6012d07b41ad0ca992d57a7083dfa6531c0b50097025c3eb3e5d610a147df9b0c269680d810b14d7fd266cb3624841e7dcf1a7dbc438f1801fa45d3ed27089a165d6d363a5a40b45c5a39b365d3f921c745571beb007ac4395cdbe5335bb61a1c7af4ed316a8058016204a0efb4d9626f68daa696d2cc166b69ce6cb12d5e63f1da00bc668306aff48857f49805a06be8d1a965d99a99427f556554459b2d7568a6d0f7c11500570f2e159da2c75306801e338dd80c5da3abfa6acd96fa33536674556d6a664bd59929f479a0b51d744da56ba9d6d6418fa79c831eb30c8da26b36fd0ec380660badc160305a031245511445d145930f468b22caeb47d17a9c5bd4a228c2b438b708f3a1352882807ea2a06d4041b9328cf195c5f8ffff7f51145f14ff258cf81fe3c58f31f7ebff0712439cfbc51aa406a1f2d457750d4114f5fcad5f3faf9f1727272727272727ffffff27251fcc9f9c88ff2ffec9c99f9c9cbc10e73e11eb8fda44bda93595a6a6e0ffda5c9d6b737560a05028140a853a3939f99313d4e9833941a1fe4ffe4f50a847a1504030c4b9514033292aab2664869313190cd1f70bd1777759b7e6b22eebb26e8d8b92929292929212140af5285409c907832a293979d49fa0f43877494949c9cb853877894877a8137e7c7a28145028d9904c261b32399d4ea7d3e9545252f22525a7161f4cc9e984fa924795e871eed3e9743211e73e89d486dac0150487e2a0a464fa76f1fa79bd5e3f2d4c2693c964329d4ea73f9d4ca60fe6643295fce94b4e26d39b4ca6dadca6da10111afa83c614399da68d8e8d8d8d0e49a9542a954a2593c9f42653a9f4c1984aa5d39bfe64d2a5920f893877c9279605a1996c0b26d3f4dd42f4dd6dacad66636dacad86341a8d46a3d1a8542a7da9341af9604aa391e94b6f9a7ba447a3519091e844e87123944ab221994c36349273ce39e7d16834caa40f669473e9475f1ae971e6bcb3e833ae021cb7f196b360349abe49af9fd7ebf5037edff77ddf9773fe9cbfd10793bf6ff4f947f9fbbed92c28668836abc8d346c7c6c64627f43ccff33ceffb3e2f7f309fe7e5ff3e7f7a9cdbd39eb7138a737b3b3c5190c56053f8be97efceb26c8d65599665d91a11c7711cc7719ee7795cf8c1781cf7bdf79fa7c7c971a29983a933732690b903cf930dc964b2a10e638c31c61cc761f083e130f69e7b6f623c8487268b0dad393365dcc071a26fd1ebe7f57afd6cb5d65a6bc5183fc6f5fb6070ad1cd6e3ac75933fb420202f601bbaba33aad2992d1876b271faddb5d65a6b6dadb5da971f4cd5d87a7daca7b5356c0d1b3decf92c1e211f54248de93c78281975671ed60256c1f80583a5f09cd30cfe1f8cd5d84fb28b864d3f6791fde79dc15058b54df1d542913ca6ba19de09068351159517dbc8af13cfc3eeac05db3d5777e62ada220d2053f3318cae0e802d4c0c9b5e21b6698a575dd6b0b1e7ab8adc59a53eedd76d351764cb183e2e3067177c86ac8108112244881021428408112243860c1942a3d168341a8d46a3d16843860c193264884f11a09d3d45b1276950e92915aa427555f124bc196eff5757bff3177b437d37fd0d377b625fc170c83aea18767d2b5be6cf4c999fb7904d80c05b8e3dee9e1aa7a8c98b1e4bfb349fead4be22ca4ffd18d5ea9734aec8b4b24e29893d2a5357a2eab6fd86dad56db5d6befb948901b627ad76bb981375de0786019dd6a844ebd90b8bdeb1a1910040002316000028140e86430291589487b29ae30314000c769e44665a34184aa3148632086210530a18430c01003020002322541a0005f4fea0970ea8de067a9ca8e7037a4fd4e3837a4ed0f3a11e27d2f381de13f57c408f13f57c50ef097a7ea8e751eab999d61a04663651cab5761a911d98f684481d2c18483e2da1e08f722e11e30381dc908b215a380d04fc3c623937dac7d46c317315e8a7bcffe1c0c443f2c16500f1508fa3feb8eb805d950086b853bec7898f427702aca8bbfc5aea5dbf9d7f178b19885000d49669b9522cd37f8e2a33644123265492560f6b552e52df7c2d626c69703556a5e2205098a4548490f8cd22fe46370aad1854a916de56563731707c51b6200db0921d3e7935617f2a1b042b37fbae0ee108cbb8ef5c2bade46292bcfc96655880b5f40781d4be09cde4fe319b1913edfebd337ca8f526ec1437eb94d2fcfde41158f3e1f59df9827faa1257dea31b8d54b9cbc61ffc4a6a6c8bfe9244a5568687732a095dc807e34f3174a51fe45f279148c7f873d954d31476ce79f49a073a08e1941ae4517633e2c0a974b2937740d95447b77198537e5b038cc39df25f19e08b19feb7d4b34c6dfd4a79c794d7e63f89670cd8b6fd93bf8a816f935fd99798596dfd9779c620d7f65f92770c986dfc937e8a81df867f898f28d2f6a98fec2a2299a849d17186ad36db8a509ff47e2ec3e054969b1204ef0413346884fbe10f02d0deaf7bbc7d2f252899465a95a492b4694b09cad390462554927e5a2945d934d2aa2495a44f4b4a519a4eda2a6d7261913556acb2c09a15930b96acb1c49205d65931b9b1c8120b96ac618dac813288aa14de42db4a8f859c2944be8a84f83298ac0c42656ee0d6bed4192418841ad6b1658accaade00124fcaa071f09033ff44ce2c2b72dc667fb11d824ed8994d584c1ee56ee442432579dc7b12b3af9b013607dbd99ef4a0587824f10cbcc6b6d14c8aed1c38ebafe33e3e0d9c01a475de50265706c9250171c7a8514ed0bb0b376729850ab9108e00b345a15bf0dea4f9adade404fd2d681d07c617e01f44aef60963a771f409b1df58127250dbb5987f6972c551c9c45706f58c0a7c39c728932b77e2f62cd5d03a2ec4219813887ce50940f19541d92411772c3ae524fd1bbaa506cc053431fac528e49648293de142ca98815d3289ee0aaa8e037108e70722577d00b93bc56530ba49b1ae925d66d93521379499908bc523352821bf580677181168490751932b37e5e6591aa13a4e94279017984cf521c0eb97c1394910e1340a8549ba0bfa951acc85856c0b1321478b47360d7d1e0ebbd404ba3b281d07c021c81f88acf20124771a97c1689362d3869b90323474d431e72229547fd866659090a3cb23ab8d29689d5d7202dd1d341d07e210e40f4656fb00534f3050048ebb319b9d2609b916c62c5fa5167c49a65b06b5b700ab24283333837d7b964aed305be21f9b62d4385aa1e012f9063411729d798becca9059a86f132e4ddc81ab88f412210f8cac30483546c51ed405fdc07d467a73b98c04ec141b3686b543171e5a08ae1bc9a7a24370479d877e746af16404863692b766979c96f28045ad6d1be5cbdea5f94e825b46f25125b86130e034fa8e07226f4a871db7819a80de47e7f6bc546004a2b938faea638682781bb67ca360b46ac6096de2851e3775bce74bd845636a7c7543501aefe1acc0a5fd4181af4014c689572cf088128871d991d47a85eca07e6583b640b7aeb41c30396d39ae80c464b468e8da5e908224bfab60bab16903bd7ede16a93e7706582b31e0d253b7c946045ebd47fc3bcf02ae6c23dfd4bc4461b24856e830526e3b2d792386f9554b1f289c1811be115fe7d24ce3b5e1feda5a0e4eb93fe3e85d58d13f0823cc27c11276b941cd8817e2d7c4a0920ea61e7b55900dbdd4c45c028c789a45bdb12626999674a526adc79c9e0fca63babcfc85bf01abaa6d6ed1ab927b813d6df3ac7d880600051284e5ea2be68b9a8c4891befdfa7d14a62260d69875f4f9ac304f27b07ba5890500eae10c68e77eb259992a72d81ac7eade64002547db57aaa7c50fa9a80535002802beb5476daa82a69dbd7a5007445e8b0d2bcc2027d193f27227622697c64a38dc7136fcd5552bb0cf8f0f6ffe20077e9a984865ab7a2efc294258402b52c061cc40e0d1747e9021426003b0314714a90f88749e3b5de9a1fba3e2d69dd7c07e2f076b6c6dc18d675d40b6479dc581b3d2c32044824e9859751b8a75413090a3977db56df2232c773157675400a71d0d5a253b35f6b661ae4a2d4505e41c2ebbbbd58b9f2ce43526645ee0764dcb364de36d032530fb5e73062899d6cf0723b303ba2ca71864dbac434264b421a44d89e270d53aacf48f8036fdcdd0e99dc7ee9f55dee8ea36b99045ed2ccb6a27e649b991a09e60b44023c5f6cf5977a58af7c21a0f2f1d6215bc57a375937e77bf56de784fca5fd5b3b9b5aa7cbdc2b9d18df5eb35dfdc97607b9d9ec09ab846a0322d6168cdf8d08fe1a00964d53ae2333163455fb9f976a15d10f1a2e167b512b0187445f0f1aa7b7c132d03ad0cba2f6ed2fe4019190cf75e9045ec7909fa4f822e8dc0cd4911318f0d586f0795d8f92481d7426cc3d6a053d2ed44f479477e4b1a3eccbcdd471679bf95a96c9236d17fc53b3629738404dec089e3d38923b045452b8c98bc113c00c36211fca74889cf82fc8a2482df053ea190ea81c6d889a498bc5f987201eec5d12443252e1b30a3097016f16f7d08c4bd00f516df6fc8cb0066e5ad0c855ce616ad824b6cb579a819c33c2a5d627a2a88807e362f472e736b14a5651aa7c0e728a41bec0099a993f067c04b25c0e78b15a17773d4fd25c299be282003c2a2cae8c97554162f60a16640cbafc3ac631f2bcd5902f9e2215701a2e0eb00e17305a4e84550530e8445605894827e6125c1f141816fa24ba51569a14716b06762be67a14986255f6d62d610158a8445ffa14a8938179d9483aa05e2c8937827e91f94aa0c0e0bd45160f367636d7484b4971decc28b22857333531d9644b299c245991242415988b52f0ac15507be9cb3e8b3ae45011114565d505faca1b258034b35011b7e1df256132c855ed8ebdf441d359478f499331397164502a5641e329d0358d229fe576f027008047b27df1ff46a09ee0b7011e630a548dd9f74fd2c2c4c9b71b90acc951e0c1723ff0642fd637083c54fe93305e2997aec5c20bdac0b0984f06eeb52f8d32e99c3064326868fd597c89c58fa5d5e4dd0b02818e0f5c570eaf0302e45417007e3f411ed2c0489969e49b3863e0e641a8594b1012d751cf7aa3f22208281bd557216c04c932099e1a300ef02f4f88937f584f894fa9afbbb1a6f7521025b06589d6cae40831ccc99c085166962285fe9ab380ad12a27de216ab41c7aa10926a704bd548fdc439218168089a1ef6954a23ea54e3f0f07a6e0b554ff472109522a73b1fc665aa587744ac70109094ca5904cddde1834f0e59ca34ff52602a760b07682c2f45be6f2ebdc0153f15d74bb9862d9e42538369603a50a99d5df2054ee022e53a6846d787d238017470c6d7a62600084dda6f6cb26c648816470890098508df2646e98905a77b85170cc04e29c3638e84c2da9ae42d3d2d827446f66bd9a012fa3201a6ca0e54e477e754f06c900b077557b13f415de9cdfb843ba18f56e0000be051793f7f084112c923ffd42aa60260e1b8ce1605d0cff41d4cc2350f551a38d8cca0e33d0b28ee2bffa36008620d85642fe41b8ca60dba10eba6042f9ad4ce74879e5c2c84d6e8969c5d368ba0016df825f5b0ca0e50fdf8637a222b4cffe6308f08fa1213eb0b5e169595cc89b9cfc81b080c16e32f1819ed265c80257dcf7954311e2e90774a03663397bb9b30b67b10012a84ad4ca21612b651a114f3a63a98be931ae47be0c8e7877c96db326c49ae8c941b1079e9400e124a68c4e45b15a1a61c687837495e92801177a018ea0d05c2de9bd36d82526df041f4e88e62736b8d1d0a075dc6a9bb05a9057b4e30064b03f7296595b2cc94c734f32754c5393de437ac989272aeb32a39befe415094a3921e58a8d52bc8ede725f6ba28434579ae164309c20aaa2d9883cabeca66b30c34fcb4952387576458145b19ed3085f8d901a921bd40c21e660066d556a8c4c57c602443c95cb52bb8d81c80dfbffa03cf08f1c378a3f524c5e939e47d7aeb210826e4ce399d891c906158bc62218456f1c5bc8154b495ccaed9617a25b0f226d2094328f4a3608028067cab832cb281fe9181c28e47e922a703510528d9a0433a07b75e75e0e31277c95df92cc12815273001dcf2c805346f298f996f2761c19440a01f9d4ab28a2e821b78021776a58501b5618b9c3b630861636c9651eee838462f8afc67cb76ce5929339110f991e5806bd37ac1a8528f92460f5787559b006379ae256e73cd96983059c8fe76ba4c81ceec05ab3bafa7b996a13202a7bfd01487f989d0b63efe38d6e7a9984ea46c614c6468a2d8819195b20363276c1d88898c9d8207483a31b1ddf28921b8ed3ae606c44ccc2d84831053122630ac446c62e181b113b191b4237707423e21bd2b831c1de0a5caf69bafc4fccfd4d91d8613f61e4b14b95a756b109536af81523063eb75cd80766c20521a1bc5f8974b316c399dba2f9e35f6e78177b3c8af2c0731b7a2b153847832f1bf446fe539ed201a5019cc0f4ce01a80772e9c06986490257b0210361d06d2fcecc88bd2faaf6cd78dd363a5cacd3a23a9bb139e2c728853f4eb359e869ed64022e3dc2597ef761f128cfe9bb77dacb030144f0e738dc627dcfb084e032b12f9807c3896f2292da38dc93536d6118b2b4da74fe44d46d9df722870bb1be45bf860e54ef04656b890074c06166b0d9852ea382c2a4a5ef3db67409b3c238f7959e17612f64326208be81ddb9be2c93a6087b29b85b2d202cf8c5fb721a52ea0b9fe14a68d66796860215187db685f8fa2e54e5586c300fb1ef24b796ef546e6e89928dca3d355a2eabc12320887fef64c6037b6ad61c8e47edbf1eaa8bfd6611c6f976d860f0a356371e6ae2aa2fbb1212f5a011033352b9ac7ba0791ca394b952a0b20dca8d36db287d36300cbba20e449ce9d1d2520a01a0d151c17dc0ea225019b82e35fd99febdf253f64e4c6c074962b4da8bd87e123ff14d4dc87fe9857d7e5f05ad18ccf0d31e94ae8a3e55426d0a4ba7b0c613c07d108c0184a31999eb33e11dc120c934b053942fc622252e1aafbe81c15de0ba99ecd265175dbbe9b2cb2e5d76d58dbbd4d08ef25a1bf986d5b871c61e337e2ce3c71c63ec783a9ed9783ace31e5c2844623a11ccdad53a579d0f5bc5a0c13c03167680118e935881bb86ff7f9491015e85e27a8f550b9bcea5d3df7c424a2698db71e1bcd78f6b25e7660e1fb5cf6696756065e9870628fd0f391acbb3d1681708f2ae90076304448fd8134f33959bc6083fc75935bdb07353de717c85a5636cf19deed64258c2ae5d91a60e504b034ade8d668dc0cb28c82d0cb66520a6abf9adb5255907394c16c5f229a3cfd4bd8be009aedc3ad8b4fa69750a668004a683d50a75c2b6bd54438e64c30682eced33589f56b8cb660d75c68a20863d74c4d974dca4eb4a723808b4939ae9000030c187d426773a2a3c09fccda109a0535905cfb0a32954befd088807251958a62ab4e76f77f39975eb33725fae32f1234b3490ca632a25f3ce927fde89f698a12a66cdabfbab3085302fd579efc137ee4cf7b4482299be65f9ce469cb702b6c26914f70b8efe1ffac2e274c15f3f7eddf11a66cf37fb1f77f52e5f7f70f61ca9e7951b9b9a86d3d4c0992dff4e14fffb1bf0c1217a6eccc7f91b002a604c96f7ce0a7fec85fc4c70f5376e6bfa46965a1b1a2a83bc2e7301edf2402f8230a98b269ffe2c7224c09b4bf78d24ffc913f6b354959cbb861bb3715a632c96ff8e0a7ffd15fc489075376f67fa958075382e4b77cf8d37ff42f62c414a6ecec5c42b3075342d9f29b7e0cc3d4621785c5da51277ac2542e4857eac321839d7a09502e215121dd38e36b1dd6970dc25f42341e30b5acda55225afd8ed8106798fa40002702830f960a7bc054d6637f660b3e4c7d78f142bcc72562f64b18e0c8b8a6f330555e3ba7c1d4cf213eadd5886e717849b7bc78ef350266090f2de6ad39a11ea65666d741a117855243101be632aef96996b2c558ba352596d21c8513bf295ce977c8b5b7bf00fe53dd9585d9f728cee298329819807c0f1559373b0b6627b739d9eafc81d1614f27f8d00976474c1aaa9fcf69872c0642e0a91876033f5a73ec2f1e93d36df9566d292cfe66b56f831430d8af2a07b5cc4cccf8188c86725fe9096cae33461cae87b2adeb8d4812cdad45d2840b79293ee146910a52d49004b8ec89c594e2387d22e0b73891dcb4ac35d254553b8a5528ae0f10e2ce5434570a36150f6b688ace0e7181db85e6c3acebeaf5d902b3e2372b3df217e8479dbadc22cda6e514c75597e79690347c3e910f0aac628aca9472d662e496d8e7eb4120b1426bde1d637f28a2bc47f76fac9a6455cfb02aada7104027827a5262670059c88785973e84409f81226904218082384f2e829ac39ee3592f29148a6960c3427763bf19eb844a23070666e63a165b46585ffe607f46a106d6ce4b3fdca7d33cd25292088316349f80bc1301a7f0244e84bc8a568115ee367f372b40aa5305a4f8571132b5f84390e11fe96278630e0158c09e976db58a867ba8c9be41971415ff6e8114b4c1c80c69ab45a1d4d1eed3330b4d066e1555996cfe7290802334fc177ca06410887e75530a716db0fe92f61709208edfef28280f5bf32442312e580d8cdfe27d4b23b26b0f2d1636f63973a82418fc0e5a1208dedb7821fffbefe67a4953c9f9a749277f80d98ec180f8d18afeee4f08c5f560eb1a3feb1bc8d93f9fee043b85728e974cd19cbda732cec9f16f0ab6af056489c76ac90fc33d4918ea4e62fb6da3e149cb74819b1bf7fe1c9a76e479886b5ab12630c8a01d04bc4b10b9cf2063355488227a9b14212b00419ab0930f48099a26990968c97e80c0e41bab7b2444a0680bc2a5f03f10adf7e88e1991800e64321a6af489f2a7ca749615a67011c8fa73462f5044d4afa8889e1970ce1a080f3fe48f1b587744f4547acf2746da045f86a59e8f30fd1ec5f05bf659f3d0fb63c28d0e679089d33f3a92d34fa6266a17aefd8743626d9315370a1528e29e9598ed4a44207718a0cf6f26b6140ca47164cf862505d94b0518177c854e9745045b940531f81f4d317a706521c023a9fe61b36ce87bc015d7c18a2b586f2200a9190ee277b25be85b24c705f3141e3cc99bdd6546049952e4f9583cdf21e60f410342debf5426975cffe3c39aad255d8cecd6f2ba61d8127ea09f1147ae418c51f86c9eb469c5488970bb43335594e0938820f2eb4d5a73c0fc4badd92fcb40eaa657d5780a17b2d6274930dc342a98c9542349ebda10a36ef0b6157869e468e40ac5c5a8d942852d548910d48f657385234181dd8b88b344e391d3e34f737afa91563ccf91c776a3634262642ce737861b32386982e61aaea1056f88e6cc76433d07cc5c8f9a1b1434c4e0052eb37938ba5d4c867045200931f6014cbdd561b2c4a0ad0f23ce6570d9feed870bbf25a3d181f09d69043125dcb7c9a69c94b6b74d43adce1d48d276cc8891d024919f081aa33279a0efb033323d58ede85968985135024b8b895c57570c49691e06e64d70fc4a423eeba96ffefba7381cce9db1b072f1385e638d57933feae782bc210b75b76ba8ac291e15e33a3994b5de3b478a0141989e2d35754bb0c24b3ff2d2d9486e6692391e6000df476e6ed2f5baa20b214115b2b883add411a8aeef7cca07e82290febd170a977a9e01f1d642fdf22596924162c6ef9cc3a043d43462648b1ee13c272738aea5fa0d453a5c09cef8b8c677ac9d2d9097fe592e408742f1772102d2956bc1548faa6ec0316900a5fd929882095e07f0a498eaa30a83c3f77f8941628cdc26140e500896cb41dbfe85a023daf5ad8e0316df9ccb70b3a1915cf731860536ca5b1e49c1d93dc281b17097afe87ffc8028d7a63a8be813f9665bb45e6c246d362dfdc67c06836b1cc5b791ab7b930c27b67ca079632e94fb69dc86e46c04731dda40e8f5ea859c3e334c58fd20b304632da58abd6682f9e7a0e3d99f0f2659f76a465d98ed30966134cc70065c62b16beb0e9c01829cefc8901f34b4b40f98176d063be08ed0f55a69fd39fa84f271680c3d722da3b57f9339370806442f9bd8adc2494f8df73c91eaebca9a14fce8a13b727da5b8f42f9109c95923699ff931d9486a999f7725422755b04e715a28d8673c26d42c3528c535f9609cb46a82d082fbcc93bb9f2a8bf9c4e3b3317d8e46e04acce846e4be815f5769d88ccdc4c18658dcd519f4ed0d3be28791a93281d308f156b50aeca73134c8b2d9c4e5f759730e5d3716e25c7b343ce028d5c1e2b37e3dd11e82aa17c7a10ceb03113efc854e6e0c1216a4d01ba21093eae7123eeda42cd790d0b446a4c123018d09f11e60ddeaa85223a59d58c50ee78b2862f2c65fbe516873d32649332ecc40eaed9c2f0f69d66de8a5be847d53f1a2cd33c34fd97018a8385cae10fd9006e166c98b1bc820f30629e236b8f014475ee9c1a2b6f8a19028cdb83a4b067e35c201dfc9fcd07b8308ab2afbd77cd7ebfb65306e084af515dd3c76484c10cd5a085d3cd0b56424e45c41714fd483579aebb441d08c733f45009f4867e8efb63ac37a5976cc4f4f5d7559233b2abb3b9e884d7047c9404cbb85c974c936e2084d0d44fe809e05180af52bab1dc428af43e44252d373871907258e4f38e548ace53112c9d98abca439ca8370f5ed37d9e0cb97e7318436db60f0faa3d23d0a74b3c1b45fe969eb64c07d18abb786438c87209ad87947fe0142855c9c80a7168ba79bfae1d4dbe3f71e1ba492f1ec4f33e6083f3e6c74b0ad9bc0bc6979e94fa74040093f329709f35838b0dfe88262fc7ae6350ca8f9a73d5fe4c75883aa0aad33d50ed1be6897bd1840f0cdccda00ddec32fc73e44bed61282dd14accf3a984b17257611e497ca921f0d215cd39928cb05de34a4fe1a5f6aa92cde88966d9802d54930b158a96fee900f75de306f4985658b3317b115cad6297c74cd838b58a93715280fa9fbeeaaf05a84d134ff307d1b13f9dd090c3a08615ae29289ecabed8c6fbb57f5bc36e14e3f46ea1156b9da9d6951dae38d65a3f3aea2808ef8677e483a45dfb4b6a165bdca6f92cfc545674a6101879df338c3cf4e779f93679fac4501246314cab89cd1e4032190a24349152b9b2c25487a95236f9a5a73d4790ee5f1123850b37eefb032f7b990a8c352ef1592de857a7ef743f16f6abade82eab248b3fd578bc75647e41446ac41c40ccf46074d10d6a8cf882e3a40b92a0c2e97d2de4229cecb628715f7b9c0a3b24b251c9d357c0216e07e76a1a1d6d1a3e68c891d64f3430f7591c6e9941f09ce589a3745f4a2b20ee7af4039143378124c61d2bf558350e24049e6c65a9e703e54360166298e9b0142506aecf475ac9902f6815e0baf9f7241b514746a1d06ec8145a9f37f64e1e177a5212955fbd7160c6e6ae70f6eb6d113198aebc1cfe1fc65abe593a469aa02c41ba23a8f4ca3fa220411821207cc02734f0ae72ddc2976427780084f6a283286c52ba3eebfe23266bc1bb6930e7df42fd58a1b6f5d24366375e099f178ec3169921e93b1e9e4b051706d0262473955648ade2c00b939106d816bd17a281289dd9f5e952816e80d824de4ba9ab3dce3f7cd88587c492b810dff9b8c5930911c974a88a9237c63a836920def5e045ad19d783cf6f128fd0ea0f6b3da89816406ca1f34930c6e086553727dd6ce917ce7f1d2ef8749f1d0a03b31016a4181981453b0a15785ece90dc8e25b62c5588f9e068a90b1c9aa03f3b5c0dcd7bf67d109b464afd9e5b62928e3b95ec18dfaa412f40b83c25668a7e101b384fe2bad2b0e742aaf5d63450772fa12ad9bff51eeca350c8ecf823135587790215372ba333efa907edeed72c6e5d9061ad14c9bb914d2a8260c1b5d7e1785b7afea5143c4fc6ca3b25258812b102413401fa9d0929c7a6f9ce4c06c8d68856dc76893a5ac743aa645000eedac4daecd658f8c50229c5f8396f7918afe40a49a3ed22598d5ba242494671166f8ad9249c79a783e58e13ad11b0add3568fe6586c13e9b96e2e66aca71e01d16e8bfc423c9aab6b37199af2fca87c1106316cf35d1702b872b0287a6ea80479bcc7560e0a11ba8ed57dc0802273eef743ae92ee5255c98890a551c755b78de932db4640bd08125a5dd0b77880a04b80acbccca4882bf4276b83f2aedbc6c774b014c1b5191d6adf122a2922122431c8eefef545ea6b86420b700aff81b5aa2878429f6e8ea4d94a862c846fcce523ba7e005a0c2888d05184360ed9547e7d4bf7d8e69f0d05667ae84304654b03e765377fec91dc9a41a299f52456bf8a3fed341ac40ecbc8e58f781b8d41bcf126866140ba6a127b3fa0348e858e3b719e06008802ceec47d8df2a3d40b7b80af81a2a60f05da3435e3498bb39852375bb61d1d8f3a1f54b0b67674e8c83f9419a1c75741d87fa602d0e9a8b03a143a4f95c33f07bfbccde80db3d7b5eb2473016c88cc343b44b0cdcf05f63b2648a692b9e7ec518a2eac0cefe9690a78a38f7878e03fd0b6de1702d0eb73fe17bfab01f5c4f2d251cfd62f21729eb76fbf7c3a1fee91f637dc83a03500456bd03c501d73ce56414942fd5ba348de0ac38b9a123eee50c8dc9f8a5596a1d122b62430b0cced7e57548a2e3d64e2a1d68e261a9e118d5ea6a2dcea2e96b6b586763e383e02a64df6ca6526ff76a2101316aa479cbaf4081023e8f1ead510213dba206daa723d0ecc29f6f4a184be0168552ca36cfd46b14637a836c25025122d31a71d25753a6f0180e259dd5ec520d89942962a01d7aa5ee7ffcb784fdafc759740ca1351420f2153d6b3f012381febe6d47f836f97986e017123d31b1e007305a0f3d9b538c609be274bcb5df008cda2cee4c21a8a6f5349ccd8667c9d5964042329bb42622dbe5319704bf1be48db5f8a690b8a6d2bf7cac1a835b4bd578c6addfbe60e89e9ff798bbfc3ee7bd918546937aaa1aa12124d3fe43239cf7941f86add97371f48ddb45eef27b8fb59f21b734301377fea5dec71a71d00b92edc690c3c7da6760fdd0880111a0ce145e75aea84092a3034fb3555d21c1866155779c4da3512d58238e4431193ee762125a0b4cee50eaca5fc298b4bda65d06cd4271dfe35f2cf5ef0b66a1c661c616f868cd06ff6bd0ca9e676b7bc3e5e68d04dd619fe7cff2c6387beee0e0ab5f3f562a29e6a947ebbcc208c95f52dd4d78d0d3f7ed4521dada0030d3aa9b431482d894124087d652e979cef383b64adb377d5a19521bf130dc905e17e281b17d6d7c2ee30bad36913927fea135092d8edcca67a2059ce58898615854224a0d41dd98e576a55b471f258a0d6b05c9ad6b0c240a54f1496d7129b739450b59badec872a1a0b75af868ea792e8d16f66c6c07e33f13fabd34efa8316ae57be3781265115ba0db81deb129a5df871e23ff3ff8983817a3a80abaf26a0dab347cb6c721ae84d53677415eb8f3db5c08254735a17091e9dca1c20bcfbe0a31ad8867229d64f853321f32cab2efdd7b0a2e55e42b1cb8485d569850af92ffd5451dd140082748f0b9a59d130fdd839332384a307668d5a59e4cd1e4fb83f68266d906b770a81fb4a5cfaa1639d3367c7446dd30c803b87fd15f88333586e1541a56e8ddc9ca7051c01c1f053c4a6cb0470f6f0b199b4c1ab248d24691fdaba419cc91dd8987b5e49b03855053468ecfbcf557051b9fc7309dc0bb3c0059098839a4408da505a7f7d1b3baa3e7b29966bfeeb3cf7f5ceb644608d5d3b3229069e43edb59d79a6731f7cb0ba7a78c7fe32fcfc49e42284c40333ae68c8d2f7f877ab47fa7121adac00a013696c10361f774204f785359ebc180094a29dec63c7525e4344f615058af123d0286c76d0360246a9cbb2f138e83eeaa543b45febfed02e3f19cde37c032681720354560ea15c3bde261ddf138a4062c5d9964ec6629e8c6f3c2f8a3a04e66012a85b4fbc924d36fd3bf4479e85e300ba63463cdfc229bd647eb24589481eea25dc96c7fe45eafd622fe8b00398317e7c45e936949f6c0d59629fc557d405e3da778617b3ffc837cdb83c12a52df3f935930401d2f2d191153a06db5415702138fd2169582d763ae356ab7d7ea9b93c71eef30fe2e5d6da100695d96fbab46977b5feaba01dfd225592b6ee2ad2cd07fd7978b34cc977b812380678344df4402c54305dd0d4c7926897c30f3cd0abee79f1a87465c0e4de9f561254e93204df8e6a2d64744be91222e3558beae09f5906365596d65189930700087dba06f8d6b933b9135a7bf26e80d2260ff6d82904cfa7a1559d9835d8b4cd6cc079dfe7dc7ba464dc9ab18df3fcf6143e2103a9bc0770928cba23026be7deae0b6102ad8f2db62390107b027b4ea740e6052fd314c31bfd909756be533f9c9408d3eef99dfaf060a2214d19c25311230ecf78eafe60b061db45162d86624167dfde126639780073a63133573ac9c3977695e63a03c1bdceed27f0a6d56714aa25924a4da3fe9d2a362ed91c669ffcc1202892cde4e50d38a5a6721b9005da4f49b0e6fae7180bad9e80d3f7ffccc16dd9a076caf260109bad41a041f9b6230899ea4f0ce045ff0eb5941ba99cb66860f0ef736d351c197ae27eb11afbbb4f364e89e05af36520f497a3a1ba70070937b22a27d2e77b0000d296ab002a04d5c057580c51559fd094ed0197e5027e9a059fb16f298101da0872f0088a68b31e73f04a1562b51ed4245968e8142a2e1b71f23e4a9bea2656ba23a4c5bf0b7120b36e2aef626e360b5a7841cf3a46853d510a4c36f1f372c7257a8729b9235a8b66c29c091eef315dd87237b8b412d1cfc98243ca568d45b731e7c2ed0b0175ecbc0694ff931eeae2aab364103b7bceed0f0d5944983fef3b4ea79a69161c7d55e59c038eabcb22347f083907e0548cef2b1fc44883df6c39f45957895946b99586094033713987bf66114e685de5581a320bfbf766c4461ce36412ed105cbede013c40545d30ff04d23df66525028218c5f6dd59b0122864a4af0c27286163995676a7db3e05c84776eed05dc0cdd9e86181910fe9e885dc2f9eacab74aef6a17515c01d5aa7ab59d53b4af12b933a26207bd8aeab570c19ef602b13a630c1c26706abcbd68977337a93f7ac5526d4f74f971a2512472dbca6365d2ce3eebfb056aba0d1947688fa12595641656b4c4ccb8f3e837652ba62987d857a0d174a0c406174e11f9017e2e910d3e84b82fa775047f924d0c7609421e0a6949b10e3538c880fbdf8841d94c40680256aa9ead5a53988f0a4faca276c25a50a901818d25a0c50ec4a780b8330a8e61849a93315890a1896ebd2f7d2a0dc7fce0a7cda6fa0ae84f83c7bee7cef28b316ff881d0a980970174b99b521780253462ac2cd87c863c26362b22b07f4e0280db8d5c468a33e2758af2c2d810b3f04ed0b9c7d9da1f0dd6b20c60dd5421026243ac66a483c7c057790c9ad1f25d358de0d56d2e5334cb384928371327f90cc43b374065c38d5c8f9f57dd97d1d91ba31ce9fe356be07c18367cef4de49b68f317fe180ceeb7ea84624aef5c2b50d62c8d78334e652a40055e283ea47405199ae2577eb9ff69641ae51bd7c3826ccd7c27ecef2c1ce98954dc5e88dc5fa5c0bb425e629ca3a6381b26d850067d66cc80f531d9034ef4181e5ecaad8c86da9e37422fa9a6b544644ae4c71b3c2028deb771bcfbcb36cebea1d501e2ec48f62d31136ce2bc1b7718690eb0c647dc2cb857d68bbc33cc06042ff7cf97f5a3a8bd7165079813eefc2a4bbb466173e373139bae1e7db5fea7cbab68db333f533bf2c6071f05f9b0e5e9fb6bc1bc1690f1df9ba627f35c35baaddb16f1f81b3b4f8fb5a177b4acfa1308f28d4fc7e2d7170d3ad77a129994f939430832520ee644c2139803002def3c9c488f15aa27aee329dd54633ddeab14848943518eff26c0db99724a4afd4dc3530f043e766c989cefb0e14604e5708bd1a0972a6347315c88e6aa649d5a1dead00fe3b52aadcee3e4444477286b37220e4227bb691adcc8da208d525351a3b8d9971550a5898533016fcce9557ecb4ffa1e3daa8963a8eb42f4a3ab9c8b40128289fd87df1f16470c053d436fd6b2565b96751b014b0eebc571ea5da873aeb828c03b36f38c2d0f7ab90c6025a00672ddc1316f305e829037378a1cc350c83d19aaecfa14154059b8fe8b42e284c23f84a317a7d224e561c4c37a5e74107faab6595aa858ec75662dcf2878e96cdc320cbc4ca9c44175d6bf2ff7c415258c7a215622012bcd5a11eefe0c403c534cc0da14c4393a9366410220d51644246e615873380dc0d49d61d999762f34f3769fe0a4ddcb1427b8550fd7e2812ec96172b4b9b64d949fc89acdda04cd748de6c09093e40d0c25db9724a8747feeb0af75125dfe46a118b2d5a48cc6be3c805c884eec8ab5f8c9a500440b9642f6fa84bb51cf8684d08298c5c734481fae2e0ac186d208ba7c5f098172792f7e4f42bcba7552702efafe7be305624b03a26eb42c60e8459681b221a48c613cdaa14a837d3916ec88307be8d420d3b191a240a64148dee506e5da482c6b3d3019367ec7d09ccc2a18e539120fa6db5c0af734acc267725ee45e6961af858cc1f099f5ac212a37525d7c28fd33a9a627eb8d321451246dd3c926ce5d7151fdbefdde5a0e5340036999374f2aced26d2101f31322731c1b7358c443627fc473fa8842c40e426a2674a994e74e59797ec7a1dc5a8d868459caa81bf832f5fd91cce4b6976531e7d589823c69a785ab6aab7706ae758bebc8194ca159df7ddf4e146a4a9f0c9f2b0527818b0d07983dd6c896a2f63241c642d472347f2b3dd04b2dad0a284eaf27cd37adb9f9b2f686cbcfb46a51558538702a6737d1ba44f0ee44403838ddb1d1f12fd03e85f04acf1373f90e0d9e2821fd575c1cf1a245912e2b965a07a2743d47b0adbe5a3fe1c05b2aed3398b51813e621110e00dd545640bb222cceb5c83267b1c041b4e94c274c848474a44480ec993d48c08a4fe80f50efc461fb51f1c0f499e34628fa9e2c287e10e6fb05bc54c34122d7cea071a8fe63493c1b34182783e2e2e28b3c2fb65fa3b47071d29e5e093f8f16c09d4543aad09629b052e0ec3534e95992f44b4c08458d197391eb364159a3638ec3352c160c5280305b6fc7bdc4785cf2c8b1749b15068ad430099749b067d52d416917c50b8e090344f2e132901dffc8cec2b0a443955caf93535a25bda2daf4b1b951454f4a7bcbd507a025e3be535f490bb0379e1d1ac26c6b1ed3e273736b56d16eb6aae597747fb56a9dabb3afbc21a23c696bd05c101875dd0fbb876295eb3bbc2b7600e03ad293c76c32d285637eea80804e6b224d663f2f7d651c902d60c8f79320fd0426583986f3517203e4375705d26019e771818cc72bf3453c771bf021b93ebe8e891919b47fb08c3467672dc6663b243fe8469d9d931b7bdd37f71e014884b973b7a72ca1b31ed059f7018fd3112b0a9cdda7d5f3beffdf048891c62a1ef2de81c7ae016f197462033928035919e8b9f3bd957b08548eb22d8b3361954c2f9fa4a6a21ebfe1c6ae9d42b8bd2cab71d061170720525a555d9ae3ea9ecae2b20aa56c5b6b88b5c3d3f50d7b376af36f4e2e7deaabbaacdd55c0c358475f1257d0a605e05f970571ddbb9e6b8b042e5345c26101222711ff4d02b389e9d67ca406f01690ade5e6a8643934ec97c4b9aef00bc1bf1dce831d46a191f665893a2f603258340e884b644eaaa4befebf53bad4b1f7c260d0a51d1a2f9befbf22f2d2fdb990059ec9245709575f26b9fbef3866ca44945b828dd4d1dae91680a5e580619a8a9770dd32805c52c56e3387847f60b8864b64b2ac2030c7eb24d5f840ab64e7773c17274f7d2ecaca91ec3a0536f0a1c2a3dfd97de028b87b301fa440c25951c0a9dd740f74df014b917b08a2c6875b9d6311ec440c1d24882bebc2780f2fd75a8a7cb51811564fc1b02749986ba614e2b3a38686bd925ae0eb47f27a0a4467dc719947a339fcf015f367dc1de52242340f2c295c308a6e1d9ca480d25826f494103474ac112462adb60b94bb81789737668384df8cbf56ff93fb49b0b64c922553dbbf643fee7a3edb8b49e0bd7c9300b9c232135c96c175f8cd985d7d0520b56fb2aa1dac8730ce4075cb94ac36b5dc8b5c63050d18b93dd158dfa0e3832f5854bccd8b9fb00cae082d8f12527848906a067314dfdca45b728e30a7c86852f6e6cc55feec782aea58f432e8ce0949c37e9d90e6fbdddfcce8a7d8099d8488bbcb58e9f804201015bb45f69677d32aa38626bb5d3363eadbe769c24430c2da32b2b6743523b1be20404ac670dd6433b8709d7c29a89cd2369de6528e6c54b82efa90f8ebe8b49a144b854e25f8087363711189b39c67ea1e094ad1ae531e5d76f7c901afdb71d64026ff9b0e70ff270bde66c9569dc1bd228326dcd79dcd5814c2c621f476d59f01e2b7cd359c15a007b1bdb675b8c790c0d31f9d9b93945bbd1d24465a0a9b05d49a68bc3536125d181b212c2e28a861a1161169eef41833e5945b075721087347b756b6c3e4f711d3680100335bf45144d01700fcfe1f6b77f7b98396dea6a355268acbc9ceedd6b3906f88bddc296446d14e3f94f9c47fd6d7828fe387b7a3ced6445ae36e858866e5a345177d2567b50c8089d56ea476b9b78768f4fc68a2c9df42f7c3581ce10a80b63ec6d82ba2b0cbc34b1f594e852a6ae83983328a06245e295b0b8d34250fe54216441201add4a77567ea97ca32c834c2dcd91d11c443f9968604940fc61469806bdd88fb5ab1528d9feac47074334c90fe54b739bb9771053744633d12daa97b27769bf2dbc3617110196d80b1124cffcb4b164264a6783b22446ad31ef8098d92981c54dd9e4254a32210d31770ff49354065243e8431e1a77f595bb29ca8bcf829dff00b39c1250698f2b01de9d9af31ed7f44404b1f21982e06cb30618676e41dda64a90ebb8fca6e43d4b293563cac312cfeffa0b49122b8fba0aaee3247c2b0c7d3db96368a3b2daff04f6888108f3b23b8220f1c824885c94df2ce49a8e93ac0504368164cdbe47a0962326bfa0bea69892f1dbebf74364e7b2a66671d7f31942ce83ca557998f79baa1be5289b65956d6ec47e77f1b48800d24baf89ca4a8a27d0d9d63596f06c20be5b0fe99b1696ed7ff1642dc71717f4acff6dacf13f38031f2c0ac794d00c5708838322ff3667333948513f595dce4f3a6ecb6916d36010c9089cf477c29468d6cfd4ab91a1ac9bf25db50e4962a1991053ae2fc55ca95b04829be0ea125d966449b72a5d5b2efded44e7aca74067c0986dc2f30eb0dfdfc6a8e440411bb7ab8a9c1ebf2ce7ca68da42a1f81e2554e24b9566620bb14c4c9b94b05cdb24945d70a54b959642064dbd17761831b086bf23fd815be16b196383570e71a7f29f5c1f45bc65af43c0030cfb3ef86ce2c693de236aa91eb2c331ceb1fcc5d164e807994e0b7612a4912e3f3c5b93d58610c51674698c7da2c4b677c3d8ea6cd53dae8c3fb6ea731a9f76064e817aadfb724b9dc7940ed6293dde5cb5093c740ac0a8cda1f605944b88f58b3b4a0a0c22c0695221d33b3e3fdaa532129c0ed29fd32b010d23dac4648d524375dfc3e5759bc99415c2a07fa31658dc1e6b3361da808bdc9083d1710582122f820cba2c43d88244b16576f46029e05b808fa53a26f6e49696e5baf9bac71512d6a69e041568890140cfc4e227cfd303c984ffcbb15bd5f447733b82da7a0fe1b41546536bb7d70c6c5ac7b8503f7d9ed05376e66bf2f1cb89cd53eb8e262d67de184fbacf6801337b3c79eabe95a838fa36436455f5198f53ef8c1a8e235106e4d91d2b89f6d9f7025b3576a0a9bdd074eb8cd760f38f1563f52c996dc03475ccd2c2c34857d813d53a064e2d47275a6389c7c69d654c69b29da8921b2c9396bc48de6e05e739d69c3f8e7083ffb2653e4d2c064a1ee26720573392007eb82f4895415e394992214ab5950ed2a41863f9e79f1ece3f3229d7cc733e57def9935c5d70920a74ee01211997d225292ee9e7d9667fa9e982742d0753f3969c34f140cb75695cd17d52acc1e6436fe5e1d1b14fe252260fe8c5981c167c950a0c7f352650b3efd1022da6c877bfe411f2f86d637c4cb0c986230c2da100185a140d646c4e11c4081b0771ce9916b1224f6c7669354125d3ec59122892dff99e964ad83d65d2520701d0b9c64ebf5d409fab4c37e4727f275b1e8e83e2885917fb7138df07448bf8177cd74fa6e131449ae8dc61c714db500cf8e555d2b7674860e99e3ddfe454709e77a9d0074d3c0b28d5c290c05a81d30f39f74c04b944d15e31854e995c8cf56e35e53425c8644edb23caa3d176ab6cffbbf5f6e2c8088e6c31ba05a30c58c0f41eedf3ce310c5a10870f4a92193805bc7e10a1acbbfbe9087bc7f96713624d4096ab898bba72ac31bc80c0195d9a2f4e2671b328e53decd3ace9b9b0a381d0e556a1d67dce61149f8c96ae65d9ee6e8f8ea27e15a7f0dbfa97299e6b497e1575639a8254a627533ed5ae85a72b9084fdbb6092db598c38dfa94c7db71f473553ea1df17fff8af2d40e70724446fa8d48e45b6d9adfa18f76c5f98f4d2b5577c6ff0847b3d59dd5b16f80c5d019cdcd296fcbfdcbce8d9aef2c22bd65e9083abadaf1dd4ec9347d987f3b4aaa3712279dd58e3924c4dd9a9b08d52cdfce88f0b425537e972d148411171adfff85ad71a105abef0386bc319e688907fc5f6b389669c89613090e2e80bd7e610390ee617af6a6074966d812642fe9eebfb147cd8fc58b3151e3a27b49e87cb93f71938ff984e4838f74a23d0041bdd8d61caac199eb3d28b61ae7e6d19be2081bb07b4799c1c00bf26c7a7f2f27196b865d747fa0becdbf035da94013a43c261aeea0e8d233ff77c302241019ef9bdf199c960d368a2dafb0bee4f5c7516f6e1c7fd1161c509122761c97f965e10940345694b574680d94368a1c4837e907a2e658969b3bf59e27f28aac4ac9a47692ef6097951a70d35863a55dfa9aed1fbe981f0711338d9e1dff59c672070e6380a1d01106b369b17a870e502f143af0ee7222fd021818fb61bd4dc83d0faebe59b981a23e57d59464882b62c2a41593f48c2fb33d2b356852175d8dc01f17097bac34bf41662a1152c553a80fa6fc4c303a27e8d25c612387f29ade21f76f499638ad7f5bb31f1576a9c9ed0380fb281968032e87aaa99571b6bad614dbf1d7152bf362b2b4cf7287d7ddb89fc83d5c2c71a7deb36fa3908a768664c8498e8348e0a566425f5fbe1d04027795ad5bdc9ea32f7d9d651844cb94e09c31897f34ca88825544a0a8a822cf4a12d4c812991eb99faeda06112633c53df9968cd74931823b0761c2bcb0689d35e870ee3e092065dda3a7f773b9a6751aab42adc14137dde2e5c9cb0c8b5c7024782869bc348b0f9548cdceee1ca8e8e7f3912703fa0f387f5c4aae985f9fba23471b094d4592010722b2701af8f67a07634e6481d59a818df7805b12262dfe1e3275005bf9308593974c3eb88b579759bfc7ae5e9c3d964f30d6f92174258e66b1f861095544131c92b3d2f62976001bd721f71091002c0ba853e9f2cf4eb11c3316389d37c3dbc5589516e5f6f16743d205539674241c9169d725df1a326a111aeb350b68c6bafa46f1766d621ab9f2069ff40419456682a5f31d9250418c6a1c434e291ac3279c03ea8c45768e7663bbf6053741f07df2d029e09d91469d98ab9e0fe0073607b6ff8145d1457f2ce99b4ddffa3b26e9962bf070deb16728ceb8e7a6e698fbb6200fbe9576e2116afbcdcfa3ae023ceccad1f764f37049503a188ff175d611629c212f4c311203c0e2438336e660e65d05c6d1a106c2cf3cc17d56441b74fbae0bc8c699578544805813649b103c100a95f8ffe07b568e100827d1c182c2754ea10342c5a0ad6b0502b0f4d0d70fdf484bdf3979d458a03853b440a7fa0f81c444f9287c69c3bc3a30f46672643c5252f48678302b02eef5a0b15ad183e1e6330f37a2c27a7677da456389be488c1d6d4cc94ebd18e1eb36a988d121db0505009a3eda427db21f98de1925ca977101c7c8f9d493accc7968901958ae0faaeec982f686c694caeeaee2ec8adbb373a6a1df043fadc600e25c62c1722b3421f9436e60d6f250b6e1d3ff450e861c4452609ee440c4bf02125be23e39d5e7402fa3bf0400f932e8dd96b3f77f54e4c78f8240f01fff25da04acdab81d49fe0a14a004e138583a15e9f03774395343d3d9715fd99ff60a89791aa238a4d58bd719cf59e8ad325cea1a48dba869653585cd4dcb55d44ac11954e983d0c30d1e7650df4c3c5928f0629cecd18917b5951ee9a7a432ba10a320afb4eecb577451d2deae4cbd83b3dc74da8b10a4da34e3215550c68eb6edea965493b48404a71b512c90d7c29e55ac9103f8d662ac4a8a25acbd416f6c449d7c5961f0aeded282977029a06b781fb35c1ce3e7399041d81b312053a2988ace98357a2ed4429e0eb59aa8efdc037401e07ad0f30cb4e6df7e6b59c43c70cb365286f8fe7c04724a331b78eeda118aa213c23987a01521462024c3ea0065241d69d144ac1386e6e094d141755a9a37e6f2e6bde1cb84a844a80662b7c6225b34bcdead32cd7b4dae63c6633e6fc1dac881449f080044eca275403b155bb2b5a939dbe814180001245bef27834d5cb39eb1ce9ea9f1ec5defb4b4c157846d62a4712ac40ab7e1421f8ededdc3c96e389744ca9e419cce6f652c8d38f2a8c0e39726ee0b0f8d0e2d7793de8a80f79769ec46a9b57acbd83b6289923a67efb34b3b6eafc542a6189a003fe83093e805f40915009388e1879b44a3cd4e0e11e918223cba31f102879dd427b59a5fe1ee1db306171d077ceb7e07be9c9a993a0aec9de07ea6cdb22ad24bcca1f8ecdc1ebc5599fc9d6b5920bdf2301453c81edebc541faa7a1bc254ed771196274e6cde5565fad333ef3467098cc02eb11087fa0ffb54b1d3088ca4984a945c09c266b73349dba39e7030d3f7e08d5966aac10f9dd900ce81a20e7793a65f721711ec84ce331f5194d6521cdf52ca9bde54a6d05dfdeba87cd8f2ca9a6cc2a344c9c19c926785a233a123d539362831d9c0737007c8bb7ac328693717125cb024b764c97a6457fb0fbb5e4544cf1eac0c3e57c32800f43d8e460eab407c8bacf1f40fc09d9023c32dc1cdc889ea0a86900bd118eb859b7de912a2ea813bd1ef851b914c1b3a977b1ea3a7936577a6ac076fad336937322e5e977b97b612ec4d6bd2fce0ecb1dd81a0abe0ea2c101f13f4d49801102342f591333e166bd4df8a05441aed5b4d5e7122c53116d5c88d6ad4426afb93a35bbe7df4b188832a480a81e21fec59b263a3d81ca1eeb922d65a1d7ca89ee7d3755a4e0d29bb2e3d970ee8ee80ef3902cb0a5678417e4ca82e2821847e9174021589697f70ca38a0151cd015a526e4b2807b0e31b70c6423255fc4bb3a923e8ecd158b2d53c7ef36c631a06b6b40875c02e9c1c4929e1ab16e268aa360dafc6fa29f8e2f03b7afd94a23d810bc3ec23a540a94243aa115eb1f3fb67431da9025184bbd9bb1aa1afbff9552679171f5a0ffcd012f8454888c3c812b06898c358519ea0a14616baf9764852659999b0e7e83bb9ac94ecd7306f332c0690f40b1d73f5f6a00acda766903fd115e311134d3dd28b0cf303c16fd0467aa1b1a0db8e3b70277f8954507362874685b8564d80e01ed28880ee3636e61f709169b0812915517ff052dff463c18c26d0ebcca5f314b722b8c0490cefdd46ac93e62637781bef8e5cf8839d69e222d3b0661000046ac12bc8d0302d24cfb8b55cb45e8e9807a89ced9c5b95b1d39bd454af076dd2e57c6acf24842ece4ef90aa0564792b31a7f45f1d411bbed3757479aa4362025f0f4137f1b108b84ce17890127bb60f22e1b0ba4f6edf422cbf6a46877aea3fd4c82c929bc015feb78a2f171e815e8c0d9dc2f72006d0956ce3ace9270d29da23212e87c576baf8905c88b8c86789408b3117d25a7219bd96ac588a5ebb9d30835a272b514a1a888d2858259f38cef745c6495d3248b21502f0c42f4b52dafde246d3e01ae19074366b4d6e6ceaf1376933bdadca019c1baa9f14a63524581dc78b1f6d92578b5ff58c533b2b62a8acb52446b5cb3f48a229d2e7a234891a55ca05f045c9b12ca2a1923152b60a895941474e9a9778da3a308c7825d9cd486cf1e83af48067e2c7d18a03aa1be717008c93fcdfac962e7725905a360a50053213990d8caa11202767bb8962b162204242a22e254cb2d8b9aaa0bad36b3825edf0ba79e6a464e76a1d33dc8893e97cb5d7f9c03a8f4931055ab051ba11fc179a2af418871b52a822b6f82a67fc50fbad1aaca73f6bfdc0db79fb759e892f7257cf16255c660a087e57f0c71c2d8f02dd26f5c6d0d5f82b4ad82123a02cd179c5d6b98c0e459403c05b1e68bf88e3415d09fdb2f9dd5d5af8bbc542b0ad90cc625b4705ac91315ae07f7bd6e110fd4259cdb615a1a90656107468169698530a384669754c78ce5bf57f9fd7a870668c23d6190e6acc09cd5d23093f1ccf8d397ea67fcaeb607dab2f384bf13299266aebc4f8d6f15b445b393d804032174260a57bb5b06d9847fc299fe56b716e4765028c2314345763c476400be367031edcb3aa1b9445fb4cadb2ff2acb3e13c680fd11323af102b4bc60516c476b6915c9d5b092b8e3d4d6a98a83aca0561f83f6e0c60d88f4ce473b66968e485924bd6a1ac8546bf75306b20a1817ef10cd0423437db411f1c5ad5050c68a4b66ec358ef02a240d9962ab3423a2352cb7bb3c52255ef1a4e155d37ec4e597884c285ae4667f73b1914f1e8f3f9eddc67b9c98ae9f31afb0ae5007ece6533c00ad536b05e80c3142d64b376814aebb79d85242902328cf0d58e48b85e7f930937834cc64e1454d14861fec4d30876cfbb089805a72f4fc1c8db29ff6b47e2b3c415cd8e86423d3cab991c6b8d00298d3afa27a5f73bc2245412a479b8cc5c1ff10d8e6861da7db4c3c70cb62f16781c089a16febc2e71dc530e1b27358dd23c6a959ed8dbb7562825b96d07a49c21c9e0718ded5584dc5b369ad7e5119b31384a065cb610540c88f1d5d55bd0e465c35c121b66cb29f8fbf0d9276abaa1e0b6489cb0fd49668c990f7ebb15717baba6367cd944573987e78fd2eabc2f45003008e0447489b046c1f68dfa04c53713f8560153bb89bdfe48f900faa6a8648e8f80b390f2b29a5fe3568cf4df7d748e42ff52856c1651d115377f2dfe47011eac51da9c86b99cdd29c00cd432c14095a3fdd1362776b4d276602bb3829dcb4f645b72a4a9cd194c68da205342db3c09f01d8d3f73f0fded3e84bf9862ec1091a0a672f811a22affb0fde647a74b841057fcdca65eb79349c883f1972c902f236e21771492d2d8a0c26b0cb4af1e3688d207f328c20108176432243c0a5ae3ca4ac3e4f59f79cf242e4262c9e0c17a727777eaa230c2139f4e31f819c84adc7954d449d858d9dd1dab119b4eb499e5c483165267d70b6a946154e1d311a04aa2ec75f2d410a29fdb65d99d46e1808b9f5a04d0cd33a773a105fa29b3c0d2290c945d5e37f5e49aa1781e57bdb3d99f2196172c3c8d6a7af17b057dec786ce8635d82d418a9b1b7f4775207f9024299d7f7fec6ae98684635b7551abf24b18724b8aac065f16cb2ec58556deeda5984d09152628f76c6a26422e168582ea7b402d5e31408af4e59de2666839a32c3d40e514823e244c084ee13bdbeb4c5ed8fd96365bfe805da29e0633cb1ab464d556d8b50bf7be02af06af60df72f408240dcde94f82329e502ef27d263613767344fad7a75d8916b28ad46d1cb49bb49ff1c4619e631111ace6851a00b9e867be13ca61ac0e068785a89f74e547d8fcfa45773294ce712bba64d8094303411010cc94290e4ae993f5998c919aa21130efaf373e8231db827021e40ce33df80cc21d999e82526efe3fe257dfaafb4564ad07abd3dd338d055728ee24d6a7cece41241983bba9c51518965f2704376fc466316aaa5d291b3cefb91dbbbb996d670802995604607438ff4820ca2a246a0a483939f9364ebea9bc80ec6a5e662dc909086a8512787e5048b296e7cdb63b037b5690b7d74db44dd383edc370704aa02ba1eb2685e505815271d4985942aabd2fd650cd421c9df55d618cbffc1b5e1d35c1266c4f6e8cf611b7c4658be5876f5fe1e8487007ec401d73f7891f387bb56956530596ab0b3ecd4e940b490cddc519345d45beb2af8ab258410228dec4df6de72ef56064806ac069367fa34aa6134fb5064bb15694a44a0cdd21968969802354bb3340b0894851765e3b17dda1704746fcb911de4c3b337b2cf87f6c916c92259fa44ea03a174a627db5bda8ad71b1d8cd19aa815d097c9d0e4ec9142b2a5421ab4317e54c6e4994ecc27cc9ee9435da8103aa4411c13357f5ab265c9face39afcd89633f63ce3c7fa32a9ee2a9669f261b8f79e7f5fb6bce6ccb3eb7ecbb27cd8684f02e9a9892d93e5e1fe4f9855a7d8a9766fbe8b72dc79b3cd1645b666827d0af1664fb30468cf135a22ed6d501fb821f31428147440e2f11c838e5c41a604044c9d8434cddafb058de6c98fa0b4273ff47f7fe3c4db4f25d974b0b4b4897ec8fcc3092edc3159048b6a794524aa9a4b4fe4a4a4b37defdcdaf6c5f8d342a76a5e9d30f99b5cf645b65b2ad49b2ad47b265d59a6cbffae2e58cea4eb671c6d69f2dbb60b6675f1099b52fbccf9ec1d040f84a7221b98e5c17be5ca2c91ec31708a2c97ec35708a48a107c0d0972f3fc621ac7445d44ba64dfba349e0d8e4eca5544b6c79b8dce1988299beda94f9f66626a8e7a5aac2555bef08794ed3fba8cbbe106190dca1b1a94b141ca93eda90ecda8b562df342665d4c23bc94d4f90edc32df76b44d113c49205bd5317a681dcb17331633a48106e1ccac8b7971925b1207bda6a75b4a5615dbf6c3ab4635f109939d607fac2eaa3fb1a46c3f183cb111bf95344a6755a606aa60f257271bff0d87ebf203267bf361bd9b52b67b846ffe28b75b5d87f34f25e3f2f986cdf2f9b8efab51109e36a89267b8a2f96064097bff7915dabd1306de405f442baf62a38b97dc3740052dc9ec3588a0f6cfb38298bbad0b4a0b1b0d9e8cfc7f6f54df6b50da826a4b949f7da0784ce1ed29d3cfd32b52bf250466ded4a973ac66b8d78ecb3939681f068a22e96b5d65a0bb2b760c6ae0ef9f90591f97e5e83f63d6f2f1eae8194a00a3b78e209254f709fdc27f779797924cff77fe0653c985c917db871f36d970669c442b97a3f5ed8f31e742a756689de66df3ecaf16ee6ea62df6af5c30fb45b3a269cab986ec5549db595c8f4dbb6639eab55abb3d21b511dd3f2bc917cfb907f8e39942d2fb2fc5a4816c36964ce603af0c2d9833d9c39d32726d7f6992e3a584e9fb41dce8998eabc77dd351c7add376d304cb369905e9e4677e3dccc154b976c56397889a79f2c8d0a9d3e8b7886f034d2a58ee9dcdb8d6973d327ecc370fad4539b2f3124595863efd03ba786be988e99ab9fe93373664facb1b5fe696627e7a64693c951ca541a693025d3c903cfc7a43ee49be58198d2ce9dfbb4cfdb78f4651ffbec6950f6fce68f8f17b64fa6f4f4b3a74f24f99092a4ec997a4a442cd1a3e01e91e95f5e90e99b06995ea67732a5f46addd22797889d666c3e49a6f41a9630546ee02f568c421ecbb64ec73448bfe196d199a8a6b3e2ded1f1a691eca0d34f23624a3b0d270cfed16529f395f21a8685f73704438924d357d64e9f405f8c8e99a8f9d2257a3a5fbcd17cf9ed984c7b76385fee7cb10dd27bf35dede84f1d74f041ca315f3f5faf83d6226bcbd54d908964d3a8010954e02931f2f5ec1acd7e0e38a27a441b72284329d35a317dc437ea69bdaca7312b8d09815fef71454c7108fa9a524a29a535405146a6322e2ae715af0ca28f4aa86073dd8e68057ed58941bf8e02023d5f18e81908fbaeab3c7a49ad40b3040f2dc96711df88190bb37ca373b4e18019d9078e1939da833e7fc5527c668222f418bdd87de278318da6781af38db59b601438f446cfd93b1cba90b30f888c98fb50fec38c3c3fb0c70e7235238da6fb8959b4bc50b258240e2c48fc985658e58eafa18c899b0d0cfac2941c57f3f3f2bc67815506e2fe9012d3438ecf6e0c1ae5a4341e05013b4c94bc97156d48a07910f788516d72ad9737f532a6ce8052fa14cac8135b9cc8d8226a0790168fd2600a8b17bf94ace48487a4ca473c2ff18df8193f3f3fda60f76ba4124992f8cd17583cd0491da324f5d7a3c12ab3dc21bc291e8e547cb14b275fecd20d3234190dde4943ef9a325e7d7da105f2fcb42bf2c2104c41d97680b9c6bdf7de5b818bb51b8a8f4aa6709377c0a206edd79321d0b36b144228f128a594524a8fd0aab4d6d75a6bad94d2955a6bad95d6fa5a6b3dadb5d6fa5a6badb52261b68fcf0e3e3c999e877eee6cca29cce410cc4aa62093e9293d18a3374951b040490962947a4a78f3f431fa145e7a90be07ce3cc292254b962c59b264c992254b962c599203b3a7c7c3b9c3a300d76aa20ef0d12206f87a7c231e5f01be2a7aa81d7b86615a841661054f050538982910ed3f200084e6901699a8027cf2a54bf5d743f185dea8fb68117ad06f34d12fb41c068266ed936692b0eddaf6499afc436659134df517ff903c1963515282571f73488bd0227d02f5e9a54f1174d1c6c344ca8cdd5b7b149b8e7b7b77dc5b12103a894420883c87d887b61af61a0e83c876cae47ab10f33a35a79103302e58a16af6c7b9a6dbfe6681982233c410a4dbbb6b6f190dd12a75b87a729b517882a31b56ac2ea5ecf1b8d6ecb58e45ebbc4bb802c82c6284990e92f24d94a4ff6132fb9258b5ccd90a8c109d62fd6d0a841f95e11c16271606e9c9675ddb6cdacd3581ca76d1b97711cc7655cc771dd31eee3382ec3b287ddb36d7bc6bdc3346ed3b09a2fa63c1487cc1c7765e5b699751a4be3b2adebb68ccbb88cd3ae245dddebe564e56aada00ae2b2db6cc3f419eedc982a11537127a4349b8ee7cadd7becbea39cc6691df77befe5de94cbbe65df34f9c9cd6b97bb5dd669bff7765e77b1aebbc7befb8ea3db9cb2cbb44a2b474120109761da71f7d3eec6719f5a97659d9669d9b5675a0a8e1f5c8ed8c89f2232add30253337d28914e9bdcc67dd933ab6db85ee2fa0cd39f5c94755dd7a1300979d889c8044517f2b0aefb82709327a6b4e9c4ec993e54081d42e906388de3b8cf47a6619bb36e6edc5de1b6544cad701ba7715adc384dd3348d8ba9ecdb404c7130946e80e3b2adb2b24de3362ee3b699751a6beb384debb62ccbb22ddbb62dcb3a2cdbb0ec5b876199a671dc330d5f99db64cd64dd00f78ccbb4142a6edf6d2a52685cc7e16f985ec33567180899753cd7770d440e322a6d1368c7eff38b1d8e17efe97798a8fb4996da026537bb17e479d8c7723f4aaf5716fbb1449a258db2b9b0eb07439d30270c9dd365ce39e79c73ce39e79c73ce39e7acf3ded08046c9213d56a25c922e595cca8ffa345291e2272293a3085d7697521e870e3d10b043a33e4d6e3ce4c4e1e22deee2cba141392790169ff2c5b3daa7f649dfb556d904cffe46fae21ac64eb18b577c10e81cdede61ee1eee4237c1282ec2263fc1a2a748f11156f11578d402ffb0c00f79857bbd110a7e711246398b6758e60fb320e1824587e3e9b4c5de2b6b1be60f3b896c67ba649ffd7a331998b32f6c1630b7c0316b2b7e40203bb6f1d03ea7f6155a8b162f70d7d8dcb8c0b388972ed9cb06c4524c51dbd6b6c098d6e26614dc35b86d70df344ee7a8c0ad9302f7ce096e11eefe992b3c591ab4eff064cd96065d84cc21441afcf02ca24bf61a9e466ef40ce83547c6742b46b696ded21cc668a2946e3dd3204d07bc50e6ec3021d7778ed4913bb5566f925a44842ca684d78c468d50a3d68c2835100ac72c592ca6b0c783a46aeb470e3ba5d7d5574bece953df3ede203463ffd1652cbb33b22cfbcd7046f7de3b59b79bb8ddc4ed266e37715f23aa9bb8daefcd361e5c9675e7de65daed1cb0e59365f4d33dbd93ed35cdc3b4af6bb2b9f1c8beb601bd6f1ab4ade1c8ae2bcb70cc3646323093f429669b3b39f3a626db996c1feda7cbbd3df88f469e375db27d4cc1f183cb91eed6e9ee6e71d8475bda77d09781b49a06b383be20326fb75b8ed62eedf2c294c8f469b067f234681b8784f0c28b75b95c20c8f6574b6f3a409f8d2f6d459accf3b26cf698106990ce79ee1808d4df3eb377f7fbd8fccd7ebffd6ebfd9bb5ed2ed9976ec8921f9851c95506127afc08b3a2001916c068d62ee17a6cf7a1a94f26edbbca1c1e6b11f59ac5655ccd9c41b8ffe6e9813eb30ee8dbd3f179b8e79ee0b2273f785ddb7b7d878cc63d9e9b4e52865ea0dc1c33dd3340d5a1cf62f7d4434d9376e23a28a7dc5cde1de70b340aad863b891682394bed874c84fb24020d01017d68a67f2803008049a3c9307e402949dc3f5a0962ed91fb9583356ec4b372cf79bdb67109ae77f74395eeb5f38dd331a255d70ee0ee56eeedf7834d87d5d1db06f898086081b1ca993e272c446fe1491699d16989ae9438970b8c55623932cf393ac1074d912235dc07f34f2bcd92f3159bab8481797f930fbbc2669d3d1cfbeb0fb4299adc6e1fa79b1e45b749383308683dc4cbfe1205ea6cffa067bd84c64d2c603f4cea4d6357dc23aa662b667b1e900bd3f7d84e62fd644f58bc76d17e1098568b237c1d389a8628f024b8c47de17dedb7d5b1f2459546c3afad8e4e9977e89a69ed605a3e982962ee5508b275f2e24b344236746325e122447b02b5f2aabc14ad20e84b6d423b9dedb6d1a76d55bafd5a66157adb7d65a2b928aa422a9489080e09c586dc1adac58aab5031175c1802fc8b51e31428147048c08648c79a92d7d225dd7f50b5bdd3b64c807ca9714359451c8f5b2d5b54a9e0b471ec9552639c288a8ba7ea4ca4b4c955e6fbb37dbda87d993abb88a5f3813d5adf9235595c58b2cd7cf05b382b1a391e7ddeb7919a599da0eac03d6810980c3bbcaf539300e7c03dbc035700e780018079c310d2c001c000c007c0356e1177068634e70686710da1884d605372e1009030e690a727d0c1c522372bd098714895c1f43bd0a0e290c72fd0a0ee90c72bd0c38a435c8f533e090de20d7d38043aa24d7cbc0215522d7d78043ba44ae3fe190fa20d7a370488190eb67e0900a21d7db8043ca44ae4fe1903691eb411c5224e47a1770489590eb5bc02ce0903a91eb61e09046814e21d72ae40ae334c3c00fa97c69cd9b2b8b545db40a51659324b9c24f4c6553882a1b13f344aea7f5340a516565a44abdcc925c4f7b620af47aea44a550c815634254d9991927ecc4d4a584a8b234344bd089290d0951656b6a929013535d1351656d6c8e90abc74454d99b9b22e41a124254591c1c22e46a0284a8b2393941c8f5b426a644afa73e882aab2355ea757e90eb6889a8b23b4a4495e5511255b6e70651657d58bc925e515e4f6362cac5777ac0b3835c73907d7090eb690da2cafecc20aaee0a065175599288aacb922a4962eac5eb5f4f8fc4548bd75398985af11f1bac68c02283cc7a414ce1d75314201155b745aa5423318542255952a56eafa729882abb922a959e20aa2a4baad4b720d323725da920d7672df00b5f815b1c055ef16d3bc6f7192efdc3190c30c4886132c510838acaca8a0c32cc30030d34c89051430da7130a3563860d36a440175a608185e317fe02ff2ef08ba360172c2ec2a39b60d143d8e4de350d84b1634cff61d0e58b7db5b7b7bf5630c070ad92c4889124c6648a9189210699968a4a6b66656586460619686a6698a1c686061a6c6e64c8b8c1a9a1069c9cd329470785d2d999316327d75b1e1b6c78d8f5b627e503bedefeb8b06ae1f5978585a70e5a1f177e720b2b165872fd65a5c0f8f0eb2b2b8505e3df5978857158b3fd87432cdbc3c0e196ed53300b383cc9f62d601770d822db8338854316b2bd0d78060e4dd91e854319b2fd09873564fb1a706843b697814300647b1aec67c0615492ed65c0615422dbafe0302e91ed5570187d90ed63c0261c462164fb18388c4c647b1870189bc8d6de68b247b1027bd164df028ba2c9fe053c8a26fb639468b27f811f4df62e704a34d9a360309aec49384634d9b3c02bd1643fc23266dc104df6214c239aec3d9c030e1da2c9fec23d4078071ea2c9be847b88261bb11388058e5660a3d6cf4c56c951c9133639bbb4cc522ba913c0e3d18a908d95b2501b80c7a31573482cd5ec71121151bd8358aa870183443231e92cafb424cd03a1178b8ecb48ae2f31c10be79022a7d0ae724c699d910ea1f848a6759274955f782beb7ade68f4574148067354f2b393b3d831b2a675f13dce9416c0150f5db8e8e1c824a43bd843ca845c4f63264803e3e18cfae2e1e807bff0baf085b7852ff4bc940fc6178ebe6f0e9943ee2d0b64d5ff454b9bb6ab2f045ba0cc7021877635b2ab192461249aea33cc6a2191eb2b0bcccf7b4639b9bec6094b5cc9a8065e7deb25e4018f3ec9cd4f929c92a3929f2199523a736f9f68e5149a8a2dc7cc59f77bad2e16fa7263ca88986a41ec23e22993aadb52a40e691bb40c726de9178d521aca971dd9e99c94ce8d448f49d8ef69013ed2c3793373e6cee80be78fe8f7d91591eebdc93d98b18b2e8672492c568c2686596a2fe9de25cbbefeb648957469c99b76c3bb8e61d8439748c35d961ae634bc6958d370a6619086310d5f0d5b0d5f1aae1aa61a9e59c3ad6199bf180daedcf8d0dd2a422d5ecc31d7afd49a3813656ec8b546fb8cda0fb469d8cd72785dab9713ec25d64b239b0bf1279b8d6ca465b7bdd9a039997e61ca0f29d9cb17eeb224124da077f9c29ccf85b79a2d5f5883d1f285b3d992e50b837440f9c2980c962f7cbbc8cd17b63f974d3d52f385698e2610cd179ef98768029d46e39829d1043a912d4729d7642cb61ab4488345843a70f27a091353295e2f938829eef554080c44ba92aa392355eaabcf0872bdf4037366952bcaa623bba5b7945a7a91680afc4392e8edb16714936a485a9d35157ba1b95613458b74a9fe5e4ffb4ebf10a42fa2651c16da76d06bde96835e2391e85753138462efb61ad847a29f0e50d218b9f2a5412338a4f5b3a6510778953f06781831a2622c8571499657c8f2a1178e1e104bf2f6dba1c159b9bad58040839a74094db29cf30a3994794eb9edc06e2746e3ce7943e4455303fa44ca71730d65ae3cf4c0a274054f5e21b74b4a839a7c4602dd850492421a6c22a6640d264e867b34985dc30fa3aa8b902af2d7a5bc4c1253a0dfeb79a3112679e6bd9e1767172155d245fbc296dc7b640cfb82c0617bc4c0f60a0481c3768a4317a95a2263973dc89d41589099553081ed31cf1edbb71dc318f689430aacb2fd9c19080287384bb206596a7d8a1567f2b0caf18d796890e2cc20b4860185214030c410430c018221401082107020ca96f641b22295cdcd1a1d9bc6e62625e5a014e545afd278dd46c8b2e8f12b3466a986bc1ea44ad54e54dda30ce18689a6233536366753bb3866a11ada3bfaeb793d904b4495199a50e8d9c530afc60b65cb4617a9e06834e79c55b6224e4bcda7c8147c62ca0b4f2eba167aecd349c827e413f209ddc73b8d0edd0b79a150e8f1a3a2df066b0f96d8f14e60a6dc018fd045cfa49006ef0c2430b5eb42ef42ef42f69de854742af286e0a14091354cc3c0843e5b47f4b09590eb3ba771fa24ca1aa7036d3a445de84e1f6fd594d5d4a5e96663e6133c7366a2d13e853a6deef40905cff5460f0981e3855da48bc406bb562b66b644d8ebbaf6f2ccdb76c3c76db06edb8dd027fa4a4c78a2779f48345bad295b53b65088be4e0ad1709cd192ad99eb670ccc6cd5702a916342ef62626ad311fa687761ba1ba6fbc2a82342b1d238383831316573fde4e993e8f553e6cef4ec70c6c464214dc3354287b9dd33149a8665bfa093682431736a58e6cce67b63661fa22974cde6420be4d04751295e43f79e02cb58ea34ef2bbaee219665a87b085ff1a08d877df78669d164179b5c2f0289b61c5e76a2754edf64275a8783dc1cba3751b386a6cb6c31c193a5d3e04e97a4aa9b88e9e9234423e47af275a8c58b3d6362ea8aa9a9d3a86e1e9fbc62358ccb8c4971926f566ac0995ca74c34d5cf980fc4842e7aec93e80baf8f096e982348640bcb992bcb1d4453bd5422aad4d398a84ed25ee89ee763b61aac325f8909afca3c63fa44ea0e84e6b04f28ea6cf5a9611aac2928394260606a6612371ea53c43427892a66a76e3213a6dc9993ed16bd3213afdc9c917fa383910321fa17b7d371155ddabc9eb27ab751a27a642afde611a6684937f5627b866efe4f5343298987b4f7ef2939f3c0433112e31e1d5ebd8e7e7430404084fc332cb9f3edd8f74afbd92ee773265be196382634e18737c88a67a8b97a084869931132585b072c3f814e11919c59ccc96f1a472dc69e6b6af01d1542fb3948929d06703fb80903994e600832ca6cc99c4beeddb1e778b76bb747777777777777777777777777777bfc5b424162b462a529c884c5084bc8edbb40c845df86a1b0fd98d72172813039947b91e2b9441ed9c9f735e73b68c9d233abad7c52fee5fb885a6d9a3dceb55127631c22b4e599068710d1c073c5fa69196f1921cc9f5c5c319855c653c6f346a997fe16ca13c6c997f2198b31196f905cab78dc768743a3a1d8d5c9058ac9844a8052273385f22a657360abe9976ab6770d3744ddba8c07d9302374ee7b44ef3744ffbf4cf5c4d96c9c2b3054f970685608c27915944cbb878fdd927f96ac48b6ddcc34435a07b946bd75b6cdb21b79669b0355f24ab6560aa7d3893c82f7db25fac1b0ffb9e2f46a20e6bed376445f7755dd405a54731437060080e7472477f346a6de4695205fd4c7a3ff0425a841689a64a63962f51a53ec543b177c59b81ba565db2aba8425d6c29d621b9d63320aaac0ba44ab5ef610151656310835ced79504054d919ec908018848cddde00f63d62eabafd0ecdb65a35620eb27d0e31e5dd3e876e0f0001646b6f83e8f635a0b2bd0c1968c8f62b31c5224725db9ba2cae6e8c090ed4b516575ec59882abb638fa3caf2c48829d2edc19842b97d4a4cb978d1f322dbb7882adb63bf22aaac8f8a6c7f1255f627145577851253bffd28a65adc5e14532b6eefc5148adb2daa2e8b3d165597655fa3eab6cc6c5951a5de5e8ba9edf637a6620a3fe5bdf1b0d4d24f2a5f6ce4b8c7789222c5637ce185c7a8523dc61b6e788c0000c0630c40001ea30004f01869d0788c393f461c70788c0318c063cc21871a8fb1868dc768e3c663bcf11871e0c841001d3aa878d879c763ccf11809f018753c461d1e557cc74b3803e1ef180669f87a8735131c1a61d159e0d14998c55130e92e30ca5f60173f7ef116f829187f070e25ab85c572573f3e3d3c3b3a393837363534332d9998243388419855a8e40c7785450dd5cc0c00000028e31400003014080584c2d178204a439d8e0f14800c7fae54684c1b49b42cc7711042861063000000000000000000c80c6d00c7522080af9225bc55081bad444de53926bbaf896c9734692cbde52adf29c99825e52ff9a8f0c8d11ba235a77af1f2e495527249975119b6e76996c8c314c710069c6a7d41c9913a7521a2612298e9c81c184ec25b3b98e86999150846a43e1b7efd009d93c5734befcc2c61bffe6159ee9e70d49aba17ba64cb79c66441b241cdb77290418a3f840d731aab8fe6e879caa2008fe7944a1ab90dbb80fd89b5df53eb4f1d099ea5938f64e39f372016afef4d293b932297b9be825785bb83d48b66711448dcf4a1af1eafd7442679647db550f4266015e72515888d4fb8d9cdbeba9e9763af683f671ac2be641de938c62d930d59dfd98a45e910f9a3930bf0053d866b893434bca9ccb1bde903360bf3e82e82b824e53d61b2c5b58dfa7231379d67e84716b590e56e7354ca5f8a2fd037cca2c4f3b6a9f35f8adc18a8675322c738868cc04692c787bd453c9f16a87b9b026aadea6a6b23fc200669af31436c1d6c285006b561725271311a859be0ba9c073846144115a74d3b06a78d5a78ac25b072dff49e0eb999f2ff51763141a8b9bd1c8e117bc5ae9ee1aeb195a3ddad48af5970a767bdad827b49e8a19cdeb87b0bf206716f2f65fa63f9a6259f133b30795620c6c1ef729aa80e58528149064271aeb7945d4825ba7af3032095237c9c3951d8fd427cfd62e944b41a1f780ec8cc0fd1ee4752afeef5f2abfc95886e3fff082c3f4b06e485490dd9ef2127a16681e0a015e8c70e767dbb09e99694c305250cccf11ad1659fff79267806837802f8ba86dd03bf553ebcc9c28778f67c0bbd90466082688365fc8e936fa3f48b9c5de486fbf0f2011371cb8e2ee57a54765dae27adc031d9dac670e436baf4895c5d7e7bbc5245a482ddbb09c7685bbb4cd19ea2f78317d1a34f53df033e7cfbc7b68c1a9ecb74a9f189be279d30007a04254ea07234df599c8a0d569d18ca8a6d82a1f1f5aad8bbb38d082cb3fff115640f0b590ab67cf33f7204f62e470eed562470509d9e936cdbab663fe986e95553407bc433217bcb1fab837a1ee61a9288497712abf743c04a02d33fcf00925d3c00a138be9e081be50f30f68b8f3d04566f2b28ef2a46d910bfbbfa86e061b045cc962ed23273c070f7d46285e16a177f4ae3feb0890b1e0cdffff05dca16daba9f5aa936f0cb6f3537c143121b9c7520f98dcba215b34cf3a17772c0c20196bac29644385d294a934a83c94c766c56242faede4c30a270aa6e304fdf4882a94807b2c1cec983d7c5741ba63758b8e82db24063bf1b0b20e09cdde0547b87445109e28ed80cbceb1a2917686ac28ddb8615614170e4140ee90216cba457c026d6b129121d7cfbe789714931edbeea4266ac6235730c3932d3bf468492b71dda0b48563811a90b8920cc08517c760cc0fba91094729e22035a15cb308c40a42765f0faa9f03caf8f91ebc2e7794ab666e85b33b73c97b14e3a59b27e089e3b63634ac251a1c0e414bc57cb63308a8fd48210317b92d286a4a8a624ff89b6ffe5dbe11cfd5dc8d14793ff7b5abc46b76f246f4a952e079f50b7c1b6e913e9e31dd2be57da1d9c1046791d178865269ced199878f6738bad76cd99591ea76ddca7880fd669394a0ec4e9bb637c3028a3743b683dcb033cd288711004e120a6557bf77950d050f6a12613cb69d2c0d3bd064fbc754746f006a09c36aa1de203785470aa0307a2f5bdd06b2d408866b045cae72c96ee24884d6c4cc454919584ba6f38b7997e4d8e488fd9d2e8edb4f6900d39991c629a664a1061fc38c49ca3e308aa599d2328f383fc4ff316394a2f29ebc7379e4984c34fac91ef541fd57f5420b2ff9bd731535b861769e589ae277c28eb2c1bf9a7e9b27c847a29ee38992af10cdc59735a885c7a1b3d9539f22101ffde1a84bf15c20f78e00699e7057454cf0876e9edaf37a59b26456ea2daa718696d3987560bdc00073fdd4a1f32eeb9584123d1fc8bacb6b77a3c220f2ff48bf7be60eefddd8840733467322726302226863971ff351ff9f43937ec4df68c7a73455d8e3c25992d7011651899f32089716c0695796ab35adaa2c1f1e9e1719c8008775788e94772369fb8574e8d6b48e1397b746f3596e5a58b26d75a11056dbd309518ec225f6d50d9d99a403defa49accdb2bc405661c20558dec73b907221172c609807cbf97d0bdf0cc4a909dbc7012a7086b4604646851990678dc36f1d9b08c18d4ceadad9df46d698a4faf384517fe271ba76127b3737e67eba368582028a24024af4cf48c0a2925605329718cd76caa687a820e4ea7bfa614354b21c130826a091722c8a7fa7acc2622ec825be180a893eee4087a2d8d13763fb005c5970f9054794109c12390cfa6dbc3820dd41b21f96b83f927d52436709a1f4b268b0c9fa4e64d140dea0aae94a61ecee2a6fd0af11635dd99300a47a1b21bb5a11b98b2934a68a188f5232f44cc32682c36568b091fab814bb9a43cfee8fb37381ea6a57b21214ecaf3642169262714a4ddd2f0ce6ec6eb523de91cbfd29de64ca4744254ac6d35da28e4dcda0e5c00082c949bc4c6133df55043b29f9007445d373e38788730750f1a801d0b7812c70198ab5459b0e9180f424a15061b9f580ce2e278f8b9c4f04375ca781e2ed3ad2ed0671ac799b593c84934a877e41607aa5ab3be9bb8900ab6642fc19f8e25d29bec635461c5741c138894a59eeb51582c817ec5fa77b0d09ca0108e52b6b2023520ff49a0ccb38d51eeb40df99263622b226c5dfd17f514278e894610c5b4c6093e1382f66cff0faafc3eddec766e9906f9edfb1fccc174ed5536eb3ca6d9043f6746804a7bba80da9e09e2ffa567114967959ba48fa4aa2a58530d3e3ed0531ba114ec3a21f6166bf73acacf579bcbb58a5fb4cad9af87d52fbc857db4367d7a0d4b639277d74bb7e6a5794f02b944fd9dfebe74a0884295166fcf60c892f2cbeac10b3921aa3cce154a6c1a1bd3f108d055c8a6a5c399dedc0d8a3020f74e4009f45941e4a2192e5544aaf0cd25be0b5ecf424693c2057101ff3eafbae31520e4e64772c08ced432adeb47b31ec9b6e3dbe79c21f7478721a907a1a61173034b0065228b1ee7c076524c3daae122399236a947b1d17aa3237b972edf9441404ab0db41276118ff7637f0a0a476eb081563d64aac969c18a14332ff0ab6a55a50bb76cab9a01a81e204d5d1779581a3decd375f92b7b70b9f1bea38e4818e4bd948845603dfb00f553164bb533d2cb587828210bf1b01db26d6c3d6cbb8d0e7e34c7ac1eb68da5d8b50e9ac62e4de0f71a7dee0865d650aa90516792f7be4fbff091b426b765554f3b0bbd6f989ee6b8ae8f734ee2a5dbc0a0f5ab13a988b909f088472306989d4f7c1bc5949c5023ddd8301f46723197cb56c951faaf84d47e79ba2bd025091cdaf91f0dd348bb53b260c4ba743735fa2531b993f76fd808dac89ac4e04ac47745b8df45ace16700db84d3c9900aad093b03d87908fb53b7bf821f58783dd78f7336a128856fe96791e88d252b9a07b9416b61eef70e1b88d0250636ae96a699490a24c58f894e8944a42ceca47d5c39160082c0b37b4bc067070262b283a713d8f9c5ab69192ac6b50932ee589c6ab9d85b6402943067873a54c78f8f9cc2441bfecfa8e4800cd45c3271ade0d84856f5d20dc1a3f703bde9c69abc985f98fbadd3638426e1e9a08f1b8a33573a548174808dbafffe7cfdec2d3dfe2901819fe677f460f7a088fa093e9c9ac6cd001c909512c31e33d083f7a89bd0758cfe089fdf73cd51a20299da29978334706e6708f02f89908685c1565233e339e637810def394acafc86b82803b4fa1400c901479652aed8d1a8bbf9883a323a12a98eda8785298e99b2668a0d80494153ae1c8a720bae2b21332d523780196492159479328d32a2b2d5f6f8b960a1dc843a4f2bf18274c615b02efb1a0cc755e483e32a5329aa49ce30c71f0442f2d8bedb84147b22bc0998e3a7b261adf3d2b14d188e45120263d9533179fa1720438a3314082dc47de4616a1fa22d23658250341e9aa415dbde6a366dd68ae2a957d845eec237ea6e7f6817a08745ea59ff37f891cd70db8d7f6c9303fa1c76eadb0156a7b081b4da1fcfa9fb2f37ed4b863e06573cf26bd50da6bb4dc65995de90a09c70f0d724cd99720268a2f9e821bf210ee33e72d60a0ac272fba79cee3606868404090a18d3cad297d61ad17ab39c6eec3683827c1c7482dc0c1a23a90ff10d4db7f7cfb07f327bf46d9b9f5a153c8bc72321ccba28fe8c03e22d82b54aa51286db43dedd644a04f17ff7e2b50fac586feadcb38277cf2b124ab71476768897fefb4c0fe8ef11d92c8b1a27a748b99676270ebe97eb9f347def992bdbf89156c525e659afa735707595848a06d2a7e85fbe4568e0262dea669c0f83cf1d1f1b01665adcad363f118efd37171e77d721da829e33eeba0872be53284f01b65fe81d4f1bdc10f6f5cffb7712aa755175dc106035db6cdec09a7765515ff0b7a5fb5897214c399292713b41115395f0929972038f98d8db1f74c8e60456fd42a397281f91485140188f8680509e21e498056e0eed92060a3d4c87742f02f7a12850d0fe5fdde2b1054a83621d06894560be84263ae0d71152848430271e8b38da1a37e01a53f115263cf47b2cd0c31dca3d236a8fa9fca98d4c3de930465489ad5eb4078dc808c1aca52026466a3e575c926f2a7733aab98beabb7f80e005179f7b16657e66c9206512bc9152019f877c4cf58a3892831ffe2a31160987396ae2ab491a873d9c57bb860be6a114873dcb2c9de76750d9e3ed1dd97a54ff9847d2715b05ff4b7bec4c24837d8ff430f508a029fd8e1fc9e87d5f43da26fd5b049274e903550c39216510520d1697adcd4591c80f756aaf23913d3b3405a71d85efe0ffc03f05cff73dceed89dcc052b3976f9416af684fb20296ae10d929dd595d39a9b3cf958d292f2b9d1d227b79a9814e31ca8a8f294a166ee708244c590aa55e93c2a0774452ee8ac56b82832bcafaebb75a952cd9b0fca6924bca94312d993ec6a7644c781662242faece1d77d31d613b4bef9655bb72e27775e8f152302f9774ebf63c8f98831a6708e0c887f606820f3d9c494e1bf927ad30d6f23509a72a4c0b0b8b9e555d83eb888f48498f8cb19fbf8f8ba1a88f68c6903e1f0d437ac40b8afcf97f24e2238c4884cf6f20111f413d227c6e8ec037db604a8da7ad99e966ccd284be68195c62fabd95c88d28a79d253da3220b2f57dae8b494702ee2e505d2e6dcb1f36c084c480f0ef6b265728ccc25eb8e3b1600a43f3d012438dabb040a1aa396c890686dab3dbdf41ecdf817b6fa9d2f60d4ec24bd1e3d43ed8ab5035b4a2bec29faf009d348ac7c886c35788cd4ee6fe66155e28d292d72755778ee4c82f7b2bdde0d2586bad93349923eb3eb41fc3ba0837d320d8ad02ffa04aad642e784a83a0b1123b07258b5e1d0bab4564a621b3fd6023911d96587d48a11406e30b46b880b870957c5a5090220e234cb3cbf49f810212e126440d33f32bef7231bc7569571bc5be42236931130bf4abfe2fd339ced4577d989e3326e613cc6d632e4304742243650ab7ef5fc291db2df066ba5d5ed645298761c22adce8f5b9e583c81cf3e3b1f4c2c40b410e881025ee29db826693f52aee9b172b8e42fd15c348cd595477d5398d4d138b1d5b850fe24f6fcc33b323815e7ebedb7832a4359907a40559b25de92c4bb1bf2fd99ed4c8c7f5bd46e2397b099b9f0e915d470093f9e59a898f84d604265c9a353e7948c557de07d0291ad0579d23af332ee4d2b761975805b239d6dd02d0441ca17187e2dcd9fc789d6fb87fb60c38a99abcf9b02e8fe3164b74edbf7e56a1f1cbe2f67ef79c52e89231ac428738a59fce9884ec1206d70708ec5c6a7a2161b347c455e66fe741a529d757ff604c322eab342043614e572f67b17440f9dfd5a9ae8dff1a2439e8cba3a5e960467eedc4adc6aff9971492f15376b72019d5dabb65a3c06d69793668e4259869bec024cb650c1961c2393ff8a25b2467d46e9d584a8d9477a3e380f5544cd113145dab4b1df366eb29beda3df06e2f466e1faf90dd64656b2aced311409fb5bb74fd3a803028a02a9e0056d179b9a01c33c12d848631a2e0dba97d731c3673ab836dd0e31aeda607fd27fbcdc63d2d61e8af2228d15738e79a56df646ed13e7c422c2c856746b0462d39d6426f2e3c79c7b71a4c6a0b034dbb2273f26e641e5e4b4c4a06d4c03bb4f47135ea70f55024fd746ba9db4b040c8e13dc14684f2ecfce98d085134034ed021b0bbc85baf5f1d93c6c0d07a038a686142b666ec7626de38733137c96d11c7ac67207d6cef5989c476f28c380829b67d2d5540da8d77f5b67ef9ec048a17ccd11f59b09f1529b0df87656dbb92a0926a22b337f38fcbf9a7c1f8dc47833e49dc36620d76a540aa97d3f73b4f7fbb5fc6c61b278c6b52dcd7684c60af4eaa9b8812956e338a443a8a8823112e0e547cdf578c44a163f1109a380e75f98553606906f50a78353210e354de44e80f30eaaf5066fb008fd192fcb207dd9825f6d911e00464387ecab628928321c331d2ffb869780f121d13f521da9f580f92fe693420ee9e560b79bf09dd907498d40d61ef74176941f4de6a2dbd2e19d52d9bb0bca8de333bbe2989fe97a7c0d4d7172620333b5898aa28e193ee71108a4b1d41063a91ac9c4ee2e63670f52affaeb653d03499264b9bdf6750dfa16dfeb8f2e71700ba0215e35f8822ef6cfd5d438f30c0fbe575433f380678a3af1d1d74d40890b34684c70d093f28a8f86d2326e0a2edae3be2cc3ca30ac6f63c21a9ed176424c1d4e586988c2879401987560c6281ab3b99b4c7d590987d380768017bc521eacedb10dacc859630ddb4fa19c7d366993a8269b3c8daaef163104a5b37ffe24a9ab81a3eb129b1c6963156b1dfaed80cd9efd4a75d145deba74f1ed3c4c0a72e67fd131187c40f859b289439e99532ebf62b9a5305005e15332d940b2bb5fa5e4e842eca12359653dc100d371b12386577d5d80c4ccdab32029db05c2e595938580cd07858d795d702da8a4b0224e158e7ac2a2c19b2715e56ca240b298c047fa56763529a20d92be0227dc631aabc96167a477acbb3a66a0e7e5119d26731e07d33130dfbed79df9de271157d2c2bbe7278b59c6a6c7087c7ee568ba638e06b0657b43af8d230515c207a6eb580d170faea7c1e28db04323bd5eef73683811ccdae0e41c818eec7a008b9c38abc83166e66b773c236e0aaf440323d21b0cf0360e841878c3d4a011a96b6a1a7ec85502969d29537e014e158cc580deb63c845a58a71ec250a548baf8bac90a6d567b79e1264dc0283b301603025c47fa389702de0f8ac476e4f4429e8e6561e431b191d1a140b419cb976b83c283dc1ca8de47f91b068d57d5a882d25d7ae229585d27f33df8a693db1645428fc3bc6b33288bc65e1a9b6438e85b4ca18e91f43472dceca670205da2d282ce990369a9664798bfc07667fa7256fcade6381dd287e1e367ce1eb9ae26b3fd24b78d6c8661a772180fff4741a768ca563ec4b59a4afcdbd26de44b7dde844522b6c3a04b940616506749aab8521e48a944f58ae6ed13a2d3150383ee8fbd1c6ff6a246c743a16917d6a19bc2a91a0081152f73a5d1ba8b00fd69f802f005a46a9c94fb5eb8b98d24e0f5d40bee91f84780c1785e5a41bfe0ed4fb9cdfdd783a66413b00426a72302904a78fc8bac5a03c323f3a5bc00b3d238ada068fcce35330b5ab082b8ba695a3d90fde72c62b3d7d7ad032485eb9078a86a8c0be521dd8081bf4f0b75eb501d9d7e37e6970cc5e11b8c1e5d74f4d86f74e66d9dd0c31024cd9a7ea7c6dfcdc5139b660c8564aefa8208d8076a3414a36172b0b164f9b1c994464100fe0ceda92f9b78087278127209f7e550ba649b16bb0d2e2172646786fa5dfb5fe73222f8c2a2eec4e39baaeeff2853c2b526ae32c6a7abe5f99c171e4989a67634ae18b34f8318d36a95325d42504d3bb126c5861c5c3e088d8a3c0e8987122c3f8c0faa9aeff5035740685bf752f105db520719119afc1857ab1ac4a0e6c89634457eeebd3ecc2fcf66558b039de769ace9b74e1484881e9510517d1454410374f9762109845cf2721eee2e9820f7776ed740b95685523da2b084d6d4453321bb2630ca4d4860c3df03d884c81f85e5c7aabcd4b575f9f2f6b50f9702427ac13f17cf4b4293f523cb2f08584331097ec0bee76f39cdda27b226bc9c34ae77484d7e8ca4bde91f38950e49e6070766b797e2ee49f89028c0c5be4699a35018d588d55b5cb35cdc343386123c9063f9c112018a8e8463469860159fd2b6d0fe1122f0ed41fe5612706dd5b39b84ac7c983deb90582ed8afc7a5a18f6dcc4ecf7747e5680c72c00c6bb9164acc7082132a6bd1918307de830662f8496398b96ceb845015279420b8823b96e034253bbbacb643eb9f805e6b9767ac334e65b6d62fe235573054ea789cdd7ac17c0929c3e3211f7b8960cd2586b0244bf4284227132b2d22a3614c19b1c88f8dbeb86c8fb1142cc9d5d41f4a63b20e0aeb71f6667990f75d3ab07b8eb8e87ddd96d87aa5634a743305570e972a48d0687faa7d4baa1bbc53009cb2604e3651c79d6b3f1239dc405bf59efd17ad12b8c6ad15223f76f07a5006f75f8f8290cf116dba10e75696b9211ae35a729c4d04dd225cf0351dcb0017d9ceb05c57506c09e02b10491dd08e8d41e9f667c1f9cd1caeecbbd8a16a6ac6dba5e0aec98a6e12241826c329afde2e6f66a80f4881229778650ba79ad395fd753ccd51e12bffec18da714c1b56b99c607c4964f10048db39a956e43615205a3e3306bdb9915fcad154caba25b663763bfa7236d21e6e48656da3f83e20ee20eefd006e881fb4e2bb19388507c1d78e3c55228605ac53d95cf1d67e060cdc30ed2da5f073f1d2fda2b58fac4202d3b7a4bd12e0ed2a65ffe8c3f422a7aba913996af08dcc0299ca8a924e08640e3b4ddb3c1be81ae68938cc84bc2614767f48a96fc1b2ea4487cab0a8f0f8ca7d885abb15c61bb9be8b7d043b33774ddfc69e934ba5fc44eeb884e1704c2566f1289f217f6e123df7d82089c09400e70033fa43ac4042300b97dd3eb9080223671ed5da39b87509e38498108be03a3721cf952b2b96e2f0bf0d7be01e00e36ec4a2524146cabeeef53f7a6f40c90af14c5c870c92fffaa394705bc9654a59b86afcc8a7165459cf22ee0b90a0432ada220f21f3af89eed2f86e8503b626a17f15ac0e72bdd3d6cab40fe72520f8da3414ecb3d88a80f3491b22841f496d8b85d9e54c1d10609cf36d658a3c54611f3ccca7fd4e2475c68fc5891bea7b1aae814ceedd42124088b9bee0da7d3123618cdd8abcf407a7d876061fc722d8f57ec8b6542d69fba8612d0d88ac567dd40d390ed507e66fac0834ca9c64cbe4c1c678c2788362b61f120ab70b2965946a0e25b37b50b9b130d138392d327f7e14c953e19993ec1069890aaaa2d366cdc42bf89a8a0f2282ae3e86722409da81f64da2d122e3896a4d0a241c81040362746bf445f45c3803516e99740f6ff0d2bff66045286552ce2fe751c51d3b1242130ac1cd208d333b0b71ce7d7c6ff79708e4d723a7dfde5010531315ec3b44dd82a63ac9f77de9fb133fdff112c91f8f8dd024e6b4c2593a331352e0cc93b40d9f489824c2c9b495efcc12e974d029cdb0f14e8ce5816d9211bfb5f2b7cb3259579c7b3df512bd0535f1d4b85f03db8f4186361bc124c2e3bc18828c98266eb4e021953f004c7d8c8f3e72ad8fa7510c849a61900d9bfb4578bb171497ba08d7cce85848ef5fdb295287463bada0526a659c0434343fad5548e30167c81c98a708ef0a71af85787469eb3178b78c4ba827a1341d18c95c2b1673be5b2bb79d8ec5262d033ddea16ac05a75c8d71c89957a82754b6193128170265d8547726481a7919b35ef9768a30f918a8d884238d3a07313cce46b33cf326ff0ceac23cb41a6ee961f7b241a8f00a7e0aac337c22bc4d13ca5061ef86aafa088ddac579bd38eb480663a7477dc83216595ffb643ff063a2bc984bf59e8d91923502ae39fb7df1f4c9857a2e0586d6915080f96a18d347d1fa15ef79cdec727ad7e3ac5790c259c9936aad76262be021e950a6da9f9444e88c859343d7b758b1a064f2763166faa12182e31a691fdfe1ad824b1ca05bba03e477295c1ff5f70aec6fc22a5491654dd48ee5f5f85174de4b3effad8db62bcc50a349ed5d3946c37f6fd96b1ce680242c5adbe757cf9a5bd729cdccc413704baa95372cefb5732a73d5b4c16bf20743846bc7a67b2bd30e1c7a234848d887b525e08ee687954e9a9ef514c5b11e91dfc428a157c0216b56937224f39500b00c4af489a64be2ee5f30e25018ddd1745ac50a42f2ce2ef6b548935f580bc8763ef7296043cb032bf69a791447545bc5292421675f15aebf7766308f01d1258e3dcbf0bac2889cecea2092b22f9a4a6405135c2df287450fa9b6d686f964e47cd9919c486e7cd2f63e0b1e5e59957ef140151069d4970ef6d553c9130998dc14e40264be79a49339211e675cdd4af3b4aeea98a4bb9bd8f3b82fd780948e0e629d46df3b275eb051eb1b90eff9568c71835a2b5282ae82f455bf769c6cdee6eb96fd578d860f34bb9b8529d932d157af2a3c358c68eadb2ea4ac2a9a376cd1d4eee4a6afd14dc4b6cf196ef4dc4de3d0edd24068c54ecf299c91279855b3316ce17eafddb44e71c20cfa597b6ccca19d555e5bf923348ed82e8ad2d685198ff90cdde8e5905a1f00aff5c938b0d9b3e2c1d2e2a327b241b4e43254ddd13886e30c0c123207145ed86942a8cbeb9db8f03a83643ae471f47d7bc33a1107942368e6606682798cb012515f312bfab1baf1fad84cfd9e2e336e6e1556b12eb803656413281c515301fd78818e2059949488637349a037bc86c0f4af2f1f4d0a4777fc0b7e0abbb6d724ace292992bd5366d8ceb458cfaf1431e0dc20238ff0e89d6796ec2a68d581205340516e117524c517b835ee83766e44ab81bb17bce48691faf52d3c6fd033d6997ed988b6e8b776b3bc002ceb33162444eb5041b149f0ba3f4f88078e2d6051bd780cbacfc2c33ed57d4c904bee128aacf5ca049009433142a5d62a7b2169b3510ace6d3d30b6fe658b9bbe4f666557aaf189a04170cd0e6ac510a6df54e7c2f1f6c2af5d8451fbaf7750dfcf035e3b3442364a82580234589e011940bfe190809710e8d144171f5d981f384064516d6756a07e1718a6dea8acbd3338ccfc887ba56d6fa419773c3783251eee9106af40cb72c107928a28159aaff4371cea784798c7a92c3872728957fc4a1373d4357f8993b3c859a213d0f2376b9e8e27eafd3c4276f28f6292eb02d41ba2175d93734a013885bb4337a1d744f19c0cea856b0e634db93bc807209ddb513314a375caac36ffc7fca43c7efcbd59e4fb5327a6283b72b04f12a3df57e7f2041462f7ac1b3d33a46ed945c3678b2f533d0d7d17c603112c44af37433682725ef593411039065e00a86ae21f32748944c7b182da5f7ef562fd88439183d0067c9f8bbdf8dfb88f5769b0f26ff821eb99466422f930199f0b9715ca9e20d77b168250ee9d5a2abbbf5ee55325ceb57e34ef10a1dc3bad0df80f15e85fa663e8cfbc505a6f0aba03643f9b05ed7f8dae576e9dee1bde58224df7365f17870f72c7002dd502890aef07d557b7d2e3c8bd1a38da116e26a4f6dfd47f68b4bed6ee8b67b5dafbdf0b20df63bf4a76a26041e3ab2631e71ea88b59af870eb15d11f6a616bad54191de4144682d861bb61ec09f3a0ffd86814e53705caf7342ccb5d10db02e5515f8ab9b746ff9b401c21cf065e7bf596ed46ac6abafc2ed20c07f1e8dd5ab2d0b578ccb6b7d29882f5945948b0afc8b22728ad2d915ef3edd546d97f3d9dcc5070f6558e74e3048888a1534650fbc80f98b45794ebea5696a75db2d9a9d741dd5e6b367186cdb6f46de59fbda6c212c5bdf66baa3beb1cfa772166d3dd9fc9bf63cbca3c97fc146bed3e6b725e547192a4479a6047e73259198f73c0bfb9eeb77d84a16953ea50d3d720e9d076cb8d249359fd00c4196fdf9f5304836c4f3058b3b7c01371154a36a1e52804d695e8061447aab5ade5e30912f49f87b998d19062dbcf41d11bd3f7f355498185c72f57492a614dd63838f48a5f2a15f80bcf5099f0e08bd0c0a2a4cf7bd27a114e54e02b0d8a782c9805519c8aea1549d5b40dbfb1674d0b030f6cd397d963d5b099ef8d88d4ee8f4de6eed1ee02a3d5443ee0b1ef32d350fb45a2a0216fccfefdafaf9b4489ef72ff796a8cbff027d3acd7c5a125d4d01378a41df3f6c57aa47f9ffdb1fca78b0d953fcd15eee81310889e9b5ebe13c2c46bd99fd8cc017ada266f28bbda05c068f6b832c5b0b4c6340e955bdfda9ea27e02211b59872331a93b518503e2d8414c1f510a7aed8e58f1e4bd64ece33e8003882e09fc7c9f5bfccda79bc718fe802bf7f57958202bd4fd27f8f992bcd7b9bfb42b22ffcde9e0a20d8e6dd5584874b0bb8a00741e566e2108d9198612f7bc2edac5ec4af6efb7cd045f6b5e4cff79b5bfc6134a6e6addfd0c20c7a14520b524e63cb868b5cdf7956cecd2790121329e3b6882ab448616b4fca36a3ca1f79ae8bb9f7a6260366793f8194633f5f436abd0975ae7a0c771533b3aabb32e4f77492848a23d3303eef596762b65a29e284ff4fab0200f06a34f133944c5510753df386c5c833ead6b8103394d58c3036a685808bad22c82680bac8e3d6c674738fc4e22915d1ea0441a7f4a0603e7a7275963132c8a08fa6f5a34147509ee94afdec10fe2ad508b483c110e345079621f46ea85f7b61703eda5c75094e3147d5afba1b4b64f05464ec5895fe38e473bb024702f6b941f464e8eb589a9c0e0a16f8158ce6a4dc85a016f4b00ee52cb5f15a31d1a49c3daf04be7181116edbea18bfbda2a406f2fc72a384a900e0ac5751795621bac2eab817ffa2e6599d58f8009f2955de63674005bf94776bdb528ef3814eb84ce094dc2820c9ae83b10e5e9099fc66be08557fb1796f3500e94cbf3c4e515067cda9d3dd713250f54a0160a401a56b0c5caa6831e8f897e045b4e106d45eeef6a6f4fea9f561de1d0b5fe4923173d6dcb6a676f07b439b5c97fb379b230d01619123b9044e6936205d39b803ec2b55f4017fbc3fb68b052e31a2b051416b256a20808443b5b3971cfe7ad8e15998e093a12d3b1a09483964ac9d2ea39f1ed0734993bf34e739bc8dd4e3959ff7576b6c3d8601327ff3d0da013eaf52135608577b81b6126d81416b19fe161d984f6c249e5c303193edba3540b52029279708bc05b4c490ca362a5350d625348a17e62125305445afcef194480cbaa831653eda7293d8ee827f585444dc209c40be6d64e049ac28d7a7f4815e8945ae84c25ec14daba002241251b173da3fd39426564eaa7a350eb713ab841da781d5ef9b6fbaab9888de3a847f2f79ab56d83c2585bfa33b2bcdf12766b46b84789d48a413fe218786e5c4515c0e460764404e3e141c8f961d4a6a1c2359195b30d8b99d3983671453cf5d14d44550a8841e04478ae753fb2a3f21d03357618f7a053a80259ed53ae9930726d741606db63715fa4633c6b0507c773e16af8d243a336c377aab1d9adb3abb65122760b13a7c8b63bb212f03ccdcf0d4b528851641ad4afd9da0be54617970842b9cf02b0c242876bc508a9a210682f1d6130499b75a5f8f8573874bc28d311f9baf85a2c14c6e6733121c8ee1d531a4902326fba1ad1d4e19e191897507d867523627235fe5a7bccaf82aa5c5eb8126ab30d731da2f1cb362a16acfb2bbb03aa9167b74a1454dd779cc83c7a1f71264b75a3579daf011d25c50732ac7c358c5d291fc3a52e6cf36825e01d8dfdb4ae02899a32c78c04111b8544b851713f43161bb7040f356d56aa22451d530a5c444818bd6c5f18cb902456b6ba183e74670337807cd753258d6cd74f71c04a9a031187932dfc0ce9ba2ad80299e396ce90e64a0669b6ffd1fd41e200228f16458dc0c328c6061d49d29641fe4a29226dcdfb2dc19547bfaccd0472b8695ab526104e34bafa846ed182f227bc6bde74d0f46a2cadd77f435cdd5e60b706b342e64153fefe7c70a35dc77f982e1dd34115ea24353041f3d389bcc1f34d795c4aebca3028a3e88c401732a1fb454f126807cd15ccca941e9c85ce33adb6610f802b6d6075343e325c2547b670d9b57a3a4fa873d6b53d8b587c86cc69efb5ea30132b03b7c08349e0d1f5913a7e4c37944690e9739a66adb74dcdac779301b6c9185e9a502167a4c66d33482f8f10901ca7580dffbee4421218c84d9674581a82b28e9578652a37602869dda87d931a0d907a49b88100c3975969270dccee915103f7421367acaf1852fd0e30c9b4472ca4797951819a0aed0cbc3b9d37ad0b26f73f0ef057498176d55c0c1dbe8074215db4886d84d8738d28edc52a7200ad7b91d3fa241f526541a610acd189f87453a4996602f41b921bfe0490bea02f9680412401053c52e0cf3ff96f96915aaee460651d710567a02933db718972a79323ecfa92cc6d8d361a8d806f81931ad8b16092675bf0e62f4113c6c7c23f4c03751c24ecf26114139383e8268168ab1e58cc4fe1e91040e9ee88b9851cd757e1a5895ef8c465e94ebfb934e7681448ea5fac5cf503e410b709deb98c77c6f2aa0c134cb41cdb261c21cdae430d90315c01cc64120caa4cc04146c9c4cc599858c59848fbd3400e28e46cfaaf3c92be7e24ee852c4437fe63332e00c39d60078e72e42b68c4e38aee20a431d4a4548661bdf5a856abb058efaf56dcc43ad094f3552cfde6e6eac98f981314f60beab63dd3413d213ed323eba884485e9ec00d8e00f29996e91a2ca2f93cca0cb31a3c20ce34a2741a455616b75c3d3da10d0865a77f663f0885fafb752abc6a1411ec39ad9c775cf2c5edfcc3235f8a01710ae28d68fd4beb4edf992395e5882180fb62559416f43c8f807ec8715ba74faa7f041607f828c41658fa4fb7501ac580f81f1c5c70e967162ac39959f2e9bd0697f56d626f4359dd425b969429467a0fc075a7d2c24754111625e922dd18f8686b3c290ddfbf7113b025deae432da7efd8f7420ef79ff80115dfb903bd594188afb4b38a5a60858bc7e9ae9873cc8751038f211b9640095147a37b1cacbf4e69eb4155147003e1ef79e37e752e7f80b737fcae0e4dd1ec178fd64aad8b4ea2026708fff27f00df1cc6c8afcc320c2cd503d0157e7359fa3732a857d2927309c6b2c7d1e4b1c1f4afc10d9f09b3765a57f354ef4fb21334f0f3462596312862a0189029ba6cca706010ab5e08f18c5c7016e00535a603bac458eb1df20b301aa496f7ad1c7d61839c818c58201633ac51d7c9df2496b4021802cf992443bd654025413e55538979ca0a9827cd3a6510f3205d202c69ce4bb1ec73738f094b2d9d47f0607ad13e6173ddc2ad8eeac58cd5d132758fb2c77159453ec64280b90ef0090895b6a9548313e00e73619230b31b3ce0c12c55a3a1cfb9441c8b215473d39aefa28c662fe82651d139d27d31974a9c4fb98036113dff67cbd4913cf773bf34a56ece4c1a0e1bf28bf89580a0fd6a7a768b067a661423be7b5e3531754c27cbb107543d8f27b17b7d100bd209c2861280f37276c348926fbbea054cf7c7c366661c4ebe3f5d241fcdb3994f2dee62d0a159c70a1a1dc008ebd41ba1083ec2de8e737734b2bfde6887f6705885bd3a4001acbfe12ab098ac432044aab13e1432cd4bf32bb64aa58c3c0309b01be7764d944538504e290b6d444a31785380ad48cbb1ff9bd0a48dcc51110ec07f67617c6c32230079e973fbdefa74867357fa5396f9b66ca6bdf801e56e29354dae12c8cc6677a490d38a3bf8a97fead9a204847b7cfff6d6d757ab8dfe3b1cfacf780b5efffc1bef060522f15f5e5994b6cdf40b2a6a83d7786aeb0656d0a654cc9804e8a1c5d435a003966ced5000194e01b40b0663f64c125c7d1ec8fc95e87c8c793b2e19b078b7d51aee3ee1fda579e88ad61302ce950404968ce9ec78f6c86dbe180c01b1a999d621077755c51df1c78a71e09323af7139aa6307c7698847d73be0d8215a80d2f25d46dbd5586b7176c45c4af613ee249229f625e3e02a65aaf024f4450925b1abf9c38c4888209cef6360b5a8494ab60d84593af397d11ce9d2b7d9e6cc81e9f202d9a46f0555713b5fa5a0b7f495d0994ef91d270f86069ab0a05ba11aa9d9f10b66a58f8b2730a11bb0f0378133128127a688c0c5e821b0856c29846fbce969890246557ff20310680e2d83ed02c60ae36c887004520a7c4342e4ab8206e6bc6cb05fe9e97bea5f457831129aa2a38bf0bc12878e18e3711e414d086ce314979850d8f8f17dff9da74dca0cc4be8d1e48f0803372328b16bf03d4cf9b3b9fd58ad6ad657ba7a86ed59cade8c2e5a26a8a6903da82139dea0c95b7d83a77cd121281021ad37f32ecc74069a2bcf670f213d679a73bfbc46251ed7bae70fd18c65525e45447fc20539eb83087f6141243843add558eb080b509643b21d1e986acd5c740dc7f346cabee96b9cda5fef9c51368b1302c1382544a879689da9c5ef4b2dfcd6aad1f1efe9e1f0b202534112baf81e66314914041eec9031959cdf1cb5dec3bd69ab71de48177c3889b1d6c0b5598c14902c90daac004f8b6dd96f8a4b5b8bbfa36c124abbf48755a857a09d1284eb1702b90b95b77dfe651aba593077b2a37c50b7409624d11540179f744a1294ec8764d5df51181346b7cfbe15b161490b387f852bf2e6e5d84225bf1fe9815f5f32d512776d392cf988fdb662c38ee7a0da119ef44e3ed5b21c7247fa0abae9f296fe5132f51cb56768653f849a52ea9b33c5c85603be5fc42c0cf536dfe2bcfc5d8ae2ec6d306c81f5a3d9732ed6b693102cffe57e6726562626fb23cd23ec16fe34ffa97af7b28e0b51ba7717c855e40ec5d0e9ca9add810e6154ab4b56663e5a940e809cd2da4ad49a77b99854b9e7514c36cc62223a20b7574d5629e820454f1ac4b68e0251bab893d8af038451e3226c0b9ec82179158ff7e60115bacca74575622b4cbb75fb2b5f1b0330ad5201019182ecf91128f405224bd00c3b45faf73b706f173811a74af9c58a49b7a2be24bd63da41f4eef288a43b0323d8f2f4dec815951e9724af53585c793bc6c2d3c79c7c6fad2309f5d06073632dec2053b1a6d683b63cdc833c664ea4f87f3d80f4f8a07124ae5d3806350a9e54561e6a5676ba2e74ddb34446b931eff8c44279cd2ae47f03e4005d49a3e40f6cb893b9d1a5c972a074d84af322862400871ca57cc054444179d9d05d006654fd029a6fa557ef4311386053c79abc5509749b6bf56c1d36b7c56dd99b74188be239908a647cf5e40bce1732bff0d44d92bd34600a756a050f9cdc11de36ce5c179c2b4b7c91850b019912bf7a1c7af46aa03c89b44365003c680d5baf0e98b33603578eb910ac83a6b499216787ce221a20b1f389fb050c36c7716fa5531172b6276e1c5b7b238b9b5b1b79121a8a3f7dd2b132a6d10c1674ae2f33a9206f564bda0638fbdf7dc0c12278b122e2f4a2abb0003ce028099600c4f935fc8e0520642ae8c9d5bc5461eaff5b0812edec5f8e16b68d708c01926eb53b0391fee3f197d2baa8c95408185060550aa72ccaa2f07da3f66a5982f02e2ba259edfd82b0f1ac37c7689b5e20e1ab6f219fee80d9f8c608487e6de3b68b40844411d34d48e6c90071b0d5c80a20165a26eaa0df1b40456103c798055c42ed705598a73ce72b16d50b8a74d421aeb4cf4eb513e270c4ce49dc426f974f50baaf74fe2776d646a7646b9a9a092d53c6684b601fe9e23d76755b163755291050ef6d9357bbd64df9c011ef2efa0bc1eb675742a6ad278490d0952f6e51e9d59478640d2e5420d283f8718663f5eb3765d26ad729ac186c52b10885883df0c05fa1e1f2d9885285a0f31c879d4830343e8638932c61d9a9b1e755836bf55b0270935daca5384f9f0664be32c333178170ae09393801648d731a14ae01262368efdc9db5a38c52efc358aa263488aba74f46b3058c2b8c63825dc04fa6e9ddfc513b98d3b44b7a5125cbcdd89e9a493e0d5d7cfe6cf208147f4c1a567de44dafcc374582b33b63ad591f93e1da184491dd34323131b0287c0591052b96f2724f786b247d0f2e84ef06b9eb3ec84ddbf007ff2a4050778bae8042822c2cca4f6960185d5f17706dc0dd0cf4392b829edf5f58066b06754c080acbf940dc245c180c3a24fe050458122cb86b0443a5c3cfd23d09d3fa8fb20921ca39224cee76c30761695472e486197c57c0d5f015dea8a2b6ef28a4ab2634dfad931cf542f3e714562734c61530be4755b5ee94a3bef2fea740127a12735652dad01429a27f43ab181f5ba3b2de00aaaf2175cdd253e1506cd94796a1ed9ddd91f77f6208d6d289d201b550fafc784e4db20af02b1ad7b9ab1d5211ef1de21c8dabbe8a07354e5df88ffb397dc1a33a35e67ce364b73788da56c9257f072c24faf20f7eec7274441ab255746108234cf4c2046cbb1238be01fd8f7091142ebc747dfde7e006dc5791d9b96d260daa023749c7203d97ec3618eb267415b80eae631ab8bb34bc285977bce8d2ca8754c9797ab24961d2b514337d8d53f2c0ad9c7fc57301bfaa06a77e13a28472bb10d719d3f4be902e86537fa60c277c4816d8a09d9c940863a5df4df47bd6aea08882a81d6ff8c7972bc43a191f3b37634453b9cbddb9b10b32b557104f042d0804f9402e364bff0e8fdc54b7c2e41d6bb2cf5110cb2088b71f8ff7f913da2a94cf452ab87e0af09741fdec670e552de82c514018ed6730440bdb51837c95c386a87783c34b8e8e45c7153e6dcc09d47d6b31f24455e8d06f8859e9648c60d682d153787467cfeebbbb0ef65c3131cdc4d948ffa2540a523fd5809a934f202849f67eae4258b862491543dc186d92523f57fcc7172fac45d13ea4fef75021c2a5474ed29dda5e4343d0b244d2d1a76d6987a591409b4e2368b27ecf408c2c75bd451571dbbfb2e06822b0da97fede35a07c4c9c3e0907f32d9bb370ca68b0c9a3bcc79ded755d83b0c1275ff014ce3229f88acc320abdf2685f479fad2e75ad36793f5f4191d66d69238fd2256de8c6f93be8ae220a2b88c5dccbf095e7ea1ca47a4b879c2d34150ea20dd17d87e5c78a03905343249ef57246c888c477c04a4bbcd7d3d779cd41c1777f223fda0720cb3398cfd10b83bb6e1c30822b2d68500c18e8ac48becdeaddb3b2dbaa21b787d4601dd3f754883340000e444fc21382a44b3f124230d5b812ac28347eef707f711804ec676f360416a36058cb5c96ebb1991acb4e1da8aa2999a19bc63296bf2744794ddbce3d8bffd1983229c1d9c24ae5bc290114ff3be4cdbd03aa3d29fd869cc713eb03cd1b4453374940f637536a49999447b55e9e11d8fdaffd283a0d4d078667ae816588470e9338b4cebfe7824e191e7f0df80ca7f13df5b63dd5ef96f411b3904e3a2e743cc51e9b1b9b0fd9b5d9ec121e7a93ac2bfc63b140c32bbb89bec79e3965b1362006e24c56fef19322967662eb5b9f87dcba0cdbe14c6bf64f1f6b83fbc2fa739e35218e0993e9aeb80bf511c9dcdabb62a27924ce5cc706c3eac16be38744a46a953338b2f0359c8ac9b4d6ca5a0d3037b5e1d281477fa0ab533840650ee9cff12cfef0bd0d2c8485539d4a55ac31a942038182e9d77f7f711f9b792ec03cf7d1ed63cd8e78712495279b5750773e7b3f8aac20d0097f5dda53f18e5e616b0d4930051fb2729a1b046bc6c0a732f8d6dd85723c0d8440bca9253a3c2940f75e4e1e32ca9d062631c83d3deff680ad766bef9ffb2e7193d7544691b67e6a071d635a2267673a62a673a11d66ac192836e84ea0aa2b7632750b2328504bd92451555ec73f39b6b766238336df17e97db764f32d3d2f215cd3ff079e677488012eb643b4ffc9c832300444393103bb974d946a9ba930df48beb29dad8578be8880617f3f00250be0d631490aa5684d150373159485c5c5f963fd5ccb57bca3794755ccc65c6173085b4ebc4fec276453212968c3fc8bb400ec6af6048bb882048af06937acc8ee07ff0aeaccb76471d70d11619bfe393f4b71daea63d38986245b985d772c64fb8f906ac913ba50f9c8d1d0387651cdcc2538fd9ec16f5b0949cefa4b4f0a4dd4021b0426453596345ee61711efc93ebceb4b3b67d47ae1bedab861563b448b4c6300e5ce234ccbbfd570e18156779e71d605bd3fb0b6840142c7e51064b70385d6dd7204bf15e6775db993807615e56aa99f471a10fe5e92ad5a4549fa1461819933d8baadc48e56ade8bf7e4a4ea6d71fec0ba5e1012043e94407feca5ced1961e3ef763e7e403a4587c982b1033698f2b6bafb940211c72b8d65754d38f13121e428fe29370f72236086477ad78e1384b9eca7142c1a8daff3a6e2596d4206720da8e9b501f71dfd938b1ffd40efd2a9751cd1ea10bd0a0fb0945ce57648d68e738f72dce8a2b2d01fd1aeefb9652c11b521b574da961975418b6b69cb9494db043ddab31555ce0897821e600e717765c33522bfccca9903c19efefc3a080cca3a0f4ff2f0c5cb0f4140f655c1774c23ddbfe3d7f25e3fbea50adbc77de831e463db7b96cc65726be8344aabcab0e8a91ff8f10cce342f181648abe15ae2fee8e449d9ebcde585c1e54178f3073f881b5c847371d2b55308b039e40711c4190c1336380ce6d8710c8b06b2ed11dece1ca0fb76030b6499d8e45fc8fa7bbc13a4f71216b76ef050422e9fa901767c7bc97800312fd1d82dad5886056c136354439550de0d36024086b3dc8528a332e56618f28d0e6889a84dc31d4ceb5b5c1baee71040c792d86f58a608e22a5fc79233351252cf327def88d16a11de529881a9b5775b6c061f49247af68cc8fdb5eb1b5340a852efca79e39bc30bba78add0df49e488f0fea3d51af0feb3d91de1fe83db11e1fd07302bd3ed473623d3fa817fe8d70015cab039a7ada805820cec24a074dbd1637e8db2b83ba36d027488e20d5e308beae2660f82c9090be1dadefd38f240f9e9db1f153fcdf5349c70528244f940012e4c29bd2a3679ba79217cfb4d9b2ef08848899434812b2f7de446e29934c4906a108080890073cd880244aeca22a8c47e4fcf3c492294fdc7eb7b7eb5e00a5f3ce6be71b2891ddeeaeab81a4c90f28a872650b196c2c4de1850c134a284dd143b295420c26576e574a29a54b799530e18005e72584f51bb158fe21458e1ad6bfa5611bc0a3bfff037f0a70c321e9012155c4346e370c8e22503bb84290a8a63f6c4984d0eae377108556e091500563ff84066030362c24ba9c12cb1d884072859c32368c4f22481985b03c0f685853092588d030b9a161b9f24b2959729d25d11d893a04db7523c99c84b3d1c6105a983ad4dac2f4f171e2ee6339830e8e48971d8719743658ede3ed4868812f832e64e84eb06b580dcfefaf7dfdb476bfba5bb5af5756f48ad2f768217e75fcfe694fbf2bab9fdf15ef1f7f75df60bf8ce2e4371f71bd839ddf244c50489caa46cd3170213b33b315363e162a2845c145183264c02813a7560003374c90e012c6cd1157e4eb151d943dbdc5f06b893b51b384f31a803368c0e276bfa28cd1c4ad8086d96d37633e3f05c32097bafefa9c60e7d2df364cd24927c882df22b35d10b526405148a8ce48926aa2399de0494dc2c2b5ae6cb5c2afc5cdcd913d764a9fd648bfd86f6904f12f8a40f225047f10295d826101ae8c6d6085200ddbd2b06194aaa10c185a5cf927e8703054a266922e5f1f93a6e9fadc38c6055f5c0358246090783952759fd25c9e5d8ec27acf8a61de1c71c60b1cc448a15af9708226283386c0e286d8593356feeaa3788a67484c5fd60891860c6dd0c4e99133c6d2b75fd4561a4bb4cfd9af9972b07ce95be1d1dead8ff16ba47f7ff8b7152431566b432eb24cb09e59cc6216b33e2c4227e8d51a67313e6c8867e0b7c0b75c2ba51188a35774d47db143946cd84c2d7be7eb4e2e7eacd73efaf19514482b4e9c38f7b9ced3b4197ff5130ce373cfc9cefbe4d758645ec97173ceba581ef3d4307e1992dce88404cefde04627245097b1303779f73756fb4751fbd57332e9cea4293751554f697a4645354bf354f551667ffc56af11798188bc128c112cd2390ec8f2248c1b9d6498c2b32e367e61a4e2aad85904b2816711883fa78c65a5a9a4c4c4cc542b7f9c7a85f66a317623acd619cf629c857fc37a14a9ae8c3db38cc34d3b372435f972ea6818bbcf95eef973ac366ee340a3ed7d0373f8d42b4adfa303e9171be61f247dce863c094edfc1fa4876a7d5dd3522314e9c38695cff16fc56d088fef4417f3ac83f2618f3a94370fc832fd9929c8e4c695918a77232626689f3d719d8dce834039a04dce8340396db4e34c8eecb89062cb7bbb9568e0ada034295305d7eaebdaeda87d957cc49903f47902fe5468a9b4e073d89cdf86bd2a785618bfcf8fed303e23fbd11e41b4dd0829593ae6c249d0d511c71060cd23445618401bc305285d39b30a6fc90c21b2c4038b1060a0a59114d42a8a08c2c090b2f8650218a61a10a8e1526807892040f5e040a667c4102ca862356f0e40535375071048d18ce88c253d0451070acb8e269862fa8549031482166c105142e40a451c3b40513152480a429cb17305fe264f10508072a6e0843090f52ae48410707aca4008397308aa82269ca114978216208282772d84245c914153e38f2c311355232486d21828c1411569694686a83830a114a0c1459105e88c8b2c587401ae2043224692203032da050408ca5a732b6b00163cabb2001214615306ea8a0d2848c0a0b18f3a68b163c51d1851b59d0135f9e805aa2cb1a30b0aca0c1d2992a284c8001a5831000b0028c0c6da2e8012a88146258220a131534747144155c5a600514497ec8a10a064d56088a1f39f092a20548c4e042050c2ebe05055254d218424c0a6644c4a0d2e6ca130d462869d9820549ba2083ca155108a1420d4faa8cd9e2044f904122080ac880258b2d9638d13206016160b93244162347040184d3165862e00515a62e5e5400c09b2454ac30419c24315936587304124b901155c6970a6411668d0bd2f05085112c3b4e67c060c3a50c324f149da93244941aa09466b8125ec1c30a7c0083260635a480a444061c22ae5062a58a211c0538e03c6103103e84810ad3fd8ea73adb7666f0d065072d9698a2a647f8baab2c5e1007cc9724b210d1c3dddd7fb8ee5ee7e7e3fe802c6450c060420c0b989c9a2cb915272867dc70e2431247b840c19229ef14c393fb373a41c1c2094a1431c926723181a82e9600238a8b2f667c30e3c4142a8c880367872154d8f134460c383cf12107a71ed2c8aedc50c6892c905ca1428cc2cd114a34ed10c68c35ce43bce2c90565d84851a60552f494616202198098b2051967f22933c514a82e5610058d1ef1630a4eacc06e7ec65881154b584c31f5822698297e54418c982885b41c5d277b1773b44f6b1db4baab2bc13874997dae0fd786757314f452e50d2996bfb9a59b1c267568686888abaf9fcbdfe5ea1fe65c663133357247c3fc1d8538e55004f28ebc4204f20dd860c3a130a2b92e877ca0030f441060fc81bfff5124a2ef492ca8e6caeb9f635698fbcb3bb8eef4c80c367669ac6134d2f70eba1da5f4230a11e80413d8b0bb2b46c24a9c44031bc04004c104fe803ea56fe9903481c65ef42315d766e0ab55a4aabfaaa0376c3e472239a9e09479f3a689372e576ea561dc50dad40febc3efca1dc9fabccfa261ebd414dd9e822b304639b5ea33604b354c8a80044dff888024ac3226d1e24a1108ca862e356625bd542c97f9fd690c147d7e8f221271cfcf7d445adb177660c8dee491e0be35b9a63da77d3e73305226bdf44f939435a1245fc87b57f224fd23a756dee4b319b6e7e05f3baeb4b4f26fbee2bd8493d441550a79a09c925346530db34f567ebcaea583384a55deb43368b68773d991348c05e724ada561ec4a11888d3499f4d24146f257cf2f651dd40426127912bf51306497922b77a986b1d4a6fc8c1936ecda8a94925407f1c829d8546c989f712d2ee322fef0bfa0967199cfb8fc47e4992924532f36520d4b4db1e4b0ad5c2d3c6b1898867153c39849fce15f555ea20a39b8503f605897336e9ad5b7b5d6aaa3617247c3a4d720514a59e5b681213fa016c531745cf90171834f83afe7e3f9b8ff74befb6c4ef82d507a45736a5a97a3f3f68b77dac8b1dfb09d0f632cc1b008bdf67da258bef6873200c3a1d80e18c65e6008bb21eb5a384069efebc6732ed7bbe6f3f6357e6ceffa6e998deff8026d3c0e30b4b2a7fa4c4b2db5d4520bfc0e5c7290ab6c50d90ed211b25dabb531c2ae4eecea0ce93c7b453a9dd4b55adb810e0edd30269babf1fdf84bdff535dff9f1bdf1f1adf1fd88b7f536be78739e9b5dbceefdd85aeffae8d7f8b8b5883ffd5fdc7e1d30ac64bca18a40fdcec60d5751c964526883d7e0c3da23fbf0d5df4b52680350835a415ee201c3d70d3bb0c8bcb6c87213a744eb73be9eef1b163677655a50828a3241942185972b5e082926d0224394186a60cd8cd5be7a1f1196521699cc3df5cb7df752264db989aa7a4a33a39a499e9afd132c8aa594fcd5e788bc70354e5eadca55fff68545e8d5406ee2ab31952290ae54b9a20a1d7e4083872718c841083654634cc1c407ce9baa85e2134b494949469a8d16d913a64a32e686ade676bb714ae2e58612895c72c9b20e72bacd3385aa04505748c98c68a1664d8f4ea1082869aac8987803a7477f53f5779a213d4b3d8eb4d3311670d1470a94943152ce4caffbc1b7c626c4f890d4834eb19d04bae740ba6491aeeb6a5fc37a698cce0a8f5cb7d6187f5660eca13daddac70854b5a37be4ba73c710d7a66ad663d3f1b0da334cdc8903b8b5befac31d9767a6c34c4ec1abcbc58f659618b903d8b113c7f6eca8377a310323965fcddd3b3a2435bbbc41ce5c3940f9c40bbbf13330629b9b59b692a258b9eeeefeba614413d5c8f61e1dee596e38745f3d63703112c55967bfff9cfdde11cd9a3802ed0b34d63451064d932d63a25461460a1410d1c41764bab489646801861733a67428630a49cb15a92c4ddc5459ea32e64b14d306083763b0592343982f566c806345490a6250831354886982818f335b9ad4c045952553585ac060b3458c23d2940963441962d874d1c3130a5220065406b2a491240822aa64a862228837606ea0810728353b64315305144730d9a10566f830830112125ebe585a326bf2e58d143c2c59c2e5cd0bc26862892c6da214416b5a40d2e66929ca940bb288542c10e38d132931f040450999041306155792a431050592113d3cb1f1a24a0f4d7849020b075928c07b320176cd4dcca576fe1a3d4cf952f6741e9a6dab885f59c41f88a3ebd58b70b3574eb0b9b9db997bba4f8f605c6b2de49e2e9925b717f41d0c27bf4bfef4aaf9d1ebe8d56eba1f31f7286b7dbdc009d28f8f62324b8b893e7f68a08c2e1be33b681b268bc8eb3fa34b1ae5ec33e6fc413f82e15cba51fa17bf460f363c9a4773e97a8c3d264e173899b061779fcec92ca8524a194197525ceda09d178e1bfeec365c3574726c8be575dcb6aa1a9d2e9bdf775e19b082fa8d64c3c048b2bd281ccfae32173edde973d5a646416ff797d47dba9be93238e919ecbc2e8e5bbf17f5aed81bd7c6ad5fadde95d8eeeeeeeeeedfe6ee7ea373775f551ff3a50442826bf429a0ef5ec1a8b1b3c60cbacbd8943d123ea7524570b58332989aac1d94c1ce7d5d1cf56a33fe8420f046a89dfc5c97ab9dfc6a5c6ed5c94fe7721bc775f2b397eb3816eb72ac4e7edee55a9dfcbacbd94e7edce5723af96d97abd1695fec21bfd5e56a74f2ab977375f2d32e6783c3c1e1e8e4e7977b753b5dcf0e4f062c097a04f29f24982db3a0f4d60fe7dd7eb2e673b32a09f3a74baed5fb62049adf7ddc1168fef679049abffa2ebdf3e937c1d8b07a1d648f650f5cb4c04ad3a6eda0d5f3ecaca6d55f7940e8afbe7a2368bfd264055d7b9e40bc11fc35309ceff37dd2395d42c1712dd7e1b2711dabd5aca353c36d546f18fbb8b85f5c9ab9c19d4ea78beddfe9579c32827b7a74b658d6e1c23a5c6c98808b3e21cfed79f49cdf95ed392e5ad52885648ff8b1fbedb747ea315f5b45a158fcf1bed983dbbe212ec81feda9b8418b77d150384cb9da8eab758fe8f918228578496a1ad357c209f2477b0e220b6e747a82e6ce20512b540d6dc0e90635577b1d1dd45afb1029c44a55d69e80a504261f2e48210e1c22851e267fb4a794ce15939333554aae6e3ac358acfb0db4f267689b45b4cab16c967c1b0c4fb8ac24c6c61eb107d562435288fb2dfcda7343be880bddd465178650e0354d607d0b7c817b1658e58f07beb07d07be14da5e7b9814e25e0dd328a5b3cee8fcae706fbf21f923b9efd508d03e7a25c4e48f86c586dc5f9086695f6badaa433a288c5445b4201d14dafb392088a8de341cdc840c26c66e30d958ff68cf7d0c22853850d39ed3945a882669cf4b53aef6ecc50a83994949da132cd7899a1bb296abb1ec6a3f3f075c81131432142668b11a38d8f7a148b26f1c996106a56e336c372b7152146ed641f1a5fcef8940f25faf57adf6f5c3f522c25783082459e81bf6f468496ccfe51ef25ffcf29fa8b04e4e96aaec1aa6446906d7228a2a89c104928a6ad6581b255d6ded2c16f953b380423936a4031cda428b392591e2cef99d6b4eed6a9144e9fc9c28f9fcbcd1a909d55dfd50a9e4976eec76a39311196ee823b7b0f546a724342ebf908f2653f227defaf5f40f3fdfb8d1c9c8194d92dcf0d5682a1763fc18dfdd3dc6d9710c235657ddc69c477fc1e2c72a7fdabaf69b07448ef0b36aee6037bbe58bce9e1ef3282accd97a130d8d135786b9f239af484e217d18c095bf7945d20316b4698ab3658d1459f408b2a899e20353122d3ef4e86e0713c6527777b743018557f956cd755a42e24a26345cf9114ac2c2684154d5143a38b18306945230450d0f3f50597124d62c4929a54be94337ec969081c286ae080f35c8ae086088d4550ad377fad12341a3715eda183676bd11e098a31437c371774bd9fda33d1fdd7e5b4a8f537677fb953c3eea8dbec4c5b882cb2f5e1eb2c1d011c2fa73dd52babb7ae6a41466b5ba715bd8e5dcbd87d5e970b1f4599e8fd5ede1d2e96cb13a5c727274745ceae8e8e8bc5a9782e105dc29a560f86376e3ea0a2ec6866e85e5b01fbe6a58efd95f3606791061cdbd3ac23cf816eba3d703e3acb4e3bc07749d06dbbe59e9af3ef975a80ee95829d3a4763dd574cf5d6b8bab8e8b41ade72fe2ae76dc71afb655739168e3589edd581eb76d5f8b8b44abcde3beab57d471de77997f933f8f5c3d67173b76dc5980cbe1e76a3133c72ef0c1285f14465f0ac557f8bad4eb3eeedb3eaac99fe1cea5af7d473baef4392fef0092a334a6872bdfbd4a9d73eafd5f67d8d6e473a55356d662fab8942ed79dab0fedf6d9cbd335e7f394f279b4e4332ad76afa31ecf1597df872b9cff6758064db6aaddd57df7d56bedb8b1ec16e4bf9b93c82dd1d282f88b8ed75d792096eeeaf99999b89047932454418724a723454a41b73d415f1c0100bc6d55418937b949d199d753ec56b0e6bd1ebce2ccc3192436774a60115a2e7328843c62561430da870c834a0a2333ac3218331b973dad7ef5cb81cf7914160bda7c4867c85b86efdc9712c66666666e6efbee9baf50b4fb8b5d3de7bef2b83b0bdd7018966803093e572200f1021ae6bc460e82222e354dd0d3c9a5137ec408e6d45a0c9f23a6eeb408d40f33bb08a40f323687fc8abcd1f5e5df7c40ad966cec44cacb4c45a4c13725a3c29634366e2136891060536f4aad27855ccc44c695eb2384155b39e1e1e26a058215436e361420049686c189bdd8ecd7a04901454c5d49383977c5aabbd71f8c26530ac3f9f58be2eb442dbaeb9f06541cf71e5c74b3b6052287677cf4fd77df9dc3999ee0aec0f86afbbc45ac2ba96b8fd5d9b353172c8dc1d8ad71cd6a2911c275608952161c32d6a8b8a443d54966384ca78665ed8500049b32b80a49ebb4551d916c533cb218b8383a9eb9a6a888be3c0edbbbb3d8f1ec2b7b33b4d15725d76a25821db8cc5840de3368b55705db659a7f4f2c1861d95d2eda8866cb34e0907d33cc3836d76eba5dff1e5beebc0f0689b41b17cb9ee30c7598b7ceb6ab56510daf862cbb191831512a7aed19cc2865bd4eddfa222514c8c0d6dd8588a44b1db944efd28c0e56d895b3fb4f1c5fc6297c1c7e24a63c30ed2dc0e62d7c652046a1b4bb7350a761ecc38a662a494fd469201add5479f703a83c033c2cb38716e64dd8eb162b1310681af7c03ec883c31deede57b1a11a45017f56d5fcf8b7edc0d5c0d767ecd91ff52c87b290f1085bcbb0f61205c1921c05fa70e183b148363e76b565207dd0a21f5f077f7399b58fc0b5d39d5231191fabcfa1adbbbb8b7d1fd0d2222dee360fdabf53bf633584520fafd197c75e7f5511cdfbcf1b90dd7d73558070c2debebbeed5b7db55ed7fce3f8b44476437afb69bdaeb59e88afb8fe2d1211e9e7b8efef58fd5e2422729d1589885c8f444468ebe308449ff57504a2ef7d3202d1ef3eff6604a2bf7d3402d15f7d5a04a25f3fed5b45200ac61d9e316819b9b8a69368d374725a2caf03c11adaf0e8e8cea76c2983165c7d46fe527e12a55b82081d149d6a90bafd0fe820e6da75e50b91999d9500d68705f09deb39fe1c85aafce9f7aeb23fae62315808ebc0f0397013c1a54f6bac542c4117fdd09ddfc1f9d29d32b3df091e75d7c94c298f8c01e5d163f418638cee1e638cdeb034de69f2e9cfa9b9a651f0a8bbf3ab26a53182ae4d6845a54bff5a5f2f5b411a935378f41863f418dddddddd1db4a25d6070dc13257feefcf1c79f56b76de33ccf9352e674d67349d9ddddddb2372a74645ccbb2778db13b76ecd831ce1839c6d76cecc831be4e11d17a54968297c51c3687bbd8dd52b66cd93272dcc969c37523497e00d7c5beb863e022171b8b74bc8495c1ee8b972fd68b0dbb59f90397b511cc625d5687086b85b0117ce90c613516ed7670b033976f5dbe43ab5e11cb6e57b7596b86fb584ae92e5dbafceee62b5ce4963e5b54ebb42434ad565647d5252e36d792ec6a297d524dea7491dc59e1384e82a1656eafc8ddfad046a037823502f9471df10b637352a7d53badd3748ab0b5d6ae6259753a32ab53840debd59159aed39159aeeb74bad8f85d04bda239edeb599d8ecc86f67a6df9b460fea3c7da9c9cc9e9e8c418298d34d2487d7ad439c2b10e1096257773338356a4c03949515344531818dcb0d214563a338605ee15c60c3aa04230db6664b46cb32494b0cc6474206189534e53e29415cbdf1d2014411495c139a2329f46723245def8a4841bd78d4e5092a8373e1d11e5644bce8d4e50b4cc392318c7885fb99e0f0a4eb3f13c9af376a26660f2e49348bbe9cd6e3437c8e59a5eaf5a59d6c4b2d6cb8f9b8accb0652d8b9a1333618cdc661951d4e206d90ff99b6421d2652f4d1dc4ccb20ef2c2c28c0de96032d4410cf28c4095305d6eeb38af88fbeef3fea3d6b55aad0ea25a694aa6d446298dd72d254cb79999ff475fd63c0850254c1e09fef2fd3d9ef287ffecf2e9e0bea1b6dc626630f474cc96eddefb7e1dd57bddce7bafc8d3220a3d19b7e3e5b8468d1af22516db545a9448b4009645a21a1f85b53f39e6868662b12b613c3d1fda0d5e8221f773e8c51f573e1300a83f2000c74610356554e27f10fb800040393ce8e00716236ac37130b8793161ef10cce4d7c881a50cd3524a29cd50a7be863a7535ee5ea4e6f458afe92815c492d0fc6e823b2cb06c3cf0a07b07db55195dde0e838b5555d786181bdadb45a22f56c990a5a2144ff507a59191e1341a6c34348c3855d1c24e54d85c9e1b9daa30b9de8d4e559074cec5539c6a82a2b2fe825b718c31c6186bb8ba46f2dd844eea31297bb7e4f19faf813ef8824a431d9330ff1ef3d33e2abd7acc4ffbf94925db63be045b90f78a08e6d34fa9f698df9e099c94d463be44923d91b0c20a2cb0f0e951a5907f9457e2cbda0d80f095ffc51f7b2a4347d571f9e398101070f9636d80931ccc343d81a58c24c8c4d853b5e96ae25afd0a0c7908795da3d58bf0fc5f031af645ad011d2442037ed2e7ea156d200f7501f4867dcf65d7aba76a93b694425ed7483ed2fd86b94ffd62e7d341d29b55ab5158665d97a3614dc150763afaa7630d8b5e510e9f1072b8eb700ae1e698c923bce4de6ddf71140c37bed2a95318af9cae4009bb9dd7eabab55bba5bb7da5ef341e16a91835b67124ec5552b0f5f62258f79020a1ee475575ff8721748e5e564e39407394187b9c6a5bd9df35d07511b3d94f34b2a1d726d3a3f7da8df06289f3e39a66192a9841955575dbddc90a953e57de4f758ec02d661d679fe960ec86e6ccef74a4a923bc831217351c5a1b5d65a0d8c02a34ad0b45707668030b383e512aedca821830db98a4d8f8b4afe74947c6ad8042553c3a49516ba0182107ffa7b5c32bd646aa87ea6927287084204e207532291cbb0a1a2aaa092428840fc892544200986d5ca9d558b1bae6a587f0e5646754ec4a8e853e4b20b97b59e2622843daf69472f0d04325d90de73f98b21ecf6eaaf9473d6e90ef200e1f147baeea0bbec113b9f5c60297946d07e6a600326330a5d910e7aa1832a23d9d1813b1ad67d7b3d69076e600368d55c08a3d4102ab1ea1f068bc58656b26a3564b5f2d5bfd1fc8625a943585b76c474d0e9c31a0472684a49aefc1795252e89f46ea78a0d7dae7d1733f3774e867cd55990737ebec7dce21c90e1b09e87749fdbfabc48e5dd37c397562da8edd8a1699a4f4f13195cdc6fd84e15cbbf238b0d2592db0f874dec1d3bee04976e18a93a898bfaecd77590e17415cb495509ba724d5b813d4839055d7bf5d45a573d48d5fa9ae370429078828e2027d41fb55afb02a76e3b0f3f92fc7846e0d004e6f1d1970eddf67cac2e27e930f534e59c73ce598f8678e4235df948377e8c1f5aaf88412bc1d087a5d41fab3b94a3431d627b8f759dab729d943dfdd3ec71e0ab0cfbdbf60edad7ad462e3bdbfd9c3d127c1d61bb86c58eb8773858beee5af44bbdd69a0647398dfb31bd22ed6dc4f9a3a12b64e81ab1d6fd77d3f3e11569cf0919baa108d7c803c3a1ebbdff0674bb50c7ed3e74ddcec6ed34b85d006e67856c78cfbd8d2fe7e3aff1f1b7f859cfaecb60e8bdb7bef0c301dcfe29c9b8f3e9739fdfed8b577b214337da9e337cadf5b78fedc6b92799b7f87760a4dcf4224ee3a66bdab7bdf5d33a084cd0bd76520a190303d87e4e722fbf1efed83dbb94b85e5b78f5dad315a594993257cdf5f26d7bf68ab698b6bdf6ab0d8c9c57b4e2557da7226af5fe311816b9b2f2e5f7c26afc77d5d9add6289ffee9dfbe9764f0bdb04b347430e46f7b8e3b28ec065a704363437faf9f517f91c6dda5405cfaf7db88d8e5569ae62ff0d5b4e7d1ee40369752735daebed71a61ab2277e511b602226f5d316ce5976b956082083de70e1e0dd3c193276f0085331a0afdd3af81216562431e1d3b44b82197d0b07e13a4eb46af2848e7d6a53c725d0a86dcc595200f101e1f9ea116fc1af10e399dbe06b6e03782a09454ba9c1281e14a1a7b59574e13552d275eaffee9ed4317aef602f76af2eae1aed2d7dd3e2397af9ed56ae555d0e78a75594dd334d09370da470d8c9aa6f99340a475e77791a86acc6fba0f394e8ae5f11e293e8d7db9c53382f7ac678121eb3d16eb0bbd1ff1769f7c4e364884e3f8e8d5c7dbd42ba254bcdc9d1b9da4545daeebba4ffee643f3bef047bc4647fddecff74021fdb3887a5fdcdef37e38581bc6fdf6c9db7d2c14719918294c37de7837fa469bf4b6efb46dfb7ae571aec360e4d6e7fad52bb7fdf6c50a64235023794cdbb8aef41a89b6a7bfda2251f7f4b948e4bd17895a4f9f1589ecd36f45a29ca76f2391ced3cf8944359ebe0e83505db0c8e487d3c811683ed732024d9f54ab35c09c0824bf5f27c77eac0824bff57911483eebf33e2e02c9efbe6d552390fc950e7f2035902da8c3920c8e8d58a91b76ddc749363a511973f949e01a29e9ce475ae2fab3c0e3ce478a40d168823c232ce0fa2fe0867de506bfc1eddf200467dd7e162cebf6b338981fc6186344e238b7bf1006233d3972942cf91ec78d78802ce0f6b310673782314eecf638b53078e84f5a9975c458c98d83f67e70702b4884835b9fdbba1889a60f7b8970958fbf4498ea560681198436c26dcf7974b2a6b7ba0881f9dbcb6ff5dbe71b7bfef50be1460fc8fcb0a5f723845b412221dcfa9ce5ada5b340d0b705aacdb6823e0f10faf3a95c3d0701faabe75bbf90e3dc1ae7aec0f9f56b5ddae28ba4311b21938ae21ecdf0134ce7083c3b59e570aad8c10c942a7a7ab15dc37838ed203543da3d597f233a5fd3420dac1da199a8048ef6543909d1d808000040019314000020100a8885229148301c2782547d14000d7f9242724e9b0a84498ec32886829031c4186088210018438c199b991100f9d2e0966d7b80bf87620cf1ca882827bae51464d18a9364808a6c8feeea19badcbf01025721092335a1a0c3a936588296b992a3afefdab4f9ed77634a0d9dfec9c5369ff8d122c1b9953b11e4297091d14df5578527a5f10332d7999c6cd65837838a85bc0c470e32b7abcbe1e530b5e86a9c7cc6d6afa301402dd1fdc328d085057f7a089410e92fc54cb3bfa371ff511258975f54f6e81de66928b1b9d3698c9dbb1f18cf17cdb88ff58ca3c4ac52727b21ceb3ce70fda463e45a68ac7abf223a78d7547c7aac9da2d2330c3c44c821413dfd1f9925923900a60ef2d05cd2996ed33d3e9dc4259d47b450672e11d26b90aae9c7e966b29086d68ba1ae0e30e64170ec39e8d078603985ccd191b0aac929c2fd63f85776ef87c0024ea528007afb730bc10061ea3b324523758a8435757ea655a414ecc77b9d44cdc2a515ecab091f4c9e4a7f69a0fc29749a5ac18c9e10c09dc3a90d6a322ac22d4943cf3f087acf34d386264ea1f04600f11665351edf5ec9783ea1a197035de640ca1b63e492b2e31391645e8f470f643920ab2ca94750979ddd40bdc12bc91d773365f656750d137d02d260cd01da065e9e482348115933e3bea5f6e5d965e2ebea4faf9c6d2b292bd4e072d3f4d956dbc84431680f876bfc4554f944915f469903fa59a38b1e4cc2ce5d1417250fe936d4dd9a0bb6458b1378f2c670601bf2a72774de07441b5e2044d7991c260049ac8afdaa9bd68bb56daec75dff0a9aa4882aa47ceac05de874085dee94c1a08bf125ec73202eb7481027d626dcce337271255d2bd069ae4795ffb4cd6e4bc5725c9a79044e7485e5798972187789685e55770caf92d69bbab57504ffc7fd8e66b9d47c7c25dd5afacf366fc7a6680f942ee42a3d885b4e71e9544b34f4dc5a7a75b58ed8971f37c2ff7223c33d4fd9b899a9af5f410112cdca39cb9f5225297a7a598d86aa8a5b59a54d95c82de52f98ccf708b905f4a929ca5fd40c554c51a404375c572ff71d2c7f959f5b7d6d2db3136673489276b16ee04efbdf69128ddc8ed22c9fdf7fee8b261cd43184a106292b84a1df8fd44218de3b0b9a8296ddcedb8e2d7982810d407818d908c8364283d2ba35ac69c33df41a103a8cdb28a083b0d10eb2243d69423547501a32ca2dc86802bf99766db955d07c7cf41bb4c15d476d9c22d3920810a619c411bab78a3e703b0d288321609f5ad88adc2fee4c8af6347db640b17cfa84f5fc98a4d37eb708c37778144563287184a1e0add8ef536f037e8ec0544d872b326d9525e9a856292e25844826f41e64eff95b4663c3a13fd964c7a2e3fbc97b27720e701b1b8ce0c50653c8dc20d6db12426163a69cd9602c038c5a991b2539f6c0994a3378fd0c9859f4e6617c8ec1b76593baf31c4ca65baf3690d35f538bc108cba63a156c77ad1b6b7d8492729523886dbd0bfd4b17a58db646aff58d129f4881774b7eebbb9cf033161a649332bfa11b1412c13b1518346da0f033e45e4760a4d5d4749f8031d676b059e1b198a164f7e3aa6f88029197c372d2f00d481aeacf3949185294f43e967158f76c12d1c2f2855fc92bf1113018c20680b8ee8194368c54fc87c480d15809dcefc4c79c36c80c65c88869318697e634fb2a7245ec1da81d537c5500b181b461b6f9d6a3eba62cefae4a12708e227a12d4086f362c9dddacadb28c1545f5e90b2a579a44f1c6cd19da8b99e53cc1380113836bd4d809dd517231864d37448e35f0ced48b223629eb3623e1858b86485ace7e7e445ed98778b17670fab3edceefdb468dc4ee73051e5ed5773a0a78b9f879f9c3279b6ed7504a613948f51199b60af50b18c4b359d8e365bc39e6c091f73f225a489d2290b120399a418dae3ab1801ec19530c58ee40a8c111e51d09e7c241b598045f52780e6f8f255d019f78b38fa582cc69595b56269591e0ce04656601a1869f792009631f917f18e9911c74622e421a31a854d9edd2180fd118a29b5a535827f6a998a7aad84823d62bb1227b8f03c4dbbb61e70054cabd2c2ce5a3c994a0e2ea746a9afb72da53776e8cb2008b57ad94c2b606215c750a399f886db6bb94ace603efeea2d649bf885dec950ae5dbe2af09af0e47a52e4d774f17070685215c5a171cd8b54428cec9ad4c6e2920bcaf4bb6030939978ebe478577a9a06049cb03addf8a2fd7dd032b4c539a67bc23cb53bab896363f500c7969a9ff0662eaebf68c11537059f28fc0ab46aa3906a25de1a20d75e3cbe3cffa292105b0d8d62c71c75e57509c87c654feb186d6d5d161134cd3a9771b7dcac7b7b3b02083bcca3e94d9d584c056684b07de9c7463a177ae687d89d7c163ac664692de60a2d857c0ba365ef1910b86a3c0902de74dbefd52c0805230602b72392d1b1e81bb2ea60be6f6d929f3b168b41daaa53538de4a4989d930296965dfde1d467a3af1fa78ee3d5e38f6d2327b9fd6b0e9c562de6fed670969ab95c2314d8131e620c59b650b28ef1ce0f3c1ebcaa98952905a36457fb14aa2333e034521f9b38e051e86ca3508ca511c15d5cd4d1353e779a68e7c71ffb7137c4b0bc8a52c2740c4d6f98133b03e3224ab802470dceb5a66d1a6017c91d449ba1af23d99e70342cc0e5696fab5ae7091a0a74b6e7881143aa85c57bbc1a02ba6f9cb009b4a299b5fc38c1f14f355ac5960747d429cc80374a06ac659e6a6c74ab09120b0221ef37cf48b0e0ea097e7fad34bfc2a9bbad4421d3c4f1ea4352f12baa18e67e82f74bce60d764608099f8a11c8d63e334482671078f66583410effb00ceeefb59170ebf41eff3194ce84717ae83c6baca9d4d853eff0625f3fe8ad5df517094403b96e6e4920f5257060f0a212ca9a774efb0ab5564a1852c239ac233263fcf10bd8b2c83ab69222347ff70e78f4f2576dcac78ddcc06bfacb120802c612815d99e987d97bc3fd8c293992910b87bd8935ee4e136971d14396a26aa3a07c2547a86395b511f1003845234b4b90568d2201333db964088686e6df7fcb4530e29c3c3e9bb24d7f009188121579839ce239665a85930c1ad9e3bed35d41c0b7f40d0226ade65cffe87bd645d5f46e11e9a4bd9c6aef66f6d2d0cb7ec7c19810fe56f3543db8c6f29f655b8a57dfd0556652152dd554ad1d24f79f3bdd93348cbd8669d604dbd3657c3a229d768d1deb25ef7edde15ae2a4dbb93b8d5fad316273459c9befc742b4a4cb8a221b51bb67516a575064215db504e686441a1f1ba88719dece7931b4545b80c93ed25d651ae8bd0e3eb4140957998135f27699822aa15699086428df1d2594ff8c13a64c4312c1133350f2076bd724b14ffcbb4bfa17e0ba0184590c3b07cb6a0b33ea01591624f064ee9478627ba4a8787d826d85f06213a691f0b821959f3c5cd52322ca4e202909203a5756a0cea3d8dc917859e4783c6a8495bd1e87109fe866500400611ccc642f0991e86d00d5e78a11513313cabe5c178d90563428deb404bcb3b65e167f226ced9288d53dd753d82532f82d03a3c32d2e6c81dd1619b6a3e1b7ccfa6a66af97211e1be24a9c95bc56c4f348adeaf3ea4b784af25218f4712df57121fd7e312157a1b9aaecbfbdd92ace2b839231e77d914b53283817971a00a6da0e6d0b2c0420e5324cc15173ee5f8355081b4fd59db3bdf489fe6162e49c511bda21675738145c8b983e4482423df301bd19ec2a2f6d4197b8ad515a8804e8bb94c116db1119544a73d5b551833d5a412c8c8eab0b749bb6329a1ee8b6acb9ee6a759a4299f54d4d94f1049e090473c7c09d967b42ec455e7faa2f7107420fec90cc596109ee7c040ce2633e6d4a878ba75d75704ee6abe5483eda009df6cbb247b67a204163e8391ca606b5097231a57eb1dd7cb9f1a1086ba13fa7a3fec9cc42ed37d1cf25fddfd7a98e8b50570eb1c83ada4b75cedbd21aec1fd9a8aed53a2fe133350d6fcec562b0ee414a3e5b5a2d9a997db169a3b39fe759bb439f38c8fbf4bea07a8492eead2f39d8a02e105f885563abe73f6184fa28e5f8a83c101d00a5e75cd3f9b55686f0c4174890d2f050ad8e7eacbc653fbfaa52381c7e29be85d2291998199d4a13e74f7f57efa769522d01492ae606a997eca384d0d0bb0d62db5e3ed285e9e11a1c94a1d589456e0ec73d105364d2d67cc2a6d85aa6294ab659c2ccf6155191ec5424a9b6e8ce3ae796151fe805e672438fce84b018204801a5518ae67ada80992379ffc3b46964e7f14e1c2c558463f42fc6e34bb4726945eb29157d1f09e95b3de00808806ca27bee687f389b17c0714d2ee5ac65563797b60c0e8149a72634e75aeafcb1c62daac716e844a83a1d990b9511b0601c85b7f9d8469e62466156ef4bf295b508a6d1c7711254c60f6a51bb38f72292901913ba0b3544cf4884ef42f368fd338f8c1e70ea5455fa203856a221205b69ff20af0ca87778a2f16ca4bc1809d5e42ec1474966204e66bee960fa1dcb7c903489609f250e6d63780905614a381d5efe4ac68b1b8feae78b856884bb04865324706d76da797ff2a2b452b779768c24d55066d64be553f5e948ba11af955af1d89b4a1281c46d71704f7f28ec3d475220aec5708f6c354a0c6a27c0c8fa3d36d329fd096496645070d233cc4a73d395788e569aa8cc857b82e7a5903ba9b43fe0caa153d20ae2e80c96b11f082a1d268c07f3397fb4f126b30e68bcb26c010f540926ed85a7984a5843d79d8c4c31732aa01ba874df778fdd21cacec4187aa816395d4d9ab2c38f162880626db55cfb0fb6fd62934e4759743175594deaae680e8c7000cc3204b0316712d6534b113db47aeb08c4c33ae5c29db42b916cd2bdd627d33f8dca67cb390cfa80a0e6aae8503322c44fd949b5369299e3c2b7baaea149dbf08ce3f8268c3f2c63f02fbea714a15e5efe2484b61384124c2d30c1c99fe328467a6e8b2196737f644af829b6ef1e170e9b44d49809ba8d17ef7f4185027d3dfd627ee873b0bb41dec9149d9635ed6834648c5563466488a935016b13a23f21df1c0129e58fc1984ad951261f057c9d1296d0a23dccc7fe260d87c55b74978e53ebde0f3259a550825245cf9ba89b295bcc6f0319dcd6b244ff020fefa49d5287854655c8e63abe002817694d7f5c61a308b495e46d2ae73ae2d812b0633ff74728f7841f0b1011a1dfc7cd7ef5adafe9b9a496343bb7d423042cb1f50257f2a5b5964680ad5197134b10c8fc0804fcabff15d8ebf2870eec7da007a5b6be1bec3d12f38a5b09ff477140d0b5d132c517a35e7d1899d34d788610ac3c1e733d42a76fc3574cb6c9600e2357c4f3610a1fdfede6c5413bca649d378e71fbcfbffc487ca37db036c929f6e03400d438b1f1280867ab3b2250bc21460bfa65893145e9c74e6cead59238bb254af859951d8bd442437773038d8cf160991dfe97afd6d4fb797a9737200c9d34a9b2609c6b710f25722f19ff28af12640bac93fe4d05e1111445d72eb1b93e5c6a688c95f066979a3289fafa1c59d89c25c4bdcb3bd6c538d0c5295d11c7e8c0cd9adc498ec1fb9ad30522449d5e9af42252a55c43952dd561a68542fb367d234fef6f01f57999fe8fff7c8dd4b552a882a688d8e3e53acbf2d9444df2ec8a6b70a7f52e85f458479afbe4c3746bf2c95cd125fe1406e16956d00c0591f4185cf08db9d23cccca2899dab6bfb40b1f81545bf27c49cfa782e8ba6323761878df1f98f2b80bef95fe6c393371f65cab8f9ff7e2fff4fad466764be759e35d0f7143030b5b118e4816464509ca23c5bf55648464db3f2410203cc16f96c368ad24c3f21572d4c10f69532fc3461d404ee93e8634a703648c04849de2570cea61d0e37d30dec7093a41116761014036b63d3f83c212d05f768a57124960f9df9a7dece8be551ef6178d2a02f41a286b6712ad7e0f9e0b7ba9256346ec0bf7d24398f68e78f8d9f7d4fd2935d146425323bb35b05a1197b2b425fcf2937189bc5f67c09249b380cee0a2b1e1c76bfe8fddda5c48051b3603b5419ef5ef4ee50660a39a0101f18175ae86cbbfa146a74c1de8de64d0c63100efa83975a1338492fb1f77c5a0e268aaf5e9f44b645efdddd6903fe3bfde48a261d2f9723b016f70e5b42467e3ab7266df895ae31c7b330771267ae1037b523fe408e2192a6907fb5fea6efbb098f049b7c7183ff10ff790cc9d2522065b65e279aeb219aa7d38b21eb4efcfc5df805817ab5e9d7b471d6507a2e4f032f19ec0bc316d4685bdc364c1687d587c492cb7086747c9f8f6f85f6272f73548689fbbbdb20cbb3ef9bafe1568b7750566a529f698e4d8661749fd125880c3ed1c0a6f931b43f3dc43ddf822aab6e80b106740d1aa236ad2d1a41d709913f07d6664aa0171d17849ddc0a8b0ea71cc66cae39f3dfcc6b88a36f046b923ec6d1fdfd265c4b448677caba57d3bc5fb2ef9b64d30431ed362343926e0fc3f84793a8eb38f469cc5c934dd81ac296cd740cc84056958a4d85645dd93056b77eb0ba062bd412d67d7b87bd12ec9908b83ad6f0474b12dc2f20ced09360d304c08deeddc94f13639f414b65047c4afa691b95f6b730efd60c0f7726d1750f406a99165209dca46a99531ae2905c218f7df7701b64526e1b5e839f638e07b0cd220039c7808af900566975d2dae3381d1c6c9f3af2b3e605ebe2506bd839b42b25109ac962c1a5637c2dbf26faf722d0253e9cbe248b6ec43cc00858b394a2c9f2bad9c0e7dad6b21a54131ad9ba842ae0f5bf4db2a837fc84510b3eddd02d1567f149494af95ab605290f4c5947990cf0a5dc318c8aa06efd562fd5681d0ae6f08e73255560f06a3354b2a2235c68adebd53f3d91f39a9501a73adaae06225ecd56946681c1ece46224b65bd95ad0b8485dcef550cc634c1809446364dca7b515552c7890a348bb38a32f403e3741474adcdee6269dcd849230a4e32932cef491dbd8572adf9dd507a830c10f5b4de8f05e315eb32511d562306f557a13484b56eb780ce04d0876b9ad6e2afd832179fcfcf06b506c8d8412503371267071d85909de8bb147153061453ec8d7f575c2e9d1512d0bd93136900eddbe59be86107a6c328c6e96b263725108d96d857814cc1a05d58e2c263a3cf159c3f6a7c14d818a2706287c6c855388960a99ba2847572ca6209185fa5126151a588786fc099d5c6358fb900b274f90a1b0232eaf39431dbe07b029a571d7ac175edc9c16b8b9c3c57996450c3072c1d98781bc68feb2165f2251b33f68eab9f4e3bbf5c379aa001ca31fe3961fe03f856948fce8aa2be1f885c508b80fdf509d73b38f39f0b6ddd6079c747e1ed347ef81d15c952d997905aca7545489221195dd376364458d9e89eb198567b31d20ca5136f549428d15149135a36a635c55ec08cf109f3ccf13d5a012f4e780b46bacf1550d041a0ef1f2206cf49c004211404824d62a54a6441931766ceccdf42845bfb0bca5ce7fa97caee1221da9df7e8c7915895e58d60fbba2881bb2a2b66d2742eef504c4eaefd84cc0c34f9da5873e0e1a86fabbd734f7c2d0174c1a255dcc50264616b0771ed4837640b9600220204bc4551e14a2732d627cba0536c54d8c140dab85d0b8d58955186d0599f3dad3697e555ce03bfbb370d73e1713254217c8d0c00a0b81f777079c553104eb9d116cf4047905cc22bcf7f51da3f3a99ea8cb8b9553a62a1b59da438c774f8657bfc11417d2b34098077d70fb65e428a529a8f77a8242608c3df4fa5d83a34f4ef36abc5ed9e2f42fb26af3746793778cd562a65a38fd8980fcdacefae26161feeb656c8640fe8b3a50faaf71a0fe3f4b1fdcf85b44943b623e41fa09310941f8cc21d9d109c75039740c901e834d88cfb9191ea2db4ff701f9205a8242b3ced88b81ade9fa72892dea24e3598c566c7b288dfed7268acf94db899a4c909f833468ed0445ff87baabb1159cea2b1f21030f3a216df134235d9c1c9401ead39653e0c3fb08512730d30741c8758dcb9c0484866c7cf4dfafaebd09e08ff131294bd6ee0fde71d3f40c82966b9ed134ece91c75aa6047e0016eed8268a451d9ef6cf002337b46f1b7fe1bba7f4309c444a03c82ad4948f984af393ab0c64794143bd89095748f8edb8e0ba6f5fd15430617583e87b054db8202719475641cebac41116971656cc2cd282407f63e078ffc87f6f505a8963891e77991bc672d166384f37033d0b9d9268037ee20a1948dcaadf9ed59b1e920a3e13c5d117a21dcc571d70caf4a922fe3e766e072c2b9e21f0b74b04e5e39b22289aadf6c65ac32551423327755b3776b86eaf51a47036a0f4c6da9673252eda3dbc45c5262580e0baf9b1c5385f14945dc24120acfa98c14567634bd33d4d8aaf8bf37f6014624d5bd504ba70a0b2d8ca282af2c367cfd5b48ae431c3ac5c817ac3674022db9a66b0353588048f257736d37d4310eeddf4b3109915c32d4d049999a4ab290e9e574aa4bd86d55e86eb6cb230ac6da9548219de19a4c59876b1b3cb57e7c43a7db25164d6f989cdf1bc6f6790da69b3579a07e95163e186a0c0406191f4b723f970aa7449073a814879d3c2107a393f4b8d3a5590bf7c53eef20d2f887ebc475d48422d2d1d7c4a0b1602edfc2cbfb67e269c805844f5f674d4b61209f810f24aa74b08a8b95e7baef2369348a25e62e3595bde3ba2e2c42fecb765144b75c9737b411dc7c97853711efc48479074c3ac5ec8614d99580b4245494c74f67c38a90b6a0c28a9cd288a57ba18f42113a33bd75661add9a63bef68d9017a27fa7bf33b500413af0032ad1df94346f014303749d1c2bc18d830399cfcadc39ba6cb7be439f034ebd4e70f96d1e1abbd91554c4b5d0e7a27f897b66f914edd89b9712f4a73fa0f8095e4c5f9bbeb8cf1d38bd6b40c143bcd850cb8b59a07498b162fb5192bf2f7cdd7541c43cbf1fdb85f78986df00790380fa8797184c09c5e47e510fe72d4a691ac220e517def0d132947c4e3fcef90cc6ff6827a5d7f9503712409da406589756cb5403eb042b5514fcaa1c26378dc5578b55d5ea39bcf881e6d072f70bf37313142c26b75576ae333b1396be2b4ab08889cfd8bb73a6dab57700821d132ebb032c59a6794ce14da889d5d34c991e1138acd9ceaca2866b2b33c6526ab6b86323367e314702538b939cff0a753643c999cd5276601aaea3d40198d0172f6c7105e4355ca92b8059289457a0ec639009cd4a3d2b6e72d23e73e76b3c30908cc95720dc109dea83777956d1325d3f3ee63bd1db5e23883c5908025d1e2428b2641608a30c88415753be784777f235344084e83ad08bbb9a899e7b3d66b73b6ae7a210e86f1886a88ce6c4d6067e9d65ec4e756bfa94e8793efab25761560a5974ca6698600b1f8759dcef24478608a620122b899f26fcbb8a4269404b30855fec8b0598ddb314342428895003aa2cbd07816bfc001ff64e6b69f77d596346dee52773d58a1eef5062e8023657fa09a6064ee291364d1d3b81fb35065788509128422ff1fa9304605a059d30810415740abdee4e720fd4caea53328e97ca1928414e6afc954b079bf2498d6d0b9e65952aba76b2f18097f70e0b3cb83aaa0c8e8e7c963c49c498378a7c7a361cc71012355924bf7208514611cb8e9f836be8794f0dcd1b2dab37798d596a79ce9fe531ff6cfae4a552c7a5cbd51730231d63c6810a5f5b531ebea804aa128c770d6c5461247564b9988ce88f34d55b99b709000e3470be8733d4ffcb56091f220c451cc5fe8e275614e98618c2be276ece15895256f3a30a52f4080e35c2b0ed33b392a9825eb46608119b0ca0c250720d4950860b44b6fc70382a5824017e9ff1a7691316d405929cfb7c1967fd9061370a185ed8b7d9a04c958ceb752d863c3b7551c718df2a341ee13d0209e77de0d5f07acc830ca80b488244f19b58e9e6e489febdcfcfc32df44ccc1d9db57763f9bd4c4fce2a58a7f82de8a172224a8ba99b2c786749bdb06cfd19a49917a7bda14178ad2e70353378a289b9607471dd334b0a482b162d0cc3e64c64ec750ee952c6850d34968cc6e372ca0e2c12019dddbb05a42ea3865581122ee032482dd1b0bc562a2dd8981af201ec970e95dc4f08d7d4d6f49dbc1660c6e1705b621a8821fccb38391e90243946115b8d932b5f041483c4a8bf71250480a78488e4564cde6cce93c6c3bb9b3573885333c1c0253048d3dd73aefc3a5a0d7b5675b5f10bc74760a7d2a97b42202cedd29cd06003fc1e11957d5c7e46c165e8ac3338f610fefc82533c807c272fa32ae98ef26a2ddbc2889eb8532b38106c9e0243948e2ea28cbcd8b8f914db1ab34466b38db065130e64e5050dc45117cb4b7e1c3040a8e0e1f220a71597bd812de11c8116e06653fe98877806accc10f71585bf36c315cc9c2b7f33cfdc9c3ef8619329419082d206d34c79c4b7dc0e39910fcc31066c6e7ae376e71cc35aeba410cb22aaf12d2dca69b78af4c83da63e671d9507e89d35c747fb912cb285345a16a0b342d50d347c2c914079432323be310f373b33f9d1361563683f9b0500119543fd5c3307c0314733a46611fb6a256cb981a69fd3856cce57ed5328d07d5a1008e0ade13ebc9a64f08bd8f6aeb08b711e9c461a3a178ffbbbbee039e069c99ba20c5836fb1c7012422b6f2d6b74405fc0bc6709a42bc13e86ba8a20dbe3713f52eb014ca701bbeef4a0b891d0ddd2d7c1af20b7a9aa7e1f3acf4e2fe481a1691aae25b3b6e583e94b60d3a27fd79dd76a645e25302f87c40c916239db8b4716d7c2ffde5dc711ea13612feed637d31eabefc7fc163f1f9bcc285498eff66835e36af518e8d180215ecedb76d8fa0f11a67013735c3bdf6aceae9aec3105e99df212354d82a557d12cdcec248fb3db5ee88fcf2eb2eb1dc185ae8d264610f09a50e341db7f7ab7317ab0bae69870e23afefcb6872ab78006241bf10e3646bdda9a08d9592e891ba77629c8557cc64a9c185723da2d0f6d115b9f35440f589543c5a1c9a0a3689d42aa3dd551a48ec3bd06fe0dcbce18e15f8f82864ae9079e833ebb4547d57ef61e4e84aed93840be9d38b4ae8be687c1bce5c8671b3b0e889c640621f7e432236f4c689ca64c2d39a7254f45b030991bd623f054a5886dd470177540bed13af51aac6785bf18cf687f310d13bbecdb59d2e14f528c7891746e1c3036c7bdfcac68c16e1b8dc8a91f20dc85f4111b451849db18f0f10b38c184e69fb64afea8dffcec79f26b91701984336b4b031328298ac14096b59f53aa5357033eb9263066c91b5d98df69897c9d6ccf9fea5e2ed702efe902753389839c06a9f65791a3c41107ed5be1b30a48f48d3961fc1948641c0668a35e7a003e59860752c4cd9058518339e198c3ac40402871d3c440c40e92c8d0bb935f407b118ed400b80c2008d12884ab40c5f6a3a2d5c866a3cf35bd61988e25491865308c61d21ba28fe4fd32cbb1c9aa062fefd97b166a4685b749e61c13b8c1e82195299b56d1b442201cf5d7ef3f915cc20a7f1de1d140469ab273a1c795f4489b89865693f0f2691494eeeb91de93069b6979c71e40e430063056c373f9ef1f55ec10d1a696ba168e9ee28dade51ad9f5ab5f8fd18ff36fcc0684c4256fddf9bf1e1d7551c1fdad26fda83a750bd2e370cc566a3b775635d7f049989954b171b03f48ab5b1e33f2f1552165bb790d146458c99d54ce38c65f18723abef30558c0d063ca7aad694460d628059063b16315492a9231095b94be6ecf348bbf948e47809f6fc2583a382374139650653da3598369522028511f890411e229ad5f3ee6a1292f22a08dfbed0568ca04267c0228904f3410a47cb1b1368a55fb296a7fd8ab2c49f14ae4a45450eefce5326c5a1630e6e14f179c3f4928acfbea5fc7f8a803ea264ac5d67fcdc176dee0f0d63f2d330d0664b11a74311488d83d552d1777f23b604acb3dc8adf54e4a9b49be72f572df4011e9e63edbb7da60022f291104a009e4cded9b362a16dc0b9d8bb72c447349b6c82d56285dc961c524757dcd4821d136b2ccef38f09b994d88d4e2ba9ec23b3f4a24c53e016c5deb0dabb305597c1342a701f8db137acd393eaab07cec87fa80b251d78b18dbaf904aeac4afec8ca8a2c15302ff9a1e83bcfcd9e5ede83586e3dd05cadd78ac4c3f5e2fce8b20e9ce9546c19d0c51893989895000e56dfb03ef37556966829be310b0ecae3ba4e25c55ce8c04ae0eb843b9c1484fa0c0b9226a2710fbf7a5fb9ae4bc20755b2890973603bcfc629037d35c7776f6a28940cacb2f96bc1a9df949b094a90d7c9fedc4e28c6fbb4498c552975b6f58b63e2b3bbdfeb9841170823a8899ce7f23560f81b8aa64f70f429f0e4f3822f324d8623de9b27d4969a74d55ec643139143a0ead3829e5da5525ceb24fb6f498aefaa0f75901b5f1d20e6aa877847600d04057321d1aa74071d231b731f6b4f8325ffb37e14be380172e0bcae1bc35e4a2dc22aab569017ecb3098381f7f16f636b6e6a5b59e85fefbd648ea8a2f5e501a95295320319acdabb13fd6648da3490d745bcf93e00561c950ce6866ff8eb015e71551021feabef0429dc4b2003758de2a88257622a6aa73381aa16d6ff35d63edc9e1e57a62a059ab69e767ed0e62bb6475338d5876c4c6d52e6d307088f1cfc9ebca9b942f987ab8fc06fa7ca68e34bb2d714402998f7fbc39273bc25a17550debddb338970e59e36543a4adc3ea2be41fceb5b46681a72ebe486186fb60f501ece2f655662647064845a6dcc9bfd3d372f0c40f8823e42464c7d5e2d60cb945461013bb5ea04bb23f797e82c617a5d4d2223fe7e6f8f09d82eb45368c2ce18a7021f8a0f7cc91899e8d8105c9cee3f83426c3b2002cd293931651d9c9157428089c847cd52ca6b69352e29ac6b010ba3e78708be0ae5bf7ad0d974f0f572d72a0983498767a65fc4b24a299bb0ec4b4d5a15095f58d0119d20ece2fc17a63cef07d237152ded77a788d0b1f271035b88822a18ae72a7f2d389ca2eeb12c1b3d459856c6b23226a79a0a47c338448e37889b3cfd701124d33bcae87006299db4b1b9a5a510f8d309819aea31ec24a5460fc6d8a64f62ccff5767d9cec1cbc8932ea55f511cf668dc1c5d43734c701b416ef1d774ff35cbc3ab88f8c3d9601393cdaa071b61d131651175c342c3d82e6a8aeb598ed80cccf61540cf9f38ca342aa0cd401d8186af6fd71142ae91d2fbcf1081f961628781b96217f4f5135c09f6f5b38c99bf94d711e9fead64ed2d1f09fea19d75bc170f323a9d3022b1a34a82b71714a6418462a6f08c22a38d8535c3bc6230fa15781eba2c926ec6faeaaa2b2fd6052742ecb5c14c09941c8f9bdb362917c85bb1c43e3135996361063184639224027851dc500c461ee6d490580350f9c47be189cf4fc8207c5a88a1b9387bed77b4b442cbb23585989196bb75d91e13b66e2490223fc5598b356ceb2ee703ba5e61cc1153fc637b6c7340acce40220bf6afa51d05daff981ee3667b03cbe633256bbdfb1821247d356c276f32fcfdf7a7ad0aeccd444b63c2cad2edb35bb10e1f1bb727fc9bb0ee0aff55b1034b68d2ac88a08c8207804043b1018ec785df47a51c10f8fcd9b59a68295e0216a32b6a6ba9e07b17a0ff081c433ea8a9b8d113ac3ed8f30ffe38a96ea068ef3fa850c405c98a14d4126fcba1715e051da43df2f58b93771e4933f12b71421736773a1cea5b9e5428f2cbe94a89b697588d34633310406617f217293a52b45eea1dfd20063d102b69ec69dbe3ae510d1087556f043d18873fe5703e944012ef206afe6972febe612cb780cdeb03333985d29101b08a40891ef4705af5024de5943618cc1c38a492f5fa5bc42098c877a650be4acb6c68a53236fe729201209398f338d6ff0c4743e590dc80ec734101e64296d65bec59f216ce2072d681de7a4b008b1ede67be3edfb450c36bdca9aa13bd80b03579c463327e7bc87dc7a227f942be91f042e21ef6108d00fb1e1a325803fb07ad9a7c8d76a7922068b5736a7f64af448ddc93aa779963d7b138d6082b8bc641311b0fa02a46becbda8832924f08019988d423a30e0297ca9da1c5da4048e1a09e8dfb74fad3824b9413a3a2ed794042311074c81ea6e80230a57fb2afeff0eceac7f17b8a9581304b412634d1c448866e3acd0c8044f4767cbbbcc51bd65edf29832ea0d858c11cdd0a625de38b430a6e084eedfe7b187c15fd0a0005c455a887771aaeb7688eb731f32ae65f8e13f12540c8beac9232c8de322f2bb22ee19e356fa241d228bfed7d54d41887e1ebbaa697232e2695c2dd9e6ea631a7102d2a562148322c4af66c03e10ecc0c5df709c014ec07677ace687612b867775678bc0ac2dfbc3ddcd722637838b49af2a2184bfba7221af3db33fd015815638d6bc19fd6a8beb146a3a5654696aea76448e89cc06c4dd677866ab9dd4b568e4c41e81bf695ab4d40b1422906442e10ff2c6e1e10850f6c41a1ad087202065388ebcecb2add9c48332dcfd7c248f335190630e6b967ac6ab4b36c8a6a810032c42fed9f1c8fac16984377f3f294eeeb9b2895743aca2e7defe6dddee9fbf1fb883bbb6809d9af0c61617790f36a2172f7a2244416df10147ea817ad838f0591e37ed4d03891991d856f656491be096c1933e37ee8432ae1be3600c40d68b3bb290ad576db4587aa10e4fbbbc420874e2c12625d0689d0681998442d2d9be4232965f77bf0b58cda5ae8286e87b8f2d885437eefae21899257c688d3e0de7c4711efd832657460984a0c11c65f69046188fcfceeaf1e59b0f832779667f687df9eefe457a89cbe915db14510945f50f7e365d3ccf0c23aef2bc676dffe22c1868d30f37ca963402fd6b483b4edfd3424267c1d3976f5a0db4316112b1218223ab75d8c013c84b52a8782471e0f583e8cafa7687f661ad1b4369985d5f5f837672a45e0d8111e959fc0c31377e06507faff0a01cea18a968561f4a51e996bd3ab3a68c77c630c31a1014776463db8df50317ffb2dcce5ff2150c46920281022b7366e44471ff8fecdc1189ae0ab9aed2ba9d822cf9ac2b927b50f66f48aade429e0621afc8044c1afc061e23a60df3f80b5b1afcac28a4a686dbceee0a89b4d550178e4ece51537a5c812670efbcdf50d1468520a765fc9c7f3d45010617b6b5999e48d2ace2675c1f819084bf17fc8af6e2e42ae2a645f225f068cb00644e07a7263297bb4a28be317194a0fdce1204ecf7085137ebca053cb10a2c1c2b3716519ac2130a6a84f891edaa2ef17a83201df48be717240e6756d8e1950c1b86ce0b4d911dd36305184fe64345258267e70495704210d4d441449279a18b313420b5d1430d2893028970f2592fceaf48e28d2c96653275a79e644a1c5bef802590a4d691d02c75754e29e5cdb6ffcf2b20ef0bf372769fb56e38ddb974280fa99703d07265300630c4e01eb07299b97c1e60d7d17ff2e7033b45b7ce2bdc00fed950444e15a213507b4b835b562cc43ee3e742fcdc2266015e330873605d06e01c7464d909dfba424c60621c734620b283371249b5775ef45c1d731f95f8288c44939968618488168e9016c7329ae6ef4f1208fa480a6c6030872ffbf40e2ec196088adf6608473576b533788fba0a2e7531d5aaa0723d3ecc4f8261879f5aa31a3b60a8a824c6cc95a0106c7fe1948b20243126db5004140f611e8f4ac762b6784cb1789fef7a714fc1ac4dd86a645a63b5d83b69960c99be4c3299bc064d6e41eaa0641bb05b2df9f5c2bfc3a30d9cb2a81b74085ea37d395e6346ca14b4b6d09797aeab96cb4997c8e63681fdc31a978f20aaeb084f93c524403ebf32b860a3d9634c747110ba0be2a93e86e38f73bafe6f97eaf61c4e72d20879d829071e2c68cf478319645b20d1cd48990ebbe7ed6cc4fb6c86cac19c99643273d8226dbe756b78b4e47e1d050fba986991a89c0baded0d2d4722ccd51f0b2b384056ac0ebebdcd4e26d4e6c99b9f1a3d3f03a0873d5ff3c7a43fdbdb57ac3ff85761e8312cb8e81263672d633a495840ef76d7914c68f5481c93a202d75afee48fd98cd176036bf423b83824e5177c164690c706d55b3e95ce69e8fc942f477fef7fa83de7f88956b1a84aa706ac272af7999dceddc3c2f5443b6c7780fda4b8ee00f576590b8add6b4e841caea576b80a588fd6d4ad99e252c09e07c8c5d5e1363b2439d19edf337011cc38bb620cf49ebc1a6a8c22f0811513e655c048792dc64f21e3692574eff4006037b5cf5bd0bd8317d15aa884d0596fbe295c0336d138ad63903c3c3e6cf7876e701cd911d0641d203dde38950032c62a64d12fb52b999ec62ddf3f2618ebb1b701d48b7c741834a876b7c3a6a986246bb5a7f01a7952dca7b0e2c35b50b0e551f44f309b4225e98eda0aea7649b79c1d4dec675212d8f62be35d6eba0184709e6330bda2634efee5715611bd0ee2ccf86a6b8b226323df6a668ffb22f5068cac531376463f5020a158d983c4822c859d9457cba005b94b3eb12852acb801ed91e658880cd20f4ddb4347c146c280cb54ea5688f0e0b71019b83661e1bcaf53a7700b9048582f20ef8ad93a0d579ce691f78530964c51d7cf1d61038597b62b55ab0fe5c051223db485f250a970f3600c384418f8f4c9cb5d196d801191b8ada728182e42685e253d5f26ce0458fb757a9cb4bf3b4f71ee03cd3c81c4f2a88e1afa053f13d4394aeeca31dcfbc2dca14acbadcd2f2dc551413a221bffd715881df9dd0b1234ae919af16ff1ae18e0c3fad36f9670c85b5130fa86b70142054499d177d4dee0eb506453b319921f2955ee4c99e8006f338b0d6f4049a13c04e7f2442da35c11fb54ccabe779c1d6863cda51152b70a8950b3015f421a6ea7d270dd2c7a1e565efc3f4c122e18ead44e4dee531e094b049bf75a8a97b3ac76af4635cdca89270fcdf6b0a1e2c9c3b73444347e9f0c2f64518ab5b957deaf0f1663df2de333b8162a9a215a75c12dc8c511c93e8af972ef9821004e8639984f3eb685aa762028ebee7258212d198c0812e31ae113becfb43de7996d992b973d1c7488518b6d30e7f804cbb3d1984aa18f1d31ae4c09f1241bfd36f0446dba22d8f2ff7619a0946f01a88edb2af3e0ec5f0841f4f905775b97255c3975aba928af4347a0fdb426130cbd3a8be545b72bcfd4e830f78a45d8578da8347ee2c6fb3d89a0bf4526c74138f32f0b02a72d8326684a4ec003f7d80080cc7888d3c1bbbd46d6031f9b343d8b3f4ae5624db7373e38bf2c545fb7578c4b284d7f6793aa76f538b907745f85eea150c787957e2dac823d0d315bdf59aef71c07cc2a6ac0e64c5934e4a00f833cddef4512991a762b833ea3dc090b714a889c298e993694a388ad55fb833b315b112d985e9b77618fbcd0bba17cf8dcd0f705f762485b100a7067b174580a3eff1dd56791889f632c733d90846cf8781b2c00540634257e0b9498945a704ae0d7ee0c1474282c59f9c1d8cddb97fd6091de26e48e89dcca34e75019a2cd82766998a46cd5056217d2f1ec14c1f1a66d35d7cc3653f6aa4bb957f43b2d28cc4cd32187604c9f295aa04fd038a302b5aacabf079424dc698c0fea55919bd93cfd860eb0207baa0eda1531fadfe0bcd38845342bc69035829ecdbc61733e26e941c844680baea6cedf0953b795d55e57d16c006de2b6a55e13febe70304c118c48afd83a77c36f578e83bd90b376e9c109b8fe2634d69333f5b7dcf74a5b68bedd9284131d3c1fef82fe8cf505fb86ee1d008f0a0b9b7352e1f1c28448e64d671f54930fe805cb52367326344d846ba474d332a74f5d36596e1470dfc4078c1bc042d816f48d234b2d29ef02d4efcd4b68a07359bfecb58443d1c5d3312f8b7cb093057f813eaf251cec4145dc2aa65d6ee17229d4d839cf897eef40eb6b85b8970cb9f80242839635a57d10bffefd8b72211a3e14120c7913feba9e0a8d157bdff538ab9c6b17b4c0e15a8e9b61b84f0c5a7cd87b4047219aa5e5f2420d838f2de7275b934293d36eed7b30c6ad5e8c2f2b8cfda3eeb59b3685787b761e7e6b538b5f6b34fa179c1b2ee6bb4166736951fb6a269945c782f37b83353ee859c2ba2c7d01d1b6e61ad001beb1016b04f4cffcaedb06f9986dde62266fc5fd3cb12584b17c9dcda7046facda4a21ead7a7b37d4477fb49169aa24aa0931992974bdfbf1cc1537b1ef439d2e0a763bd1c91a2141f8b5e8e6f0d1c89d4f346ff93ee1564342b430ea68e3254ed312bed718fe263a2d01569c977102f7f408923b0427e5d8052191b022346974d9fda0169aa1c7a75d0cf8d8fc97aa1c4953ab52adfc286f7015b82633abb05c9368367493ec1aa5fe0e00e8e5f067c1691602ebcb1e32a4b003af59aeb05e9fa5ec905c50eea23185c493dee81240940b480bbf7d8b836b0f52de9c9c241111168b05ebd061968d22e5d335323e3406ad14e990182b772a9e97ae72a8d6db19a4aa9d098fda5d277f0cd935e3e025b9c07c90bde7176e987a0463ffa405ceb1f083a9b2bb773f33d6cfa6fe3d2696cfe88cf7668f9c5afa71df1bb75bf35f8b59b116bd2e60f1a1fc3116ade6ec7b8b3ebf636b2d1fa1acd26ecfea01b4cccaee6ed0093dff1f2b7dbb241a5d8f45c4c63393eae72eabeb3ca33fbabb05679cb5c7fadc06a0ac577e79b26edd3cd1f8e808e36f087ded9dbbfbb2517621f5c179278d989c1709c15baf5051e890f0d88f24523f9ee98c676cb289316d05a211c51f37628c4eff4f9bb4b8d1dbb914cd8466b5334028dd9cf14bd039072a2dd91deda4c7e7a4c34fd7c59a364a27a880f74c92c8af51576f27002c621c07de88aeeb015f28af2e0335caacff5d90156791be9834befc82543485c731529e75b7f15a39cd28e9703aceb142aa2064d701bb0b2f71d9c97182f8d5c0b4a9f96eda611807005f18260654470c6034d3d6d0a263406d132557732bbb02526c9d1ef6a3a8efe249a6ada2ae69e2988ef46d553a573b17994d2b9fa3c4eb466ac4e19d2efb6b68f7f648dde834d2557740b889b1bfc2cbde94249e6c3ba552d06155ed24be09c20a310e89e7a8999d7f4313e2b001fc351dad9cc81000959289762bc76750bb88c6357c7e1815a3eed994d564fff6b2b3aa00b91ac69d1b0ab4592de720c9db3d5f0a6fb818c350fdaaf3c509fc428f4961920aa474aaeafe90ec2d1ab850019e83ddc14d0961b5742c6a0e93c93d197ccbdba42474b8b17a5a7ba5b465d311f013ccfade23d07f4d6db33bdfa007a7186ab8f9075a127c5533618d1286dbc4391a9b2e2eaeac9a1bc41f26c4ac6c25bd6ffacb24816431836d51462675b5323e4d900dbcf144248336571b5a519702aa54c55fdf827b314516019cae05446084b2c1a89e1b9fa02687ed7f3710ad5af3aa89826c09430eb6199ade3a7fc9a64a8daf07bf1f259c7e3ff52c6373d6806bad5446b9229e94da4f66f5547aac7b20295a1e0fde63b71453c065251461423fb6920c1c4b22b2c4526ec1dc743886791e7c35826d3d871821332757e70e265f685fdd8c259de0556b01d4834d1d365126d765e6e0d3842551f81731d139a81725eed6f5367680e73adf134d29401c0d97a729cd85cd7c8c4763a12b63a3714ccb5b4c3740d3469725c97f25147d0ef6e75e3d7b4568b55519a2a94092edfedb4e292d9ee74491eeb24057ac8f9b10fb18adba71410ec7af4d0e71016d118f6c9468f737d8a447d70d89151c460645561b40394d605b74573179d27a4270828a2f0b4335345f5e0de95718739999127b2f59140d115250a90196aa2e656160225262a6e69a5a0c8a4e0aaac128a9a14aec2aaa0c809c55584d59904ec23e870e341f70193c53704da9520ae9ed547dbf724ffa29cd4478abb76a4bee9b0344c98069940a47ab8f61c6c3f14b6d9211910dcc3321919301497f778aa92962f9c62e3a2617abaf904a0a4b92ec9271859971e335a4831134a5a897ba0b49027ebed285314292bd72ebcfc838b7dee86449bfe18af38c3b645d0e8b3a09237974f1ee83551c9b21b4ad684c98a0aedc6150f3c96c0c45e429f1e6810d10c8cb92d0adcd2818f229fdb54457fdb3bba0ce4c28381bebe2f4e95ba236d43a65d496e3e5036262ef8af27556a9ba87b10c1ec14059c1ddd655a9306883a3ef765b86cc7194f6b9f8ac4eceb91c742858cd06dc0927300a018797d90bd2488458341bfae8974470c330731abb7f19ea41221d18b67a0e308113380c2103ad32b4c423f3e45da9a70cc331b47a6528ed18fdf70a235fb8fc611c2036fd5c1478cf136e459541b583f3dbad5fe24e57464179ce34dcf59e5b7b8e19038e805b70323de9441f126c3fb735f93cf0926a8e030af2c39c1850df11e727e3c99e17404d6e2fcbb7586b029d514b93163a6eb9333029545d96bd0bf0e776ad80ca3211ea0e5dff86f1469126667043d215a751fdfbbc007ccc4951137ef0815527184b099205a634400a567c02b641850d1a74c1ef26b5ec5b5cce2d6761351a9e2dac4301f45faf654e0c69c00161d674564eab2b8f1b852780f1d13b2b077e4d8634276c936898e9309a103fbc0de02ce68079453281a138027a925ae0db64ec2045ec28b92d43a241e96eca770f78845fcadc035a5cfb5daac514aa48c81b45ceb2c71a44812a93f1a2e6fade9ad417c414b045d2d772190b738ed033465f78d969d979f9e64aaad59dec6215ea1e50c1540dfedd3aff73f2f508bbdb7ae1d811a4ff7c27d7f8fe947e46bc564042548c3775c0c75077e833731921a9fcc5e34508488a7c9e2c6e6bce91ace3c3f2794f5930bab94b2fe3444a2cc46f15f4d4213e78ac06f8103063595d291033543c296ea85cd02762854f53cd3ee090336861a9caba32e1276c6bad34b47ba20983a23651918e4f879dcb234b0dd48b75c2c83e1d8c704b76af81caaae88397cabacb0a9f2e9945d16ea7ed3275ca69bcb2fbc63354388816e1f39bf518b91aa8c0bfd536c5aab6fae4237a8360e50d7a7d521ac02c4ccdb742594aebd3126bf088a21490559cd8042e4d064b005e0275388273075349f9fd4ebc7e07323761094c363c660bff6c196cc81b61ba0dbce42db7cc3be78385bcec67d587bea8c82f8b80c46e7c3eba0a6ef17a4794d599aea1f27637de12ea384d50c4d81db9be759b5da551a014d84398a686f0a8d342544596ffc0193c23e11c1635a249c0d51e8686606f7a690199221ae2d5bc4d09882832ec13925f85e087a944a59511bc55d47c9d9e9ce193a62b5e323d2259eb0bc216eb2ecc55f4ff8be7306cf1d010addf6c9663d0c9ba50362494141cce4ad093146af1388ffef4e202a5a9d526e005ddac2346e2186f74ed0a735956293968f26fb96637d39a1a6111dc0be4f5c269950ea9a48b517054282f0fb9fa511c9f0a7f68a5ec000eb891d9c6aa34036f9f956d938b770ee9da52c0c354467993e8bc8f2a36c4fc0cdb713eacc7e2ebe5dc89675e0f28c03a7804e02cc6b14b332d438fb84b3d905ccc339ad0be0227b49da8ffcdc82db6529140bac6db6cc72c3f88c557fe1e1e38a4c2160428b16c7d9fe3b02f1937d0467d9b3a9e4a274195e1a42cf30297abcea7c31bfc0a9dd00810ea892229ec0a1a536aef95b8bdaad28081fe36ef58468b5a947991d7379c9b02702613e05a614f5b2963a0ba25f62af17fa973488da43974f9458c524dde8b847a14779653d1f8a74cd6dce0d938ecf30055ca210b47f1d5bddcadee206a82ce5852343767ac014aaa17f992494debe25a1e7cefaad09aa6c0d71c1663bf78102631cb01720501909d099d8c61f69888ee82f521efd015ba2f4338f05d79d694790533657fe62df82b76c5e3f34555f9b2f0388959eed41dbee63cde7829eed34938b7cea7e5ea05721dff8736fe868d0eaa79eb20ec7cb0c40d6a0d8dc4d3564675852570e119ac7d5c3e6263f4b6634850c63e241af1c1a095c0d4fb66ebc9074091c20aa1e71a78b962d0045581559738cd80f035c03a7b7f04a4f7e2566faebae7d8d05ab4fab526033d6e7b680eb06952c52fbdb5ea0efee01b94e9ef0aaec20b5cc383e79851af3eef1384219122d5f64992c7e0688242bd339ee2634302f8372d24e07cac434d5048972f91b5edd8cf1ebc8bf592b849b28f2cb28cc53e7c58c28034934eb3209a557c65f877c519c61c0e1f846a91f5bc59354facba84c538b4644891fdfd7f201690bc408524f42a18cdeef077454b5666c9f73db1947d315b6704cbc03749b869f38cbfd172f23508722bb45022e4d2d19e3473b78ce8d8e7c76f6bf2469b91bfedf7d434366dec9cf446cd7290a477a6051049b9b3a2d46ca6ce6106f380c42aaac2546dee4220de8025969ee4127e03e33db4c750ad03c4eb47d0d09d00b3c291b78f5ba8c43f22aeaede4c608809258d8404834e39fb0b0a65325a7bc7952642a0832599078e493ec2cc06660aa3b209ebf3e6be44735f01486b19ecd421c99c9db0c8bc9326fe912751b93659e966df19f2f612ae60c45b30ae292977d367f113de79923af9c830616cc85182911151cee510f6cfc829299aa30d54d4e1dd864e5365506f34674402bd2ced601227389e688ba5956fafa4c23a2afb174429f836650d6257d8da882b62547ce502adbec9dd3ec2b57b4d1deff2705d250ba7151c148794a88d3d622ac66735dbe9854f69834ab15353ec0a964ed807ca344d36f3a653022dcbbae47f6bfffbb138295dcf295d0bfd48c4ddfab0cfb26503411ee88df07a211104ea46c096ab10c75fb7f92b716206bd604e2da40007b826934753d087f6cd1437305f6a4f3242c7c1037e18c9dbb18f8a986807b1201008519b2e928f23eef5cdbc136241ffb90e3dc1c3861d0b85d0c39d954c91be06dd811ba8a729ca33dd2442d380fc20493822d271e08b1d6078b58f284e13737f4d309234d72e6d7fa95e6e1710102744c75ec0f461c2a95d3a0a5901c1dc2b1c0b77456ec957fb1eb6cceec0de6af21f41200a60f96b564df7d8d866800e91a6710762ffc503c0a8563e419f370470806691e3425877195eeffd460f8616a6db49f344119e9d42cf51066baf2ca66276026e7ffe5e12675e4997a4cd8c7e8cd651c42c93171459053dae1c018fe5ead70ad33bab5b8de79ef610539b72f40c3aa1a36ecc4e6c5eff02706d6b8591b397efff9356660bd4807e7e550e5a3f01fcd1ceb045baf9f801753857c184b8e148f455d5249e83624674dd200a9ba1a2fb75687180b664884a179b4789ccbfe706823eb946f52923df072291c3b3b2401589f1debed268add0dc325285ede61d13c563bac7f436152f1cbd22890f82c8a46e990b3e2d3f83d211cf51e5ef9840058d93e6a3e7c326c1600d1611e37c01c6c371cc1c29144e2417cc74470d8e30e17054339ba0bc471016fca136828e8bb4b08566c25f9fee85345986f082ebdf5a09c34fc9d75f56974e207b1b515d4d6b18d1f1e80c006181508762035a080cc99761c841efae19dd4d64da264d051270ac7fd81dd99859b649b2372dd2f690402c73a9a963d028c63c9284d99c12c9da83042ee40dd5a34d742ee56be3c194041f1470040c51d395fd8c175dcd99b2ce28edde0c7e0d70da0bb2e5ee4a050b578eaa81dbf6dc41ae0b5b2e746ac183cfef5321ae295c5b6a779341a0959b2f83f289269c5b53f1ad7f9894e3be87400e0caac44495b05d3dbda2f9d34f5db7e5dd1d8f9b8aa1bb6d2b266f96fa9ecf798c2ed357ae93a542fe034f72aae4aa91ca5e3f62935ed202ce95416706f5edc7a0f105263c9d2a3391307bf922dcc8b976a404d5076bd9d2c204bf3e91e4be997f0bab2612900243432e61b61215dc69aaaa13b8e33809448830ffb59564086c2d064a53be483cd4b28cdf8505d1a6417342641cb243c6873c251cdd9c1c5a2ea9115ea133815435248a15459c6d145671d27988e5ec0fddcea6cb52fe9fe3c8a8dfcf85538e37fcea54564b72bda0a8a42ff3c42e1b7ed2091c8d1aef7085d92981b0fa4222b46f79a30e0c3cd688746dd05c2c4c72066be4f5450fc0dfc4465a57c8b315d43534d22cca73c6761ff9bba1ee8774bb6d76c2e682c101a538c93e774f73a84a8a7a85dea60e57ff3443121f3317198a4d5a4365f674d2632a4f0540294ef755e6b6818593337858ab791bd8b6e54ab6db0c4792f4aec1a247db3c0899e631690f53fb097a910187f7ed716b162e46c5d173de79a653e0b06ba9bdadfa43a224b7062b78fa6b4f3842002c0c9df9d8300c9b6b54f21a220f963312bd44cb0a3bfef9e79d21e3de39e9fb34653b01e7da5a8561303f826192fc9d6b0b46645cc376241c965282484a9f3ce99ab394ef3cc0980a67711099da8d9474bd8e21adba8a976cd2f06c738c5c6176f77b34b3cf6a73d0ef2fff82997fc88fdbd96f72a952a1f3e4dbf1e464763952d407a198ebf0501184cc938b8465a5069562069869830b198f5786fdab6868085b370b817e2a217572ac859e83f497b84ea0a0128e313a49f2f868fb8d9f3079f6412b81eef22db75a6afffdc6f6f70059f6c07ce988318633c84919a600b725a67f324816c5788ff5ec524c4428fea728b45f5a7802f99b2dda7a250c5c44b81c6053e8d7d8193cb8495b419e70835676f716ac7bb407649242b925661e43a289aba3e441ec9c5bad564c899e6245291603a1808b4c20ed52682fa9c71ab164b46c14c31c687d9a4ce9e3dfb0f054b906f11b135953815ba488134207e5f03781f778874ca5d2dc24add28b70aaabde8203a22cbee7c14d14fc21e5dc529d129803d0affca1931d01c83dca398b2e8a010cc84a98c260c3ad750d18f7f5e8baabf579fa39184b040458921610be7498403afb0a575c59805a8fc53c3006d2ab15e78718b6d5fc876c55b6752cfc6418b08893daa92e0ecc55d76feb897a4a20fa80c9a0be8327f82295d87704028c26e04f58fceb999f8149b886c0dbc060e7600410a5dec8f1bfad7be3a628de3e8fe782dfb9bd10fe898e4adeb3155dfae1359abff85f06d70001d9403076c80f1210b481c68b1c21593668bb91b494a5928c277c47ce152bdc88cb25930033c203cc7e971ccc35c52767de00d87455cc10c507aa8b2bc2f4a10ba0c741b516923ee6ef19df9f27d7beb81a03cc285562a0aaa17b35e2838a3eae64f531903823600c3edd8abac92a2cbf9e2cfc8612e5f91b35a5a61029cc1761c5c78497c7428531c4d02f271e0d040f6a4d13659135a75009f05b0073d6daab224f7c1cb17c4a004ddcced62a9d9920892158a07784c9e48ac9fca26c22143d827a38d0b918361ef3b13cf9e4d113e836e61e8c8a1ea2234b7998a06f1b8bd47ceb02577240d84037dcd8dacbfb5f1d5048ae484b243b780d0b74a614ed5e8a42602783339ad3563593d6ddee1015ea05b405f4e99c8472431c1584e80077d2187844140e4c4c27b8cdcef7965332ebb80962ccd133e70bd0b206ceb24a059137e1f70d997bd344cfb4dab0d96cb64e5c3af00f5e867ab01860503f1c110ab3e5b92e71363e0efe38b94b806508a5a3e017d36c09dc4aa2999665c604e25bfbc07b2d7134ad7d700a886fcd3a99f822f3d7a615d03a217761ba117a67194069dd614fd3a6c3e711fae51172e56032566f9eda875088caaf9d5a082f2285f5ab33117b5b6f5af2c1254e20cc710f59fede2bb1cfa9f86ca83a8f04194fb8300e22cba368e9886388c5d4b868648a7a21a5dde2ffa720388b4d40b44a63be63a3ae0082a569ba1b8829d07a8d9b383c1b112a666cad768a053b534926768837cb5dcc2286543493af91e75be416b9a1c22f50645c902cc9194f42800598f688a992bfc048f4626cd3416c34b307a007bd4959075e095fd6e2d585a968cd687ab075a8a45146ba7cdf5df4e54c83ab383d4a68d065805ff7261d4626adbc3d843470fed256819d17e569577ce68a7895a4e37ce9715a7eaa1f25c0a0239510a7a566d4a80dbb9b69eb608877bc217fc5d06fae05fef7e077e0e842821c899f310a9cab9bb057ee37f1c38b9d090fdc281f720401c3750adf941e107c0a20a9eaa0dd9931be6f4415026b7f1ab738a3733d2ccccfcdfc0b879c096fe22448b5b856738903b553837083ab06c8b3f00fe9f17a293f41ca6ed3fd5bafcb03311928d80330217e54331c5761ed1a4e735c457a544ffd829fa461a4cb6223c61d3d2bb06a55833271dda29433fb3e5da3502fe3cc0a0e18036d1917c2f2e56cd0ba121bbfb8b2f8b5d468c0e172b8367575514ec556d4843713a7ced5a9ad98415ee2eb95352494e5c5677cc7a974992bb868c3bd12d8973ec9185452a3025d2628ed1f6d633586826f423d3f08ed938b6776ec4ee6d4a2e152c032e68a457012a4db9ab99245e4246fd0b045d9d60aa3ef217bc89fc2ea4f007ba5948f51429fb10853766dcde2663b6b36ec02a4d270434103235771a241bf23aa5d8d448cd66127b092ef4120d87b759868eb326f1e11a678239e75452cd01b08992e30ec501174be5abc4edf83fd8a493fcd83805a12892e857c0608d7b29f5b97afc7434c73ef72c65911d5f6767ab75a70d61be36daa18f15c33f1b46b587f185a72e2426aabd9b2eaec28e247fb8e7e777bcc29d02d8c068ce5c73167cc99176a62f9e52307537ff2c3d3c3c85425ff7fe563b3868761ab238d4060dd30497353fc16bc7cb999f7f8efe7b90d96d0c037837e8e88e103f1e64dd4bbcc7112fb85308be8dacaf7c1a7d1757484d0832f20440d78f5ad3df97364f74439087353238ecbcb5a163fe7eb7d438d2503684862a9339c97e7a4d4eb4443103b9b6e42cadb8ca915d1ed10473a85d2c80c572d8e5fd4cedb5a6f2fec1583fff552db41e3c838cac5f7753374587726b8908ef82b8d5c375b38b59d40c6e948bcfc5326f2dfdb28369a4f15e34fd26b8cfe9f74d8407c8160974cec92f5ac806423e08b64bc487073618c48970fb752bd84f9aa0e20cb4486a70d36a8ec5e1b6e8aff462a5666b24834e70cfc85b441d9ff702ddd0ad0a21723b1c083d94e6ff4a32c7fac713bbc8f644942d422958b3ae4b52052a443ae68fdd0682fb77893e9a12197a84b58ff0887be1621116b6ca2f9417433ee99efaa001be62b5106258ac8dfa083f07e6d29d23d559d7f134e7cdb8e559a913a107051267eb0b580016f4d12c4c6748a2796eec2bbd6f158d8c1f13827a03a802a71f740bb978be7388e43ccd33278580a3b7836ff74b873b89954fc91221f5bde535ee86346182d3db46ae60eb4566fd9d4648baffd401e9101c55be6d8896f70c4b5a864f5cc1518e76ef6333dd9d114b029bed9382e996576d376ca364621f7399b4f5c6118876fe863fffc207fe4a4004ac3a59576615f05dec6db2ffb407c268d1aeac0b53fe47607d0279b028fd3b6df03beeeac03a5bdf45d6ad6a5bfbd159bfad3b85e13728761a8177be5d039fefddcaff03a3fead8245faf821c3e7cb510fd31c5a3434ae714920ca7c87344c5b6f61de217f136770ec1bd4854c4793f4815579e08a7d44a3800f0716229912dbe501988c7913f243b450a855811a2282bee930c4fa1d9fad4b95e60a10a8bb5bbf2bf167a30ab57b0ae72b6227bbe7aa07fa12a375f555e9d40191cc0e6160d46ee52604808a6cfdff3f68dc3bb17a8c9a257722e9f19cc027b181b4e3dee729151b7802f8152228ebd7a0d50e000723947ca01ec98f9e7c9d4a86cc3f9de6b5890ad5106339a9d2c3fadb598339fe0604419b02ae0548a03ba0b92eb540236144c7d11cdd014d078ec86e766ce2fb0515557d014c24a3a676de6a537f303126a68b538c74803397684940daaaf9708fe4dac14f74443f688f5d8e93b98ccfb0a1b41045f46a6fd35fc347e903c4f470e0be2dcb1b389458490fe8d396f7d78180a3fb31523f7559491c249dc9f247af4f8feed3182937100032342a8d53a6b0da58dbe4c9f06f711798cd648d836f859ceced34fad9debb37878f6059a63610c4ca27988438819ddd749eeb01a5fb9831ee1f468075375a35b78bfef0bdd401a459c4b706f5df376d6d6ff914ef7a812c3caf004cfac2f6213ecbec548f2258c06973efd0142884178931ee9b03dd624c1899466b76074b172030e8c4775268e9df7bd951a1e23fc76a0399c9210d434fedf33e762c5373a30d7c9dabc6f4e3e4657893bf435cd4dac85a036a9059d624e31fd69465cb91f4f134e6a0ef3e97d7cc0c8598e7d228f00fa76d309762a66efa8e58ee4ef266b8b21174be415d952b312e10d6e4674898e1cf906d80cf77c452b27a14eca6d2d94c6f5fb35cd0bac568b9fd68da404ce3675e512fced6cf7eab2998d114d8bc0a84a9f087be1a88bae6b3594f66c598c8760678132130ebbe746bdaa59919c445f48f39757e71acd320feaa25c68e6e75e5e92b4aee84a84c67d9961fc8eb1ec3a347b94a5bbe75d0bdb58edb201735a6b953af03a6e7cbae7c6a1025979e1921d6326e42bb0fba02e37f5c2b88fbd202a731070536237f4e1db96688f8ccfb1b41958efb2cab993a63cfb22e5c572f768003ff3e08c16f352a9ca56dcad829091edc68d8328acbc11471aed938e3b4c3282024d2eae42ab1c1664394d35bced12b4adb07b71d9a985264c392215bd1f5498c7a8a02863f12c99d903bcff43c749e3d23c18e937ff48f749d0fdd458541832bcd06f4d0548d15c1fd711bd96b8e8b26e665c7dd47eade80396157386ee8f6592ebf7431ec13db9c07dae8171809d7111c27f8495ec4a39fd240408ebda62a75c3d91f32b4228d94370068c8a8f45a87b2d126b69924db8ee7894c7bc4e3e2c7660f6f0e66907046a1958ca8fc6e9abfca1c87bdd4ce6ba7671ae2ed05e009f36aaf90a42f81f12f5d2e7091488844aa9c15434b0212f1752655315560c17e88e764a06c75df4c6298078785e7f80485bb7b7e6fc76272f9e0a136bce5c19be1c2e7843b68a089901aa583fc1323b664f3810a6b2fc4999e220645c120a6a6687b6fd3f8ddb24b3743f979f783a1edf94955f847f7f5fcc690b539492cc9a5b105a3434f1cb3abf84663b4672205aa1e0114d48d731cf45d4a1336c4ba50d5fd8960228d9d0ded26d307fef38135dbb1077d200a70a86ef4d1ca51317cce4cc566a88a5015c0021bd35422be272a0a4338736114e4b7b6a1059036b48e525ef018ae0d4dadc35fbc3931f99b36f4b21b7adcf1c2ed52b445cbd569d9a99878a937009d7f90f8a157bec7df466d3fb8e0073d03978c99de8133c5c23c097e8fe2be40a946bb0800d822435904e811eca1f3f6ad1470dd7f35363b95ebaab44b68f651f60ac5a96453465846ce7d3c7083cf1633aeeed01228cb1fbee83a6dc788c0233bdc71013bf605c37c3cb55259258ff038e88d4b7fed663661374a9f1873930af777f1955887c7761213ebf0e25822ac6778bd179e4b1a252b0deb3f0098c04323500203eded723c662775ad22bcefa66ca693e2a529024f5d0745a8e63a5fe9149dc9693670680eca2c0f66b99a756a041b306cbb63ee216be8a9872654bf445280660a08252193ad8fa1d99624d9bcd30706d22d957dd401d62c97aa29d11011b03fa2a130c6fb29f20f541f2b7d1e0ee9c8a7a86e7596932d545e798ea7f9a000da078ca5208b03c82855bd916d8d0e1fb1d3c1e52ceaa7da8650eb4f975f550299ac5910e998f9a35191099baba9d57d2867ec0449472be8b3f1ab872ed958579a43257d35ac58a97d892a86106e0f754a3208700f9050fa310d719a07ff8877f38e739aff99d151cd5edebfde8ce51c404a692acdd0942a6ad9caba2e5489f6786f6289906f307c919efc2b944e3fd3a7701bec18279f21d06cf00891a35f24bb2e2dc6a1859311ed3ddb1a8f8a5427999f55437436d607b5cf52499175a9467c5352cb1177442449668a371fd46379ad3f8c1acb94fa10a1b3de6e6f6c2033bf18ca5cf6e2bef8a6c9058c3203a29b2d9b99f7f751e37c504b50df1fb983abbaf0bdc96a54e52fb3b1d14fd850a61fe34ad1cde2f07adc507c5d615379f1b72833f5a41224cc6b5d43a32fe324d075dd40d26058f0256b8687f81419623d66578a2ec112a90a12d4cbbc8800f8c06a5bf3d81c5da549b58e731b7f5037af7b54030941cb6223ba9f3f78a6331a792bed5f528244c58644e0e1909c13e4a05a62fc8080c94f53a05e9083fac51f6cef47d1a7d8f7d792b7188008b1b1c11955477729d55038b951d6ee2e59f2e964bf4eb1ee47a4cec04be04e71a104d53a726b48ffbfbe71c76af40ed9ad9a840539f64155887c3ce9999ce11f4e040d3ec220155089c492c4e7acc3498e5660c94b67a6f7a5b54cf57371fba7b3536c456d32eae568e208d300c017773bfd85bc982fb577a1db88c1b371727d09a944f04db70903cd83779e41704b9873b2e69a5f5ed584791de0f1aa3a173181e2ae6a2d2181af62d34e31478e9a7e98c5fa5c5fdd3197b676468b95ee683019283cd74ef452896ed0b408e668447c92bf3083ee2034a802dacdf15d5d4014564d07f9891bf2f04800b275490c305f1af1b0c8b53941277fa8d4b94f076d1b6043c7f5e5f9999df89d1b302ac251767d07d8ffd66734f31c60f9bc3a04f1b70783fd813a37b8a2df93acbed427bffe88b5b6f7967bcb949294297005cb05f0051d4ece0dee291ad751aeeb76b8f295c40ea757a587a2baba28ba9754c92a159b4d31c96df33a4dfe8ba371d37cba49377fe27bd2aeb3d4ca8eeb28d7c51d5e9d775d17ddbdbddbf8bbaeeb883a8c53f24ca56a7dc59e614acf4fe7ee55c98b87fd7f7ce8f2c4e1629c0f072455d27235f0e91627e38aa4aa065cc359c12a1f8f5f98c1b35c6769d86e3ccbc9bac519f5aa48d67049526505d7705cb04a1ac51a78256d07c61dd886b49c51b7585c92ac892f8e0bae893b38a32b6de7451ef104341205bc4ac1a1dd33ed5ab077c3f9ffae777bfdc7d7ffe36c379e8d3b70acc19bda346961b869b6e3b4ff89af89f372e32b95aab5bf5388787998d2d3bda4089c3a71e3c1c6e118b6dde6eeefeeeeceff1615ffffa5bbdb96dba1944915f790ae75bb6b21734c47772ddc05b24380ba71db628c42984cb9054599acc1e9eeeeeeeeeeeeeef68f31c6ffcec2bb249616bc64d48dde9ad6ddb0ea6d5c740ce3f2dc354d93c9598ac832d0adb5d61a43d71f5d59758074a4aa7db420ef0528c748fb48fe48b626ac5746b266b33c3d3eb1093465d307cb9d42dda23cb49483f2c0bad5752157d2cec093430f0f1f1f5e9009e4093165dc167649426ab42ecaa369b3b442afa0719ca8dbf252ae2593352d934888f25c69298fac1142b6da47eb7c105c4ac677afe709d736aa6d59d0d662b413b81f22f5064377e0feea9f524a893c8cdaa59b46e4afc34cbeeca298ade3eab89472cefd46b98d46e19276b8f295c887a257b3f451682fa92abdac468ee3382afee70a3ae4fda95d8e6e5468dab4ee41bea343ca583b167036529582549d78aacbe6b224c91a14e52c0ddb4d675d15474ed97432817a256b269154c9de82fb0bb621ed046a275cd33f6095ec2d6ad076706b42b148576a38dc09458b1aad6b42b91c05e0f219fc2569a486025ec92f3825a9a56da49640c85ac308dcb7acbd5ab0574be2562cf0a516ead590ba25b90ddc47923592556ee5747de07e0b7ebbfe825b9d95b6b7e02ee54abd6b9a6986515e11dcb711c4d96eb8907b45c16d8a1aadcc41b63ae6c337de02f190f9302262c3721ba95bda4bae5ef205843a2355d2b258b16ed1995c494b0404457674250542d1a55d1869afee90ca34f9d224678356e238ed45ca5501989b4238638303a0db4e925c4722091048a2caacba38a642015838c04f5753b6bb9ce40728d71ba2ca8648e6965e37e043a2ba6ca3d7b08d8154dfbfd8dd56dede7ee852397e0a72b923524a29ade8342436df8afc8b63a4942c3c1dd946298574110097532d63aa5b2cc962c2d461e418d9318cc3b1dbe1575bc6ff51f0f7523d59dd4ac1aa6e87365dd4fb265c6eae51843a23579235a4b68d766eb24626ce207278637bd97e0c37ed67933dffe2d0631c23ed86447b52266b9e6ff4cb8d56ce644b12114181727404841b65179265517d11dcf61ebe01b6f4e826978e98e33dffffffffffffffffffffffffffffffffffffffffffffffffffffffdedfddfdef1a1235dafeffd79488b29ddf6ed34397bbb534dcbd795c8816e1f98cd6d2f818a3f7b669ac624bab78ebedeeb40abe1f769bac61e1699ecb9c8c85cb40d870c71bf8e083542131efc0423ec4626cd1c174628ecc7575776f45180032869999b9bbbbb951603ee14d983fe4acb41f7254db0f255c577777733ed022df1112be588dcb3760ae8b9999bd11a6d7ddec24f494dd8ad438dffb638c51de58438d2a3606528db6bbbb636cb1bd9c56667c96657c2ecc2c31509e6bf354aa8539060c944b0fcb2cdd2e1e3333cbf0802e5bf931152eba19ef73fae134c33bc16085c68b8aa551afabbbbbdb6b4a087a9e53752e873827185c577777cfb9b577bbb72235482841e575c7d7e5c8d239a64ff0c184520eb69caa45babb39c6ff046f2b52a3edcfa9eeee8e8217638b53901aadeb792e5bfaa403aa1217b7dddb04b0971dae2bea6c383a57d2580ca31c184f104f5093264d524f9ec02887241f28f6b18ff5ca27c621c54e44c42a8c063bd745578c8843a854ae87d2eee626a7511bada6596abd9483862e8e27c8882c7c6ad3b1a040321ae14b3ae975b76bb196a51c9ba6195104c9dbe164b0ebfa1e5912c748fb1dd183e4131414141414e4ef53ebd9d7430bc49d4f7fcdb1701f9e202f1e3675dc1dcdb5afee76f204c9a24ee33abad915685c37e218a3265c9354af5838c00b2a28a13579d93a909127887982be1a6ab41f0d35c6681e5f96f69554cd3d86ff448d9bf78de09be1dbf1c9f0c550a3fd5e5f14c18b2eb820c2690835daa4962d603411c22908355a20fcc0073d40e2c10e74703aaad16aa1cac284a01c1845c1e20a28272b02e09daa1040b700022035235058a0c0d8d68c97aa5565ed088a9c5091333513648a3ea75bdbc39ef4141d7972655c8999ba654ff0a53dc6bee9538e11252400dcd28f0dc96325d88654428a4026ac542759d31252605752a4cd53601b525b8f51241fe2b21d8aae8c32a5c21fb30cd9953eb5a75baa5e756bab7181553c6a58784196e3051850aacbb2ccc539e19cb04cf6aab6d205ae198155d2ca367e61c500a6d739406f8fc0352f74abf2ea85c0ee6e19b933cdb068aea0ab71630f174de03352a3edc008b2bd3025c3c244903fa9aa749321e5cc8cb528a8a44a258f2498b2dddaf8c8500ec794592a550f6248b1a8e3811ec9c752f8d8b589750fdb1fa3174b5ba7e44a7ece5bd0b85b4dd95ea130535556d557740ce35a41ba51dbb81fbaf2a384b402dbfedb5fee045f2e920a66c870ef68430ac531961b65db0e65d4c84801070ebec1514617d423ae6bf5efd3ad69f9663edf7cd1170da9b1c6a49438b0fd1eee4897f3e448979312834addd9ec8d28e37e0efc383e8728236fd4b4e721575c102e08c7235bcfe373a69d3e5ee7797c0e11c7f3f03dbc0e9fd33fccdb666aa5f0f4e48468964baa417cc0fe0457b2fd1e8ce379441999c2d3add7912b695fe775528cbc1159e34a299252843ac109b9f275b8240af4411ef63e9e47c4f13dde873fc1eba4c43cc8e9157d70fc88eb8a2699fbf8b8ec3a54891e2edbe781715023514676a9073f25a439e79cf4892b2910456224e2a04758465ad904c748eba24a5c693ba4b15311b1d3ab6ff0eed6ada9bdf59efcec608fdc49613df7391c8d71cc86c4a7b0983061c262c2844984b1ec0a875b91875c492b7df070de402b457737ac87ec4adb52c6379ec88d9f477284648bcaa80c4a7551242edac46dc21483a4b20f4d2f3a932b8ae44a4b91c89a169d5199b3a9a3d861797b2c876fb8752547641646aeba05632d8c889e98e3ddbb1c9e63826ffd3ffe9b9cc52996a75b26cbf67b9e88918f3d8c6f74d6c6956c5dc63738cb1d7d62e37b2217d2a28e52cd46fa7259d6b8b458dae15405dfc8c71ec903f54ae676f83e3f6ab49ee57a38f926903172eadbb6efeb81a846cba72635da1814ee54a1d332c8acdbfd09acdb4def24458d964f4335da93508df684831abd1313ef34abd19e9678a71bd4e89d94d4684fb21aedc906355a8ee2f34e505496d3132d30eed62d9622e59d6a00a3e5440317efe4448d33bc13d08b779a010def94c47a271980de0949e89d9a18c13b3101e39d8ed4f04e31124e4bd468b7bbb12e65b2f24e4a98e09d9238c13b1941c13b1589f14e4800c03bfdc878271f96773ac286c77c8375d976365826ad873a71777677777777ef52a9f0ada5e1eefe3eeefd2ffda393a0a7e6ddadcde69cb2fdb559cf3967cf9edd9c737afb12eedc2173ccff7777ef9054d6a36d7c24ead04641658363a49da50bc4cb624d6d1b00c7942e10af0d129b0d84e14a36c852254778a28e4ae518f9489ce0945cf972797825edb6559e21277852848ed4088363529e8cdcb9c9c3f7a69d27040f8c07d6ea84e0e1a9cee3415e0f3592e33ee0cf651b23293ed8325252876c7f0a7e9d83912362ebee342f96da3ae8f7857044cc6eba4f9f3e478040ca8f109c092356bbef876046d6dce4b6d33cf2ec5e7306d9ea72e870023108563a3b977244671991a6d6eecccccccc5d0ab8ed695590f26fbbbc9d2e1fae106788632359ad727b3337c749dd4cbbd9096a6e5590dbf469b539e7b41f458c45e9ff52b814b2631251fe9c433f243b32e13ede030b642df5c42d1568ecaeab4ecd264a4dd96288a32055dc10735437294a503b1bc82a6a8909195413911d4184081104e63d417ae82982cc10440f7a8031a27e53c426d553129f2471a27e45482e766208c28711049103abf47292206a08835002976805902b2293ea97930079720590a0bb417143cb55b15000168a006142c2a0a65c4e02c4880a840524f88a00240551809040faa8d58b2b6a0b163da49686a492243f925c57f522c90f24ea4e921f43aeabca1924f901a4d69ae4070a6aaa7ea9243f469004e683ebaa433d7553d23ef54b2252514b3d4ea4962e278105b144455d4e0223c1953f9525c986aa45eaab2a14808503d494ed8aaaac1acd66b3d96cc6840913264c9870b36f1cdb940c59b35190bf9dce823673bac0ba34286523572eb0ae87305215a4eae1c4ff5f1aa1838728709038c1c112321431801c4c6083155ff4c0210a1a783126a7231e4590a10b26848e7e80ccb04309901738d0e06505caa150c17cd54bb1666c522072ef93917239091050dca68117c3818b9999999979d37cfe4fd79a9999d66e8eb9ce24e57602fa0dd568bf99bbbbb7bbbb83d3dfddbfef64d3ee4e9f74eeee1eba6fdf911a6d77d409333377a3f785b6512ec6d8a4f34aa6949aaa757b3b6b4ac9050ed04dc5050980565be94c2556923be5dc63ae12a31bd73917d53d0cea643c14e8e69534d72c4b6e9a974c1e644ad182a2a4bd756ba34373f3a4cade95f7c61ebdae631762c42a62a1b8c4ab89321654784166c2026e5b5e3d777f03dc6dba645a299bff11083d8cdab41cf48202b845dd6918614d60b208932a2611b681205290acfa795b0ba374772fb510de8e91aaa8b5710a1bbd9bbfd882075138e5abc0b769d35fb5b2600a0665bdc00ce090bfb892b7b892a35c298536fdfb25eb5669dd2debcd5a5794ade0dd18030b1893947f9d8e9a5069a21b87d93f126bc372eeeede319b4b34ce574e5fa434729d572a791d4797f85862c4019f4a2b701786252ca89667f94a2e4820b45a0b9a6dad2546aaa2c2d67b5a41dd0e460c8db5f20161d85097afe40205beefb412c3a59f3addb4d9d555ba6b2aa5b8c8d0628c81291895d5cd4040ddb65aa3b6844c3159191eead2a06ec62c79321411ba28db9f5d2cd5ca823694001dba384a6725cfc01472afaa17c25430704c1253f32adfd80860c338b8c559695918c7b42d976d84758b0b61e0265c110683540555186c06b8a6c319a99aa95e46969c69630b36e56e71960353bd22e22ab3dd361bd60a8441bf0a7c14b5ba6cd32d8e0e6dda7c2f9998f53c59631a61bd929e4a85b012ee4718ba35005e49cb4a22ce50514441773bb4e11869bf246cbaee5293775dd630ea7226190b10d9da7c8f3b88407ce3e5b2cee6c465bba5eaec26b4089439383aeafea943aa5695b537c4000644b0c0e27d6aa95a55d612f98af888f87ab48d725d4f6ca360bce922175337fdd3757747284c58a964b27b781cfd24f133c26ca6c34c87d94c9b4343433fff0f245572f683b7cfbc7bd645d7dddfdf5e58cf42d336bafd88e05fae06bedc0864c76cc76c6868289594f453b37ca3cd94dff4e410191a1aa241a51211a2ed981189724444488c51ce1891de616868e847889fd96c369bcd7eea309b433ff483fbb74f79f43fbe63b664674a9ffd337dc72c06a5aa9262f2baee87b3ac6994eb381d2a437528c5f415290d75f253b063b663b663f6bde01b02859b82114fb8916fc418a3f709f1b9e06bc127e4e3f9763e167c2bf8827c417c40be1f1fec03e2fba1bb873e157c3e3e1fbe147c3d3e9d0f05df093e137c3cbe1ebe127c3c74f7eccbf976f874f848f0e5f0e1f0ddf0d9c0524ba23ef5757a25ade3f85c498bbe47a747e73f9f1e2983b78c4926ac54edee2323114a54ea718b03fa8072361d0ea85b2c0001b180457571441c51932640409e94dab65a5329bb42f72dc140451e031223040505057dcfb75b16e298233ea9ea3112ad8c31cecf833cdc993f3f0c57dad20840303a2a1e4ec573fabea65d9f4e27afe32c4521a17e804a6a39a2a5a725d6d204aba295615d322ed6a572d9546d0172212a99b416d40f5048a8a496235a7a5a9a6889c94025b9a09262a09260a0925049a8a416a02b2d8c0b51c985a8c5aa203596528e16ad8b25032c61296199ae05847da51c6e57aaa89185c69752ee0c7008b71cb8633b10036f35b05d0c68002584224239e14c3297282e452828a828b4b3b5be18a18eb6995c4996ac4617350acd641c83128a386894b08c131411cd922b2d277465cecb247389828a8282424db2ce246b932cc52433c94cb2251ca3843593aaa3ede83e092f672919c06f0baa9443b32ad1dd743b7007c672e04e0d4bc19d177086215a2c0ddbcd0eed8b2fbeb81902031aa8b7b30da362a4a2850ad2ca9095b8e2b3528455d146199654ac31aec46e8ca8590c856a80a6995cbd4903d2e209235943e304ab244d4e920a52b7687858194283b3c2d32d9a18568a74b1e2236b66861093b92e152415247b5d2a4825152415a41e2a48a995d8951646a8080a6a1671b0ae8882fa429a29745d367084584b584a5eac24a423a32222d6ac2564c938860419360d4ce2e1a92d2fb6850377606c07eed480a9215150624c7ad58071b7da4945c6312e4557baecba6ce4b09eb08cb4238036706cbc7a950404a9425a01595058451cc31262cdacb8d2a2a0441c2c254b220aca0c59321264a82e934ca548d6b86d1d97a2ad6b6179210d50afa41f79d2c77a7660493cd840c382b54b799db515a4d101697038c6d2f4e09c4b6303d3c4c0335ca09d23b09f2422847a25ed0b0da3238e61e98007529c64a7528e1d1ba39463c7da181cb8632d057754688038f66686f5a3888312451969699154fd806ba815ac62f1a36e51a33e825bdc0ef7703d52f54d700d67848cbd499c0f158a384c4f4419152bf47224bbb8bd80342d200d129a180aa4f1218a38688eb00cb582c6c82d9235343faca269e24acb1212cd9cc01923a36ed1c4c0ab1d9e49bab06ecdf48057461cdd1e593393839e2b2344f5e3ca1e57f270258d0dd428e7ca5832d6ec898883c58415647aa28634af1723d08863a4e9e5e88b3c3535a3822fb7439025eb95b429208b0864599065d42b69bfd20a32770b594245f505a401d29001d2084de02c819163a2a501d65e45af8319a1a4852f4635bc17f07624d000fdf5ba6930c6e5c09d17dbbd845bd712605c0edca161298d707b7999317fd572bb979719f367b8bf20bd18c9587962a2104301b05d0d7419ef45bbdae6a1502c3f5fb3bfd9d7260b4b0e972297227f9f2e453eff745a595951a92f46d2a69472984a284175b15e8caeb4ac2259239b09b188648dab026df238c554a26991916598b85bf3ea74b8c808f4e87eeb1cb8ff27206ab41e9ddde74ec7ec77b656a493de47856d217687afd2bce9a6c9684e9502ad75e933ddeda6e00e6737b0e9d65c683313c5ccd3f7a9d9ccccd8cc1431332711ff36f859fcb74177f7ff307b1a4aa526b57ddba17603bf65c338d83bd46a60b7ed4e859cac4637d1cbb2864b18a2ba52574a1822d42e7916bcc2d40a191646965ce4eb86b3559e2e05745cb6da06ce4083cd0d0d35bb4d70b314c4b1b9d92c07ce40c376b3591c9b1b6a3bf7f72da42b1c2dcd4e9b5d49b655417660b31ac83754b32a74831748dd8ea5046837032917d06e871478cd828063371e98d6039aad975da20b872f68e1f52235eea06307f8bbf3901881622cc6622cc622a59452d125179a6e9a6f2517de76b8b577a81d5182df38ce6e6f39ca6d94d334d7268fc3dc27cc7dc2dc27c7713bdc141f468e71ab5d8e6e53f88f86c5c60d78000bdd9a40af607800d76480526a350a4e40da882333c035110756495b14777835846d48db816ec51c59531383a044624023837825a3939c213cb0188b3118cfd387c99af9f329b57108b7e80ea59bd62914806ff48c879a4f9190c776223bb2bf3a97ed6a2bdd5daafa49943deb99ec9ef5acbd673df3ee9efdec20346e52ebe9ffce3c9bd989f4ee666666fe28f3d2a0195d6a9135ced4a17357c7bbbba5101d5bd7eb35ef5f2eb5105fafcb29f6888e61da4a295f06758ef8fda173cbc8edf37fccdc67019b33a8940c9bcb69e766060002100083160000200c0a060482c1304d03414bb5031400105d863c6e6038190823498ee328888220888218648c21c4208308320a11510d0006fc95527c1cfca1932aa5647a00254f0843b0651607b8ef5cdad1f8dd8de36ce92b0f9804fe73c0b0e6915a0827cb4b830d4e288c243cde880282a07ad13ec6969e18e131c09fefd9f9f98e4fe831fd1df17e79942f0a2b067bac886d287e82888882318123570b8cfe05bae3cf0a0939cf725e442c73abb42047da94d542edd38e6d0195c98b494e2c9c3f9c5831202c42fd0693fe170f14600381c5304007825108580c0045e0e70c7efd420ccc7e7dfb3c6b6f1c0a50fd909dec21240d98a68fd17dc6361182598c3ac4325e9046d75af161e8d4807500e80a1889ec85bc40d791cd9e3abf17d2d4089f6ab7b4a9787ad2d77004177bbef54d93bb5f023c55938e1d2a87593225ca155f07013cfda95337bccc0a1d751d5fc271bd0302af151adbf19770b8c5042c20bb66c6cedb689e7b1bd896b3ec51be88c9fb4ef764f2162ddf01c6cc44a3b4b04feb192a97daf83da7f9c6c13231b69c9e0d21ade7c2c741100040488b062cb1cf4e46ae8ef6d6b2b84b606547cf5ba91b7711179213735138848730a009ca2a114672287cbc4b67e08431109ef77f6107040793a8208b443cff68d608b6451a1dd0065227cfb87d23568d38a2461926ebd7a0466235fb413bb4342165b1e1010159a7463631b480a6b99b3c35df8b665477114310a8ac8a699bfbf4e7431fc2938bfe01480ab9f39f943282e654c2502f4c4e6ea87446b03a0fee662ebfa28843bd534b085a720a8f5b28a06a2bcd88b011526332be6e235488e4aaf7372a08454dda58b86b5debfca2cecbd3cf0e7e19b2fb330b18163e865be8903e33d920bce73450eb8c4b350315a7752f98f13a289d3d9c16528b8b0dea95a1391fcd8bca5e6f73ff0cbf756c09046dd3900e22c3cd4b18520dcd29b20b04384713360395c7fea3f373050abb99a1f74a951a30b58fc806617fb0a9146f33b2f9ab5bac545f33451a048c3244356763e1d0266a60bc694370385c987f96f0d02c0b5dbe3107fb7fa5562f589df162b3b22c17f38f3c2bee8110ea04ed16016ea2641ea8bfe066f6dc8f61715be3b85ae07019084c6590461a58492c76531b5ebbc514457fe5da9e18e3440844d7c6c36b9705463ecde9d571369e4d45dc701a4caa48176801c023be0d235ba252ec553314ac04cb886f5005c6b6f7ed52512f58000390729bdfb6a03c447e6218ac50991ab380de8a615e943a2348bdf39879d22a23df4028bc8a00d009462bdc21fe152532ba100c16339601f30f0418d63627512126bcaf2a52d5f75bd88f21458e7b29b488372feb1e13a67a99cb07e3863a5e90f401a5ae00bdf5d0050014aa8c03289bc732845e292a0cfad2085288f5080c9b31738f0fc9d9b8e712091198a653be3614c137636a5ddc6d75dded523655f3b15542b50a01db4c84fd1e682bb88076659b7004158a6ea3d7a159bf8cbb3a5569dd190cf8916d2e22ea71d8de0d5b00d2726be84cfb050e186aa838a496b35bb1bafdc57a07b22c990fc7dc495046a9a56dc96c5587019210a38d7656c5d7921f3a6ec01a492385d94307c57575731d4cf6e812a7624099f72b40137fb7d82cb5f09c9ae070bf095218a5e8543cbf0d2b02698e1fa520471005158b03ae2a476935c735db9feafe0b5642a5509c207c2ca8a4f7ea22702c3c0652ff48f0434f9f0f20aba52247a0642f82ee1312841705649d86bd13759fc083e381800f00267735e221a412ecdce7a2191d4861d34ac3ad9c8df35878141a5c96f4f78eee9fefe2250bd2e60d9cf85a61910c7e56b9137f5ef6bd2b6d27d01d2dc4f6e23a2d1e4fe680c0adf4e0fa0e4f59a8d4c2f7e3faad898cfda6c339e3c10f5da2ba7ed55978509f0e02102e863779e3a728bc37a38b551130239ce547487615a3f0409a07363d80022476e8bdd0b53f53f511794b62ba1d4b89f0c06b8a0280416e758c4f151bc8e3a36317755ef3452707f38ad6b80a6768043b6af093ae6f8fc583b6ad00e212a31143168e6ef6d59400dcef18bec04334ff932432b8b2f2198a135dcc6d0061b3eed72d8a3ffbca7210415a9b850bf6f2d2b1ea8a4655b4ab01885fca3e21bd34bbc9e94ae9eb7b32dbb66de7d411e34d05a010f4af2c2890299f408e5e067e62025350c81c06cf631c48ad5d70e53567ce722a23edf14d11facb840cf34a84f6476862b2ff6d75fc3be37df714a105d87ab8a12e920830e613b5c1016f705faaf2c59fce221684aeb6090ad9c83105a819bd3053d202a31b01800d3c23690fb2ecb126059f95a18d4d6b4d9c96e8a24af38b32b4ba03fbdc0ce32bc7224d2b2d35edcd40cadd73ac68b8e3a368c41ba14b2d6ea080b5e755b88bba4016be31a944be3a69f46501611bc8ab132bf44965387a26a6449b3e6d750ae18483c0a7e2531cb42eb4e5a552631427aff65a1a220be858b32836dd86dfa9ef561042f64436b2a1938955f2951229a0c27180c67526280134446cbabcfc8301fe05aa18d6b23c5035124ef17854e982cad178ef12c37d09ee9eb23654c9428b9b01fab58840a1fc0aaa91fe6be65027e6ee764147cce4e32848e201e2721530d3458453a3a90c34389fb769d9ce863f19b8800a304ec02c8708846076696dd014e58999411e10e07e8550f696dd87ec6d94f6cd8c6a272c9ddea0a83687f0e477322577e34f66cf7ee533d703c47acfbe80e747bb9d9d995ccfa360de67f049a7f1e949e8be59448b76918bda4c164a49cd9b8d944f77d2b60d75d09d6fe4c9f8f2fabe3ae1b7f53a8d23e381543b236abcf754f7de5c7011c931a16ee8034b26653d7c4377bd050010fb020f3f83315658da4b1f98dfcb865b7f24c5bbc8021d0f700df55d89da774e4a5370c624468502cc23fc0aa0aad2b2ae9151bfeff844fd4e5c85bc0948ddc47d59eb5b5aa2a469ddc84decadd1ae982c3e51f8f167438ca23af85fed5ce370b5414e5fc1f8c4ab1b29258e5868c5c3da8e555a42dbe10aa374813dff39b69c77543f205cbe0c14ccbaeb9b722989a5894b2c618cab60b1dc9f5eeec9f18904f3be1b85c42423fcb79c0e7fc90492ca0c50fd70f102c69b209b3c08824cbfa294f05941ae29e903918353dfa012bd4ea28ac7f19679a5fbd49c66374773e2f66151969c94fe2af25012d3c53ac0880c2a0768229b11af349e8e169b84c2df30f47fb12428f58c27846f518f2f8e05fb810b483d58b4d1b0996e9f9805c7be1353e36be3302e218403a4b5a4fc614348839bcb91b9f0d5a4319ee7b4cd2d6f14108657b7959d0b185575bed5160ab42dcb9d3fe1e472099f38a9e807c314ba297c53dc568b5e38f8f19f23aaf4eb7c633796af6f67ffc8d7f70eb1ec6776e9d541bfbbd821a70311fb3d0be2e56b70cb0beb2a85f0380162e3413902a6215e1466c657efc6b539040fe2db8f30a89b1a51a1bdb8d0a409c5ccc4ea6ac510a1177cdd2b33e101c7c9e85cbca830c80d83cee910c6c147c578f6075ce59a2f419229e4a468d0fe3b351127f73f2ee32968e9efe434e0c788ea20702bf494242cd5597a7092b81be1ab38651186780786515e5be56e98f2a8fe5bb14ad179e876b5c7288ab31aaafbe2ec87ccd1e374bd0a42ba3bdb60714dd6e0c89773ec442af79c7e5a363c8d0a6046e4f4119200aeaa79c1e798285eab6bef8524877a92db94c51c31b55a891c7a9623a6d987a191e6e20efaec7099d7f397a4ce922c876d3bb726860566a9052694ed02936594c3b824ba7aa7815106a89dfc2b40d2ffd6715cba412c620f6cc24e98cc9a4ab478550bf47358fe338a6b753c8a8aa63a9eaea8eac88289e1e7987db4c90e7b29a9b477642cfeb2aa56980a15bae0b4a5f202a3e7b570e63ace12e551a7cc761ec3948294a31dc53d06f55a19025a14d8a4b4259811fc6194bbb78a2e3083841250f042da2ab0bd375052c448de45c1f2dce770e73dc3dfeace028077d366f7a05022f66a8f69048a944c48d054ec16276c41e925f1958236f29a0de6111317e47470ae6cd981477158921b0a2c90d2dea1f8f8923738a7b0fc7542a289885f71ee7f6e92c260ddf000f69adcd1b4cb0ed486cbe5dbc4551de3e088a823aeb19a2b7341a7809edbc318c39816c0229d15af810c046762b521132492de3c83636a20baf30da4a7f64986d11a6320dc7102e99f0b65ec9e91731f3177e4bc46cc51457cab44da2aaa0b21e2a14a50bc6a2a65aac6c7a4f8d9786f78dd52c55664416cf651a1ab9bcccaf1e30133f2a7d6c4eb0bb9fbaa2cbeee13179142b9b9af36170a303c4677f47843cf002f7f31d23723ac09335c2286d7d4cf54990b2b2c2c71ac0577bf00bf6fbfb66d21bac5d75c3a83283c754b2ad8c2a825737e93ea19a601dbd91de472e08cb90c1e34959acf5f4331d1f82e7691527dd7ec46504789caf74c7ff03a97fbd9420a85ae16bcd0a9b1d3ded47f8024c0020375b05970e4d2cfd2cdd8ced9aaa7514f8350721977a16fd83dd00d024cb213952a6ff1476da030ecd696899b44bcb763e8b5a958d0b5bf16798be43fb19a79322b09f2104fa52840b4a3006665669f6a3f4276e9568c042a1e7646cfcc489b183918e519d5f1b8e58cac95fc7488bb8a4be81e429481cba776ddb69e195300ed90c7d5e82d02fa24209d19615f4f75d69714b5676e81db5bf06c0c8c06f1a11508bfc3208e4e5506d755a15576827b84ee7035f8dfb15c14b1b030c417d7db7f60fd494a55b64ce7b5b0de3aa635e43a11cee4747a976688bf8012aad3848398e8bc592a72dc05a272d8daadd490729706ffaa9794e8ef0193457b1e90dcdc9cb17f7b4265ad8e146a41777fa9410fbd21a19ca474c6a79fc012408b711324d9c1424f74b79d23ad260d6edb09f72c04221bba5b9a5ae546b7ddcf530db26f60555fcdb91b048d6321bf43e15bdb862dd8d63e52be1158e86b065e1b402a472221c3d6997b14bbda2b0d984eb97d8b94611e5080f998bf3f21aebcb9d559a27db5704064df9e4babca91ceebec256e38b630c04ffac9f219dcffc836283d6580381c05a56d8264ea35f1656d9d6ac65fb6019ba960aea4cf16dd269082a26b2b575a389ff3324a367bde12b5c0140e6aaa263c19dacf5669c1c6cfa286e530111585a932628291be8e0ec6ea06b97bf5697327310d8e208f289ea9b4d12d643b8145a3239f337e5b527d7c5ec29b6b5b26e74128ae544b31721fd7e334d665d4b28e1fb730ba6e670a7c17d0407b8066e6d6b410951f5ceebfc9b2cddfc308bbb44f3859d3840591ee535b6741d98b0c09644512951397ce95a07a398ac50e8b6cdc56a5f58ffa89d07120d398d12d1c35c94baba9366121b65145f7d81c7432a8c2e3a5325966084674ede8643a3bc4b830f8e9ece81c9ebe6a2083fcd8cb17c1ae5e8b69f8c8ed6b9af1652774fc61fa25d64c979ef64f66813b5405c4d7f608c582b7e283f0b178ae92a621d231c2c081dbf2bed7aa2c188cbdc64e34fe1f0b03711d4d9b86a47c5a8ee4e62bbf3923204553048cafb37e374b3d910fc5ef31d94c8fcc04c5f504f360d57b0190449a9098082a58094e7a704a321322e226d2e01aa4b6638c5ff53e9cfd75f7f51d925c9429b706322ca2b4a4166842a8d03873cb04b8966d8027ac9a2326302a1c4202017d93d60a19fa81b6cb997d0763c79bd200bf3c402425fe0ba0790570829897227d8f399e27f12a27cb9df21ffbcc7271d13fe6a3058311641798c21ae5dc10d87678f89c8d43859022fb36a0ae3d8de1cbaf49f502d98be9e06928e7cac0e1142c5cf2a5932ccf2effd762001760618b8a5ce0a3f6d09965c52ff339b73f762e7ee1ac45259c5d4d33ca93812fa96534c7071527209e2597154c3dea3c9fd321fb9ce267be13100a5cc03c0f848690b7df7a69586c7c23fd0e6032cb52cabf0ddb251c42b12a8448bc6cab69cce121af090e906acb5255d3473a4715f96d194b80c55ff72ad900762a6143a39a6e6ddfc583090c90cab13cefa352a98fa793b2f2b58da598abea4678384b5a6c8f12670d88a6042df03e793775a30e76d8a14c153e5dab2f9a84c21ab33b72b375b76bf05a29e91feca0a2fa7cd208e8205a581145492c7fdcb128f79edb4585d01602f940218acfb63681919a1bcf4c7bf917c80ce723d19b18a66ad042cd11c6db559c850fdfac7b71db9f47643031d7d23980c1881ea4e260b84c2b80ba0367565e377de4c2f9c2c10255f8be1fa72819f55443ac77ff1066ad0b6d3014ee2a5afa36393a2c9dc86e70d061767e454b05c8d5145d9cb0206ccb155b37e2d34471113602cba2aa073daad6ad6139f5490ea6908b585f5e2c898006a9fa8f8ff4645e7907a1d18d4d12d80652d926f5ef4b592efb2454c795c7f577a4c7c33693c8d1ac7a15ef1f8baa2aa3b0b8246502eafc742fafd37cbb6ea23fe2bb15983efe850f8950d8eb80f5b99cfed027ba4dc3daebf107ccdbaee84c9aa10209ec25bb3713c53605da4a7ea704e22bd4fc94656d18f3965d371b81c922bbfbe2a1e40819275a41d063d09310414a04477a287ddadad4c38f90f5372989b8ee52ce64c6c111215c7c8e33e064f420f245d2191cfe287539e4cb42d43968ad703ac5c980eb412f66cce0eced84edbe20de10e2975a112c3fa80ebecf6adccceffc4330bc8acf9a58e250da2413d21b2795a9d8d6a5fc52ca44a41878931b53ac32ecd6d55fe6e3b50493f8584633cb88d61ef2be59dc555aba45279f94652b281569ab44ce9b272d2ed92c6937f7fe96111d4cd062edcc3947fbee4aa0f60b0cab0264429bc27b1ec36599264df7c4408a40a76294374c2e2c2f6af202fda9db222852949333273ec1f9868aae2be056823a3c1c8069c445e6745b6aea4ee0bea523e1587a4869be5d49e2927e7b7a73a8b02f2d0e4eaedf38352a14a3e6dd822aaf10c64eeb93ca77e4b6e29596a90b5d3a96c19dab5f65b365e84fe70c0b2f1d53b7f80875e507f34351dd9a323a34053caf8d1be2b21d120a62337a4b4d1b019e0a853e3f3d22a97a10d794e4e3e9814dac874541e2d3dfe9b9c6ec62a5ebce782fe6a69793bc6ddfe9195a082d17080d8842aec95f0969a870380a1278b8f4b340de30a56a2a594fce59e5c8a097ce71433a8bda89f0f8bee1431e8489a5ce016a0da78c82680a45238cc17754aa2a13d6ecb58616b45a5323c020f3caadbe710931d17a48664d6dda35a254810c0557999f47fbaec34990a84a3ce1919ca26aa825f6f9ba3bed912e6de27a2e4557327e25a5a3201b15e37fa9a07bb879ea6c9a60977260e359bf3c4672aa215c9cab124dc4715a8aee7458a46fa8cd514b711929fe95c3d2bbedd9ed4c5fae5b8ca927a8640873e5c63addb17e7464a832738cad9f88ba6450958938e37c14027b4b0480ad7a92000abaa6e820ace299d8a226b9dacd366dba9b117f511e3d4c6432ad963313af25737402a04df29af183f92eaff7cbbe4d93548df47ebdeb6fbe300bb86b7443ac98a8063c3bd9d042e8cd5cb0a5d39d068b8e700a6ab46822778fcc2914fbfa8681f45eae3aa35184ec4b8915895447709fbe6c184ae766ceaea9d90a9688ef9629d0ec49ef6bb5b286e166f92904d2d94b47d508871ec5e8977560f1f5bfbac04c49ed3ed2648c12101de136a8ee1bdbc6bb6a6aeb040ae2a1e728e5f2e408ba1bc4ffb61c47b65cfcafdca7e8740bae094714ebf196f793a5a6b1e6d03be87492863642b751bd41bc6e444107afc1aadc08098967f1ff66ff78b0887ac768b51829c6a148a480f1c17cdb688e26d4cc59a90a5803da3c0afe7f2e20b6b8c5fc95ddb869436da02b7e91e683dd7b3242291d3c588e1971916abd19daa44c42e76eec43cfd0434a81a6093e89024f039af88b146807d24a6a15f5b2fe0204ca4861ccb9b113e3f4ffbd6da765cdb19d1e3aef5a8c3c685d917634016b0a9b27ad0d6f44e0e1dcb70c9ca2796bb50c6adc99b2a892e0ef7f355e10f56740c276590638b3d65e2a63b55016aac2d0e045706ff940a923c642084fc8642b547c558eaab85703f2f013ec86f8a6447b8a11fe4a65aaf8274e0c56744cacf10972bd52461398a95275c67eb21e51cc0ff5b40f2d07434ef513bcfc7dd3e3f5a39586ba5bb9103a79f28b89549ac619dc6d4c326812ae6f5b5bb265b455b1aa4e3d86238012e8fb5631850b26e3e2a40b12af7015bb5ac2accb5078c360385be42d85f0eb616ab0ed29a2b0e62bf35c799ffa4d3a8b0781187a8d4d06196feab4e4f54dd3e24f885647430be6a8078ca9b77ca90aa4f87357033fc5a08fc362ef144820418444f8794968346d615c8b3155b07bf7010f718ca050ecf3cb5154142836f69b081d8b2ea3f2b4f48b9822126efe0a24a8a2d5c16b6fae0b6b902991b101d455c69d16babf43b3160e640af25103542ab11c11dba87548d4da4817eb28830a8a2fcdd88c21c8665d334869f98e7465e865832ee718665354f6fd8874d08f88f6aafff15da03b95d33fc585d296de7b37957a4600d36fb6a3cf34454b06bc11b9fd952b7e5606a7d008d28c6ca1eecca68d90dac526d7a59988117c140957ea76d60162b6ef00551324b52e7b06216edfa920b30b32ef2a67bc5a0e19ae1929ef8b1613db73e545623053cba721722c4eae1e39c5285bd729e5e7b9a95ccc051d37ec5b24563d69e46e036f5e255b0528eea08447fa6a1b240fc6ffb7ca936b888c7cfb1afa64c9cf5d994fb4ef113b13c8b421d3037318c687515d00b4a6a7fd06807224a9eb6235cb720a1a019ffe9f1c143c401d3803d9ca15ac1c953e07fa77802d04c01853a7da2f2e796ea04338766d90b335c2d1e6208c9939b0ea4400be823570fe9d3c86dc94e4e595e5d2e5394d6ff54cc27f31c7cfeddfc0fb301ccfcf9943c5a836349680c05b7ec531e54074ee30251cb1a2883860ad8d9c4106cd8cc84d1b874033a3fa1ca7ae02b8d46e0c4503738c0747a076ac351a6e0440be565732587eb552fed23248e33c72163e575a46bdacec8a55d27f6bf107536d63edbd7e939511e0f17a27ff037e00cd14aa48093658a337bc080e227c8858d7673e8037c1536ae0db14bb7378d0c9ba40b5b182270ac3014094308eeda526c6a187aea8ff5284e94814071ba13486d6ef02d2dc9514d22d4875ea4394a8daaa0ab37c2046ff378a844349ba619fa67cadd8868a0921602564031d09bb5ebea8cdfd03ddcf5275378f6a5794635853ac7d7569cadc7d4c7f301240bc365ca40f3fc5a8b9e9392fce9ec10e1dd8284bea9355083fbbb873db1171ed59a3a52ef69c240ffc950bba918a4a6710801c0592df1c8fac3fad66e6b76a91a25199e142e0cf0592c3289cf07637b63879f7eae6d44a67b2144632c0c4d2b2208c14da16bb7a67f68671eb47041e672934b4ac8db367f5b08e35526d49eeed700ed516b9c1192cb191dc784150b528f05c0dc3db6ee010f2bfb1c45e12a6179ac1ba5745c740e19879090d95f02f51ba9a9429e84fe54a6f077bd24c8ad896967e43c632b21029a8f7c4ed0a5cb67856a0cbe37d6989a5a594c8a4121256460d0b20d4711bd3f88493de605172612cc362e0909ee206fb49443e21cee37fac62060ad65ca5850499731eb5f63ff55ff65cd2166149ba536357250373ea52e5d5495dc837114495dcfaed54094ebe25653359e02bcd64d56a52655e17e40b1c3325b4d7c080ab26cf19d6b69ad8e2aa14172f49a0a95c08cab47d9b98519dd274fc1350aa56f56e2208b1806cd253a14d89dd2de01ea9e43e8ceb8ba08490371af6491847bac5e1da763dac6fecd97d196f57090aca8ca112650ecba0b54f39815d1f925dec5d28c6d505f5589f08e4a2298dcec335b8ca179fcecfd5cf0a60d985d2f5e1f143bf03f074b08eb6f83e22ae879f2a26b95e322893c9726ed3fcd23a232efa9e9981ceabdce59fc193c8d36d5f33c252413a62dab828e3e81127f19ea5752e242b51ce04a8f09310045a61f1e8fb145c8df154fc04d4e986b9b70514ce8931ee5d86e23dc90b4358ed27bc553656b641a1a1f2241aaec1e4ba8595c727ed71d80192bf8f5392be68cdcb126969dac9c726b7a5d32b844faded3eafb24ba1390c535c76beb094eadfd3eb7c0336087011c13eb4f084bda017c8a9b2521d884e5a229a4885843a690debb8e46f41355462e864663fab89f5051c9f557e7472fdce2a3dfdfb962250844b4e51d7eb870e9d95a23a4a73b03b05314e4615bc0b8d17f44874d9ab156f8725b73336a387ef4e5b1f34c95fe0158e4c01e099e1d4a4bf0785e4fcb40157ac40d35b075cfcf3bfe79423b78f2b192d9229b045e122ed40ee1d5652897f75c1636dfc5a3d7cfdcdeec5f6edd4bfa6edf30fdc2fc8714fa2138a774a3f822b32b950297c02ad87917c283c115d258093cc79bc172cdb0f3602f65b52aa317f8b102af09aca2d9445a83083785acb460743ecc467a01c911086f70bdfcd139102fa2e46ac8d4f9dea40cd1cf050bac96dd9f05db7811c935810af9ccefdda08edbd0505b7102c2717daa89b21514e9a1674634375ad1e756c0e23427154e81ff2ecc5637bbe436f2a27fe80a68654eaf925625bbd59ea555a2d2709289de1998fbb6c21029f0d8caf8e9c663665cf37917e7ffe5296a139a4d412d96880f81b784225d754cfea44e29bf9af69c4b4fec927c0b62925db3c4d00ef63cfda3cc535987caed4d366726f2485058e6f69bc7aaaab9143fcfc33013fad601cab8d5cc09123a10a7072213119790e7311ab480f486ff9be7e9cbc51714c7befd8d2c05a5a50cab2a8bf9c39f5e50d903940c64f575ffaea780aea81801af5a71403ade992e37facd503e84de819c3576e2b0974b25f886fba1ac291114ab6ce9911dfc5f12b805a3769670ba157e35605ecb6a46a82bffed7afad5ffcde57dc23191b34afec357e7aba5ace8fa8673d4ab9ba60214d3e965b66addd6158a2323de11bed2f02f04fe481e16bdf50f9186e0344fc510b4e3b075c8a205a722292a1a798b4cd432202c1cd9d23f78a519e95357476cb7ab4cc0a8818d25ba1a58ef1c01882c5cafcaee589aa890ede905360a13704827d86d028773d228b14cb5def068cd2bf66a71fb2e3c892b486c21eb0eab6e6a763d4a620771d99b41553b33b209369b01e3d76a6f558a5c35d334927a3a94904489757cd7dd4b1b0b1556e6e47778b5b41e6ad382b0fc6c3d5d0ac6a4a294ab4b5975d30f92f8bc2b86893b57da5d0c3d42d266a875264804707e2a5c5ce27eeea7c97b68d15510fcb99294d48eeb1132b8e06ed1bcff987ae5d5ae0658520f83cfcc91a039199ad48a7ae4af9f3fe11ddbd8c078ce5b4b23f07af35ef47297be3678b51439d0f4cb50c3f18b977cb98f04b7453c60cb2f7c488cf813a3d7e08644f0db8adfd3e87a160edd62ce5198565d38cbf1de656119a69b485fbd413bfe453e5dbcc08e4c889d3a9464a5460323fe25b2f550d2badc0f0982c6d85973aa11fd6ec44ca65a7f608dd61de09f2dc5bcb92c65b6bab149b476da38b438c5ede4b73deaec433a15de2c54fc421114216448d8f00170ad1fe365c56f57198abca44d184d4022c227d1ecae7172f984e8cf1017dfd4e4c27be1ce96c0d12b23bf1c8cab6f87d4cff74965b451162a9a2751e3662b962529fbe8e91209c1bb31a5f42dd429f70e31f5f5c77be6d2163d05a8d7b7cfb57d94daa47312de81ca1dc5fd1f7cbbb1e84899e735c524d9add72e5b4e8c451c94a67bed192ac731fd9fdfea458a7e30884e26914f96682b01735b113747d2c6b17480374f7dfaeb700804a0e8f176140ef6c20a1cc3abf74f0e41f9513c37d3c9c89541fdc022bab45537708184deee6950afd9a0bcb90b92eb387fa4850df1c9c87bac92a708936386aeedf436e5d7552404d3a48602d50cd0d1d303b845cfde3175cd85f4090871e4ab4b8ecbfb557455221ea5dc1c7cbd3c1a94026b9945137a15a7569f3b3d0c0d01cf507c3f5e769506b9453a12ed2f4fb64d572dcddc8d10f6966dae136b5e1600d2ca0071e031ea7f69c1d021d404ab08829485c834b97443ec4925a9ce69a38c24912058dd6799b82f8fe1cc28ca816c937fe93846ebaaf693da10b9d89ab520b5c1892898b65821fe2d3e44618c83f8cdd55a4711fe7eafc1307ff6b01845160c120d2574185c232fc1c1c3c0ec228aabab7417a561b2646fb29dd506f83d6e349f169cbf13f6f7e05d805ac6688b35454ac0ac0fee7b5fc9bf40499f51bcde1b8e6786fdd80fc6c4008c87152bf76486e2f3a2b2314b22fde68ddb7cb9994b72985042225811a1f48b1192f21852b7176c8cd6f7d57e70a4ad7215632df1f8e0dd9af0f915f5c7fa3e428d5745e1292d31969203703379989233e8b44f542c73ffc18ba9343ef0cd6283e6aeadb215d20688e77aa01c3f4024482214c24a81ecb503cc57aa10e94851cb5f605be62cf8c510f170e81a88a40264c9d283f528566b264a655196122de27f8b80ceb8f1c399b09143674bc21a047c83666ef475dbb99e7af24bf1cc3f38e242b00b6622c81e2a368cd35ecc14d81937464ad6804750e30fbdddc107488f63fa53802bde833b140ae65d7775dfd7ff9d13e88c0c67b5695cd0c89820eeb94ca69f825c41761394017ae7b37203250cea8892afe953904908c841b84fb8146ded12692ea7de1f7b482a6c2cadc0ee1fe9693d1cdd011471d64fa3a01dec690498bb1d6b882b5e5a5ef6dc089bd504dc3e2474ce9c43bb8bb4a8b8cfe1b1707162469d2657d388f33100e63fbc27772a38acdbbba23a79a5bd51d779e890164e8f42456b2759f80a2ce8963feae6e25082ac11b9a70ca384c20132475ad01139460cb482b0dcf00ecdb6dc38387feccc7179fb48984d1681df67a05a2b3214148a811f31841f3be6b1e03a2ce232df3f0fe402858514663f0b08980f6c80d278ab9253099c6f60dd585b7c01d73a718b6e06bdb2cf83b18e7312d0fe1ad84094042f807e354dbab388ee5bb603700ff196e45bc3d72333574df5b1688292e2162b625ebe6204c53cdbf593a304883ca2c1b911a08ceb3da638ec42c54555811b4b515609ba5a5fdf11c5a55f511606da9f1b4d58cc2df1b0c246844478da87dab912953840ab435b2b89cc8924d2252762a083b16548269e2b45dfb959a2ddce16d3fe02d2dcc61d8ba328c466bc06473ea631fbeca4e64fbccee17ac3c80422b5a7a93f829b2caf1b50315b5aca495a4f059ba2e7185682275bb1b35f7e0884236a4d8e7aa94747fa542dc7323550ee74bbc7cde425c218cb37d20335014820823909f476295e41f30b781fc7cf29d597a6d4ef53efcc8ebe2e7ec4c5363a1a1b9d330705bc0ade260a47ac6d2b2fbb9ad83cff922681b3b2edfa5b3601142c4b35536fe33367161a1e318e86f9fc51236e575ed718addb05695f9cf1b00d1c078a38155225673b0d7aab689b3c46d3ccdadfa91ec2b1af3344e43b3817f392c3411319e6c660ffd65899d6d615f863ccd697452a5be32a47821230dca706aecb6cd859a068e22ae2f83fb957275997aec63ee0d1f12a7d0bbc41057854b8689fc85efd24a91e32fcf8a0ee5fb20df3be03ba9f9ec1fe24047d02caceef800c008e65a46d35a91712b7f4c25734f11a56dd632f90cd2c7b6ebd74c4ae0473a8841520dcc88c27f6c2324808257a76ad04bc79255e7ec770d122786598f8f2da41f68a9c86daa85b033bdf306fff426e287b55fad3b0310f96fb864994918bc900c6c4e5699146fb8a0484694085c0259fbab23f191a60e9a45101558970fa41c3669f12ad1ea8e8eb1cf50391862029dc39eab31d9e8cc035f4fed0fd1d88b9f32ecf8b4ec4618930d8f3242b2d87c6d458dc51ac8f321a006935f3dfe901194f8ad4b0a81c05e66a92aa4ad7cb76cea36e5a4857221fb9c157045cb872355963edc4888396fe718dba0ac1226bccf59bb1c9f7e6fe2c68b7bcfb27970cd1749238448e421c97e9e27ba2161fabb2042ad491f9a735df909d815858cb50ff9efd433fbf6019e9488b7730162a7365dbddd6c6d20b1785a192caee2e658974f8ec1e1d9ba7cd056f6a29c06aef24f7e913158394082858eddd6515602764af8feab436bd8a8341d6018f71d6d30802047323c3a551b079c129dfae36a3e54c736565e9045e95ee34e33aa7119acabfb902cce0f1f3f1314e39c2a74543f83f66750ecfae7087a6f196247181e3d41d06ac1763c0b8511a4f6933fa1a75c96bda06607b4a59c65ad4440b902fe4828b69513398efcdaccd6f8c7d26f78fa4c7c62772b629d7fe067e1a33b9cc2fa50eb4bebb1a0ee3d977e861ce8159b12681be23e6c9b3a58dd57b76fe3127ef7fb56b7779f0ec9c766833917c35872d2c581a38b3291237e0f00d75c5ce012f64392dc8d784eb12200f602ed5b0e54a1be8b5a0da958453a467fcdde958e3a2519bc19895b0e46eb58f4f55698bee96e243ff6268a453615138b1c2b2a5ed7f603a64f6e3da0263e5547c4749c2ca7b63d37c7717542c1c9339956f7ae81f24a94483c8dbb3158ce86c2b9b400934941d3073bc3a843a1f09cd85d57d7146694d4fb87781bd72d6523b7015fbcc52038e0d902e9192d77dbe4600510a01baea65631614d905b56aa3cc609228bfe20d8a2ee3d60db276cbf838a7db9d28da9eb939dfbff79f98b46818f379480d2427d74eed7bd78c3ae89e4e9d083fcaaa3596d69870407215d0c6ce15cdc265e07b6190a35410de11129815004454a9e89fc645e07876911e7d8b24a9ab8ce55daa4bbf4f0b08c1821d4862ad7205e7a5a6f569b410bc8c4b965b85a8ec5d0ab53a98456e240f9238e898981023e039cf73820d53db879ef7beec1e7c7dd23bb30ca1eacff1582219f951a89f46ba8df472a1d02b3ff5fa33c9c348ff1cfc3e258305c99dcba96ad28623f860535c53ef77328d8903c7a0eb41429d2148d0a295d8a521ea408c65b1898b69e947f69c87f14131a2d6541434b14e46fabe8683d054a33108f8d4fc828e53851da66679990013fd7541470e4bb5269a6dcfc26afa8f479cdade8893638f22a938749e0c73ddd9c1518d5d5c14ab105550f9ccee0f6821c5cda278f21a222c33a2a972b24bc7434cc143e97ca7d0edb430a2151cf27650e70e988263c945cfc7d13c0d33bb90b5e7f1c4e90d26a5efc184448f65cdbbb248063eaeec994a1ed588ae9c8ea580cd22791787aaf937ae3c6d49d2aeff406c3a6a77ae1e6fe26d961fbec863c50f53a839ae23948a214890800c93ffe2a1b05bd7b0c0e2e7b18b535315ed0c34f83d05231f3c126748c9634acafa66518e6a780c369ed4da38eedb2b33b8ffaba619e76230ed29e62c6d21e9c2f736a9f0ccb70215ca90e14bbfb1dde2f7518c6f1825039a0daeac027442790f8fb702722493ecf66f146634ef9d9e50ff1bc8b45eef50c7ba6661f0a9cfe039f25ce1705546a9817e331073b4bd1d8d7a83c49e25390205a647e2a01ec9bb747d597ce3990ae3de38c44191347f4a31aa96f32bca739a4760d6d86cf4b7b39fa6a2c90a4779f2aa3160bfc445d2ee8724c1d1201a42728f8bdf1621de056b86e9161cc6913a7cda61c8769ca34f131e51f280d68c9f5e323cfd926d11ea52b244fb5b1379ef7404e2846d3426558dd364f4b638d18864b6cfc13b3321a6a2c5353a0cdd9da2da255c16c537c9c29200a5d1e30388138297412dc1202bda4210a4f0cb15df2407fbacbd1253792da7e4ac4f7a6dd8bded502cdb1098a5f3d0250066da49d6b4f42fc469728c94e089ccd9858d3355cbb0640c0102825db9f98bd8a47500dbd506e45dc8c06a4c2b766965f1c22f8876fd8da38056d703534446bc0c68ac7539f8c0b5392eb3f460d4a6eaeb275352bc5a25acd3b6900df52b8a4544d3c5fa8c236f6626aea81db89d8a9365fecb39e1afea8ae4d29a9d866433c64776da764f66f3e92b8c2019330ffa83f44977625aa704ae2a92752becb498af1d6df625ee6108873b20fa378db86674e1ad170fe68841a66350d45d30cc54a56267d322c15cf302da8124424c2ab9566916ed1cd1cda043606df1f7b961250a46f944b163ca43d076595f022ad2dd0b4b7be7bb192a031337157a7ab295c15591714035798ba27601009e49c290bc2939e5370078eaff1438242bcc85212ac4a77e700f6710993fa5b65d7c02c1eb586aab3962f783a0720fb3a74c46f317c81581646d5a577cda21f758c988e742c1c4eb31858f8714c1831e4b72a38abda0311c8a74e4a7fc9722bb1825b3d42b31c1b29cd310326c7551f000c86ce7ba6db4f8a4a254d5913a7a3cee7ab115095fe86572fc8c779c7645c14565306ff36e650976a24a0dc28b7ecacaaacfd23dab25049cb1f08c715b81b80e6bb2c5060272842924208a13cf2ab3d61519da2901cda0b8ffdc0b8d42d2c707f14d9fb3a5dd79583a80693589bd8ac2cfbedcc291e174802020986c6400612155982c1285a2db95d39f5529c9ebe41eac4fc2afde7d430f9c27301e9f30d5c80975a7ab55cb5dd26e66b32056fedaf18e6ed7bcabcb4acdaa753413b18a50d7ada08ab84dd57cd24cb0e4252ad35107c94591820e6501c49db23ff3057ec424db07f1f5728bb5ea5f03a1c9e71737147db91342db1bd7ff337be6eb942b6f26456ae1f56a1c7b6d47272c55326fa4cde1a8107d564f9a0b3b6bd572798ddd84aa2f748c93d701e4359cb03a887934899e3eda44e4744722ca4da978414e478fa27489d5cf1a4ec50793992e75c0ad2b8ca0a3c88a52417dee44588d8634ad37060533a3005cf1db2752ef697dc76d81a953baf1cd3d7056abafb2a84879764fa559fd3e52407dc907759ff6255cf4ba3ea71d2c437f325277df70faef756ac9cb86b03af39ea96834f113114b568ad840982faf4761f7d1a5e402eee33010157460e71dbdebfdfd8825614d35e7862e8004709e7a85d65f25f9ad5e34a8472727c2fc1e095a13c2e9a7cd6650b81ccfb8516a840f508b43b3936e1656efd343298061329c2622e56917deeb030d114013a74f85678bfb9f520a3438597610eec49dc7ab04fbacd0ba33495b6c8529bb2f0e25fb5fb79cb1c8cec218aa9c52a59a1f056fed507a8b33fd257d10a0cde36ef9487f59a5d8051bca1e8ca63a47ac7a4710c140cbd5703eba6c216ed8f93a8dd16a4aa6fe0575dca106dd69b79663ed01c7d78dbdd71df06a5e0bee8613eb2568e46563995e6392842a84dd06324596e80b6d8acab744c5861eea5463d6c62d05da24ff07124e6454cd8b67c9a89f944bba7b1d1ffb2972c473b25180048240f7707ba0b97dd70f9f31d84645628f0ae813fc77bd330c5fa452212b2b774a333e812f4b18fe6801f7151c786a6975ebf5974bc745902531d07641e8128e3f23bf91d073bbed6c2ffbb45afe022ae55583936738294865da0bddaada6494a1fee7266755421afa9f27518a27066232e7e50a8ff057dceb212ae12b2077b6e554cc22502445f7b99aee2302a0e3924f643958f78e3fd8f47d6bffaea7fc40e61c3e0bdacde1fcb744aa1e1c38b2f6ff200c9d4872400f6356132038219ed4734c958aec4e0d80c0c1fc05676ee305de7941de5d0756fa2ec188260af293366d19cdaef3752a258e39641224e68f600b16c2b4f09063becfba79e1b78a6ea229336deefd10c7896ae4657db6c409b4b914aded0e3d30b9ad0bc72b47512275799bab0e1247ea6aa3a0b7b321ed1ec169740585768c88dc5d62e344b05eb57b0b8f5ccaa157f8dd3a0bef30204a4ea7d22f38044a48e546be37ef888ec56e48ee856eee845e66e46a1f51975eef171b88327ce13314146d020d9ccb9e7a2a64886d772300a265d3909de34c6fb93654379cb951a44428cad208d351977927b90fbf0199ce16fb4ba4080e01ab194fe04526e6ae8f47adfa7f4be6a79eba7a8d5995fd16783f2d5f468190036e0ca12cd4767cb8daa0711928bc3a8854d21a9e988d993670754512eaf4a634222dc8e2115798a12d50792b8d7943ea9becae311fd85a8b391e454251968090f0726e871344ee682749a2011f119434145e1261e0dc2aa53556e6c892218c6c0b74a679aa19b879b149d56b3babc34c97ef53786152ebfd007878accee37e9bc06f43dfede84e37d8089329eb74e2176feadb7018b495baab94e3501401b983029599293c25974c5be4a528e584e010929818230b42fc4cd309aa4a16f6d23a0c20fea0494f144e133d4f3cba6cb127c8388f26b413a211cc3979e239d870ec84e3dc0ffe3e015844c84e9f77d726c7b13354afeeccccb33734e1f5c38f891537b7a8318887781f773de5ddf59b25b0d501ff186d7fbbedd87075424313a32252c3a1a2d51657638306a9a38741d46a0f407b789406efb8b67aca7a93011ca05d2084260c0878023c190036d4870af90b166ff4196a00556021ff86618d640ba5a72c3ac9520d4ad67f053ccd876890b01e2b58a9108e06057297067e768c36b0b21864b3f5531a98fefeff6f50db1ffe01dfd860db9e946e6276ee422373375052fe058f03cd7a9f87bd0bfec6bd8c4accdcad71e89857282f42c3567166aceae70215d59835c5bfbef7f93509f4ea2ab1160232e8d1c4308059366c34b53c72e2ee5b5b49f3f50dfbe28284efd1ab213587a63a7272c4113b379cf729a6a65b67638debc8ea1bbe0925711afbeec32a3b999513960151084c6040ae2329ec31a7545363742e955cb21580f5fcc2b49e416d587ec9b6cbc486e484155328eed39ce1790ebe7b032882b3f04e308be7c3b5a34a3be501ead7ea3c066c3cc0817873d9a7a5d862250d185671ea03e68be7109c965f4e5d82d31f75a0a961e18675a3b39130e7f12178b28b54a0cdb6a8960577e101d8a3e433fcbe5db1c39cfaafe23f419ab049829250a7b002579e3d3642442052bf2b6357e8c099da60a476a4c43b5d65849d980c895320dc871bc32a5a8a5fe4e254b85984803cbfcc8ecb7a100c4125223aa713e654b80a756abc29041675356c0cc0c70f6dcba11381aefa2c71a9a6652e120ae9432b89af335c7f82463f5d5781844478f05f47e279e446cf985b93672881c99bab05811cde4d0c7d52d0d6242633cdb32927564e2b5af5a5a7874518a275b12d34704c9f0107397bede671e944ef7bb5aedf232868b34161daae22ed05fe566808324940e93d10faf2e033ae0bb83aa0aae7099cdb0fbb40bbec2ac6c86292a2a135a969aa0177d7e719d7e866b0178ef2b5d9a8537f3bd9dab1660b7a1929044538c88bf6418464beb553e2e04bc937e859dffbc6af6d8d25ac50cd2dd76695077f3b15bd02cc1988b420b4c58a9545c6454749141e88819f40644c3aab54bb988c6b5909dec3068dc9e2610c66d144a43cbc61d5ea8d1036c0ec0dbaca117eac6f22f05118c389e6bdc562b36b62e3501e2b5dd1c88b0d993c6eebe23ce983dd4abf47f25e1dcf0d950ce19df0b85cadda41c427208045d2bff1a9ba06f4a607d822d582fa6bba1d549bdfdb5da0dfcf653a1c5a09cc930146b57bf01ba0d1c637c5e2eb92734b030de00c1803cc1deaf6c4822966207d0d1190bf07ea8d48508e23f34ce8d431a9d65346416289ce62c5690e8fd7183120289a821e61e4c563ffbd54434e2f54079f19447c3c1be943ffa00a4441a6e088a966b03f89c597d1b0f119d23c402583e0a0fc6cfcf983418e9b8e5a3e8cc5d8b7d556bf2cf9aad95caae5ba81ca308c10da7ee2d739093050ae4706061ff495297c01f13295f51fa70e1e3dc912a8f40e15060b87b9875de3d813831330c4a84278bbfe2ddbd985ef5586c59fb4aac171dddfd90f929ec73c86e000cb4c85dc99651dc23700ecdcaf4617938c8881b9f5ad6d17678b42e5367a9d95eb76907632598b36a23443052cde884f1e7a4d528c211e0870ad1f72bbc4ad72faafc8d102842f340402ef946f4ce172c34c986a9b6dc908858086ab5862046d27f526d0acaf184520173bb016c9c0235d524e4a5613bc3d922258571a2c3b6896b89e2439c3d46f78715ccdea040ada11ece0af0af4a4e4a90cdb33ed3ebbb43bfa2f1b9d9a44078804637633ee68c74e62b2e658c1a3f53e766ce56eda15cd9207a050584457cf4b47624fd0ff80bc50caf3102bd00023bc1179f8b0b34bf186e4e572afd42f810f6722b1223f87be7d030ce405302d5c6d605442b45397c07667128723198e5378f04c93051de504e871122a6e320b26a1c7226251f19185176b1de604a1a63d0d72eb5323f3d4efdf7275ee588fe1d086cffc837cae5e94d59d18c54c8bd11694147d2a7d99d83cf0ce96aff79edcb853ed8f50b665a4a28ab1912a60459408da0bba0f0ded5fbe14159fd88209894806bbc3509a808b2d6bdc08b58a4cfef490d0dba1aec4e663be0e29c621b0c0f57985a43f8738626e426ee8f144bfd47b7583b7583e0f659f1aef4ae3a19f2bea1ff2c7accb72d5a9718cce4ffc924b659ad3c663a8aedfbf98445b17cf2e422f426972ae5640a018d54c8b4e73c49b8a91174790b2c5caa6e920675c91381720f72caafcd72951aadac213defaaf6cb9990527eb61614b8fff4cb978571a89c7acb78c5839e14cc03612c5857b89071a29e660f9ad27e498e64a6647c7b90dfec059c6b9c0125c5634ddafac6683f061a8a2bed29a428762f39d32a3d291eba2a33031cc82673fb2d4024606a6314e9541eaa4542ed5fc1056d3c862fcab7ef6d0882e0898602050b73c4e0d800d70c3fad771c2b6a408fdf2585a0d13c36de25327d98d38238c21e33ab71e5fef9c4d71107864631484a89094f20f5864e9b12e2d03e0431fa553d5f9197ad70549f8201c484684949dba7907cb9ab48fad87a1131d446928f2dfa1e17cb30e0e9772b9c3dcec1cc6de9ea63dcd8f9c1f4de5fa95bbe5a9802adf26c245ad44747ac9f2df322498078c80e670770cb68b796a85f0fbdf6290b0a85147ebb703b83a192328a65b30d7bfc4ee8ba4f1a13746ba40ada1591430696783d9d9d060b0d3bc7e54249228910692011647848333b86dcbac755e3ecc5fd026cfdd5e5b9eef66dce99096cf7879d064f446cbb87e17c84bea6863abe02b774a8430d81980898899507b1931f4773b537b128d8dca91dfbda1ab7616382e7079f7fb9e50cb2f38f548fe2d5cdc42b64b75917d0242641ea829741b22b6891cc6e56d0de01914951ec1612757d2c4178ff5f321efa983deda6fb851080d12128fa53d54aec1abfca2168106d17e38ca1d5508b48f4eebd6b00ca897af36b0be102871659d1c803579cb3d5222e635eeb3ecbd2c1068c934650830574c6f8c3900671a6344d4d605da8e9f693ee85f86ab3a9c257088942dd4fe504b8179ce741ab928770eb327f3c2d29af6c1a189bb41d74409475650c7975047441777e0d96868458ebc92ac65a9a884668f8de038ab4e34a473fe30a68e10de68512e4cda90f4fb86c83a5ee2a61bb1b4c0852d91fdb62ead420162a916259a34bae2cf28796d5ab2dd3208b2329aaaf68191258789d93f12797510a279aa1d7a33b0c9cdd40445583ba3aebfbc70e53568e90742d621154440d4a8154637237204a900a1dd03a56fae0c9886d6f15c96dcc0a4328d4ddfffdc9607aa551c8956d2dbe89e62d98516a46781740a95b9c724d93a2a7b42bb9133fe832c9a0c3b56f0c5fe0f091f694dcb20f8392ac12bb1c38d04fb7960aaf998c6104d1a518e3153f84c3dd4796d02b7df9787f7f5492e4e9da688d8000241b054fadafa7e522ccb30a627dc49318346fbf8827f7b6d41fbc3d3b1d1310603d7e1a381d667f2340018c0dfb53f49f037dc3a341c73af8720d9c1832d64c34bea85504e07fa04cd12862288126eab349f029cad1a54645f1eb6f8348dbd7fc919d7259203c2010cd799634a96fe6d94ebca5e3d1a1f2abeb2c1ec371cfe9168f12370a4750c46e2395b33d8c4701e050027101dabe768bab8c0029aba52f0f66d013e76e234ac047687640444272afa0415407d19382d4ce83ca4e149aa340363168cba0263494024a02466d5f4092595507559bcffa3810bf85f6c18383ba8aaf50b72bc890878178960dea2ac8fcb014c05a89a7f86c794bb61a3cc0c15d54067786b027cea8e5eed5fee6be27586dafb3bb4f466f030547ed8f2d25b663169d5967fe76fbdfff524a2953f6060707af062bd6b6c1020b20205b3608ccceb23d809758a2b30082b0871bb6e44d984478132a4378132bb0246f82b4ef8609e14d94fb6ed8116f2205f82630923761c49b5015f126566f8275e44d04c1c89b08df84b887fa8dd934d8110a10b3ef86e136ec16ad0cc76531642f90d120ab410603590c4c2eb3e1c2789abb575cd602990b64333ccd95dd9e46062e630197bd781a93cb6ce0395b2cc7b55578ce96428e6b9748ae8df34ee1250f81d9f399739db219321cfaddb29e6b73b7a8ed3e7165b86c867fc96cf8cf81e1a21c1b031a7110901d48bf7bc50e1fbc3b8bf578f4ee588c500649c3bcc1a37d909d8de4356bcd0a1f5cac48ef2cf6c2a32a6888f1a8c0dd59ac8547797c4db05f7c5383c44e12354a76d63bab39e2d1bb6d1d0464176d9ad82d06648a1fbe892d913790939fc035b4986e377fe1ff321a3af25810bf6c781327bbe19b82cc0559b0df2d4660c49c24019e27308a28ead222ad572d3d12b95c3226143b3c1f2febbcc353b1c3376416e9989856ab05c3c7153d138a154150a8d8dd481a4c0deaf5bb614176fdaa4230e09d29b9331a075250b1db3b3a2b2854106083420000208bd59c60819148c364fc72f920d4d5b5aa8f1417d8920846b991e202f296f7de4b7e49846c0159a67425914d963e7e9718731442e43d5b6e99089be5627a2155a832d5133ca19cecd7bdd046698fbbc5ce6921ad542a954af553a95c8654d9260327838b177c403d74dca8a1f0f5696c2e43bf755df7c86d824f337331b3cd6af8d5e4a25709505d718e47f5c2a4df6d8384afb9bb8597f86e11cb69d92f47081949727b9aab83b7bca8ce98ba7361ed7ee30be77969c1ecb387d487b1f772e155bbbdc024aa377ae178ac880f00929719799b9733a6de7a407d40d3b187d70bf77aa81e8d1eda437b2f1c5fa1f008d3830102e36bc5d0b2b5702d1acc15951492d8ea810102d383f1b56268d95a34b470268c6f05c6a702e34b81f191607c228cafd5531a55b504a2c6b1b45bb84a5085c94a6d85c70a6fa5b702649be48aef372a5598a804576a2b3c56782b40567a2c2b3573a5b6b2525359a9a5acd4566abe0e2c4bd4f8537fc01f6e051e61ddc633a6aa2570456ae136f216798e35a276a8025449523afd544b5440955525891a749b2eab906a03c42f21f5696c8ca82429a202bc339b2147fadda59edbe9a75a329e7ef8f4339d7ee4e977fa9d7e479ec688d045551555a24a608e6f91e7ce5de23915e7b1518e1bef16be2611e46197faa09242128328bdf5e4a722bfd82f099b2227eb9dd90c39719b1e1b5b10dfd8c8e001b7c3373632f860e322bd339b17822927c11e27c1e08d208d2089d2eb2b94d3e6f6537fc01f4e1f3172b3d5624d8217bd2b29549e317555cf1a7005962a91e7f81e79ee2cb59ceb4ad64e9ee2273fe0af6745e26b70b29bcc26e391bd90ebd3c48031a124568b13337264557f2a10f81b4f9b1f114e562c156b9fb88dee69704fc3a2d11343864761f45f10500f9b0a54f198ba3a396d6e2a8b9563ed12cfa156dcb6735a48422420290989b4967628e94abcd20eb1446e1a594a5bb8cbd07ddd316543559922099192908024a15e3b21ada51d4a3b4a3c91b492564c5a4da4955c512bae949e4e9bdbaaaebfa1f5b7aa5c3a36ad2c1e0b9654bb650554ad56fc05e65c5ba32a6654f1cf59893ca7daa3aa65a53a592e432aa0baae405508a804f853b158ab75fda9424025bf150a97a11ac115c5e9a703921653d7f5a70a0195fc60a36a665489259a51a51af1705cf1cf6919459e5bedd20ad8692e2b12527de9e11b598affc547ae78885feb10505d57a02a045402fcad4340755d81aa105009f0b70efdd475fda9424025bf75e8a7aeeb4f15022af99dac2e43173dfdd475fda9424025bf7024d22afe392d37f34a808794ae9695cc72128fea177e96d78b5e98bd392a3cf76f3d83e764afa8ecdf5a86fed1391409fd91b81d11fc5a1f12f4373978a379f8d7efdce36fb40fb987fe3bebf819b94b8dda20d15c9dc786b85bec1cd6fe1df338a49fe9658f6fedbd39e7dcf5c98742872922151725f765429173d96112362d344142aa0367da6298bd0824a5c88554baa369149231189e83d99adb009fc6a6894dd066b55172d15be4e753718ec765b70873ce24fd6e9b23bee6c298addd2296c3dae710214144fab5a93dcd6db1cedf28c7252609248da68773cc4bfbaed3f4f45b048982d26a9d9ce7dc2dc21c1e9e163bc7b55bc47264760caa03ba437190501eaa04babe6a2f162246b1272f1b4d0d19d87d2e1e0be20370b70c8f09f1b2bb63782ce86dee0e61786cc52f9d739e31dfcbee76019e501d501ce88e84ea80f25025d0f5c5e25513511ecadb28ef04e58d50decb4653433e696ac03a12bf4e221d0a0a46c1a42dc2a4303ec0f45a2eb46aad5beb85aa85c54c31b570a8ae345a81f1012685e9b55c68d5f08d4d0bad174d5ab73b430109698d0ef4c6a661ca206304638a9f0a1854d854bc50715361830a9c0a9d8a1a8fde1d862469f21d1e7ec1f0b39d246fbbb31da08a172a6e36fccd8e12ffc2373b2b7ea9d0a9a8c1337777dd7ba261c0a5eff05c43b66979ecf6343b4976802b56ec2871ba321a03172e56dc5ec692af10698961b9b609b0ede23c2ed4452628c3c45573e170ed5c3b5c3cd70faed4e57301f1e8dd6148922ad72f2623b4a11c0643f2b2bb5d38ccc8dbdc3daae170afac4bb60a65645c3d57cd55d3e172b95c2e97cbe572b95ca31399d60a5d900e0683cdccd0d09cb7f3c5893b819cbd1388d3771271fe4ee09964e55c49bb54ee94f3c5793b71270e9fb895137702397b2710a7ef24e2fc9d494ee049c265ca8c0945cb9e49893a34ab2555122153645a2c1dab066bc73ac252594958409612acd5a5e6c28254b9d8c290244d736f140dfba585b857777ad0fbeef07c3c2db26ab074ac9d8ab5636d9a49aa9cac1deb084b65256101594ab05617162e35171bcc08254b0291c47e415b75d26240e6e9a4c57a904a2db6eecb6ed139da39eb1362f609261572b7fb04176e234ce12cd894d2c55314b92b2d9b25c54f09407e7a74045049294977056df3b6b3c28e3c0d8bd0455b2c4c150269adcc8422d72aa53cf1c43db5b28c995ed307b3673231832c2c586a2c31b0d858702c343c7a771892244bcfee8601c258883b62e46b84801755b2c282176d72d19a6da73e684590fb4abd206930d5aa3b1f84ccf4ce6246cc1ebe89ddf8994b82cc1e90d9eb61f674983d5dcfd3d89898c13b8bd1606131a3d7ee2c26e3d118709dc5c642030b6eb3f4c08c20b30e05958899bbaccd1dbe69914288c9ec149e6be556ca57b2286b250ca5ad9ca1bc953de5cfa37787a1aa2c753ca0917ec370a6a75f0e3b62063773f2191b7ecdf0ccd46041d8106c8529e130e0d324e91766e461422398fa342d90295f79d25c29272d3664c782fa0dcf944f3c53be9245592b61286de50ce5adec297fd05206884aa5a974b753fa2447ccc71c1bf235363391d1bb51430c1832d880c82738987aaec005e1fa155378eed4bf4ed3b533bb68305508393383c258122163b3d9b837692525410a966a9474251ca55d89c7bf4a3c5ceadd1290922faf768ade3636e46b7e4f5344bda81027d27d4fd3434a42485fef2c16f42829886f6232fe15ebb93a8d145405633afadd61500ce262b560a9460dfdce62311ee511e3e19b189026bd06098fde6d83a94860457c8d2a24de8c27dc656f1787c1945c740de2d77579934e4d1f9d30a08b08326f1aac3c82ccfb84a49254abb63869e61b140470636214b928869bf865636208300ec0249e2b7ab231665281d4f5995d3a0b6fed8b5a9be22c5fa3ea76a7002285cf0023e6345d3c31623ed355dc3e8dcb558a98c3ba8ae78c989bf003e8879e143b52f0643f807ee8e977a7e0a5d8a1c3d3d1a163039d1d3a3de8dc4007073a3c7472e8d040a7063a3ce8ec746ee8cc4007870e0f1d1e7ef8f8f1a3dfad93eaf0a0c303003a36e8e07478e8f074763f723f0e3062feea327a04755ee8dc623270c17eb7ce4de745b7f5177a0a3a0cdd4557415f418fa1f37414f416dd855eeb2df41374163d86eec2f780f13d5cd0efeeb7ee428f417662c909608fa1db7aed532552200270901840efc491132ada6930b5d7e0203100f5c4119c1fce109c1fe010c1298203049c217088c0118213048e0f7084c0f1e104e1f40007081c227084c011c231a2df8da3e2088143440c87070e0f87089c1f8e0fe7071c14630acfc9d8ad673d62bca7c1c1f138bb1bd3f1afbb55578f0d890501f5dba3dfbdd3c5703872f4d88d1d3af4188defe9b1181f93e161f4bb676c329ac394f03577b7384cc52b2c086bf23477bff0991a6c897ef709a8a0de8a1ebe49f1c3af4b9bf5c0dde0fadd38bb3bdbe15d1c1c3fdbd9f1688ae762604bfe756148fe75f7ce8e9fc18e3c7a673075651a514e2c96164b8a272994cee24c37a331cbe1696635663acc6cc860b6c39dcd98dd30c361967b9a3bebd17c2663e633fb59f1d90ee3cb36bd6c94d3996be121083b0b0f01d9ed66f19773969be598d5a0df3ddbe562760bcfbdeccc73b6302766b366b97fddcd32dbe15f778b6232f4bb6141980a03c26a421f6f16036fc45d331b96edf07ba6c32cb39e74364317c04c05154c28f4d6615570e9a41012b5599b0653595c7cc5ffa227fbe48f5fa9adf96f2e8eb88a1d8e9ffd5ff269ec09db1ac5521cc5b90a620246cccfbec288f9ee6289e762ce14510123e62f2e978ef91a1477e9342615483dbb7491866666660666b3d579012303c673a7f09c6b93ce18fed875923b6778c2093422dd80f18413685604e1adf0ade8cda0f41e008111f3544fe1399916614e6bb7b60bb7ed1c9915bc8b66263f5bb1a3866f54ecf00bc7cf56f8b022ddf1331534a8c05dfdc3dfecf0d8e1ed68207eb613862469ea9aaee95affa09efec57a2a0b8f09e131df4583a42c8c766a6bce820c8721812d61f14b2cb1c4123c2d768e2a0739e79208c6ff4bb1216611fecbf545b3499b4626513489a6d16432654e92999b287f6242452251d6b91c738a8acaca8a5d491953a69531c564a698a659a6504cf384344d13659e4ca6699674f9e671881209bf177dfb38b0bcc71985d622d168148624520d8cf11645711c4d18e31316618cf188370a6f144642e24d9a5098c1262727229108052535a29872ce5a8435c61ae3aba26255b4de755c96292356c1ae52aaed384414499cb1c61a6bac31c65a63adc53c662d128db4168d3aa5c37b348eca9234d5300c47dd6a656e20096ff175e4688bf78e5a6bad75f8eb78639cd2e11209a9353691a612a506a4cefb44629c31499ee08c31ce592412894438637cde7df9b374107f73fb82a485fdce5a77a34029afdfcc83b0c744e0eb768f46a49d160219c1b34046dce87693aea0048aa8d11f08c950a791fd8bf8e90f8444a8d3ccfe4886f493fe40487a19d8c16f074276e063073b9ea603367c03693713f22ffaa3c6ae3bc0478d5a905e901e803c15208247440e226610f1826e77d8015f105faf3f908fe7a381af4610a0a05eb77b0ce205c9a0db957e7d1ce871034a8fd01fa847adff0efb03a549baa83f50eaeb9608dc04e4dda28b71680625ee0ecf0842ce41c8e61c821079a0453c30e540a47570ca3112f90873e841f340ca4239e840e650430e3066bc1073e00967f08c3f489842209846d4d07844b8a2518456ed2669f868f8e879861f248d1cb99cc1098d19399430f5732b730a8a1152c61f3456b092aed0b095846029eda025ab74c1bcf2c161a503ea8af0744172cfee21d38ed08554c24b8e1f328cd8ba19ba1a2c4e0783e43925cf50c7736be586c2989c90c9e7b96d811467901949be71886b24f9524540c7d1c748f6ff91cb11e666e4ecceb9c8b53055d61007551462c320137cfc88c1929ceef206b09de843ea7ee050439fc16e1c5c8cfaffc08167e8c70dc09f4ff7e386f4c70dbc1ece409a7e4ea8b15bd7edb96ac1103086b8f5071aa206042574dd1f0808c16ecf0688aea118e40305c1fd8182e8fa0305c1f5db1f28480b7aa93f5010186ec075da68f70a43e8d0fb030d31a3932900f980627f205f91106f06e073fcfc0292bcb33c9af68617fd7b73944cc69ef76a24b3486b91de48a05c889077cf68d9c769a67c3216314779202d1a84df625c8e1790b4fcfdfea891d680d4f931c6196392111263bc35296b7ebbde404a7f537680cdfcd4dfd48f10f12be1853e461036e7c78f818f838ff54e880e4218d1389cff451a8f447cf711cff64b247dc32352f8223cca91bf69ba168eb0e8492e885ae71c84ce3908fdff347edffd380731e7c78f310a58c8638c0216f218e39edf57e31d44bbedee4d8cb6ddbaf626c26db77d6ceb96c5a31bb7f0b35bbbe86da21bea5182f35d5ea789dd6ed2db2d8ef8eeda926ef41bdac21a1ff95884610b3b253db74bda85d7a55f1bbe1915b909e194f48c86fa1d35f1d169da054fd72ecaa36db989c5d12fc09097d0efc67f8e7ee3bc7388826d7b408eab7ef78ffdeed535f1af3137193710f96f44db3e8ad7fbf55992244ee1be1e05bdbfc4fbbe5628fc9e417fdffc3bf39ce20c3ff541ce38bc9bc4afdf3dee36414eb8f33d777844396f349653e8272edfb9007ee111c77b1381fc9c70db70c0688b4820327db450a4ca67aeb44dab0d1fd860c70da7700293c0c62f31b2825f2d4774a2f8d50389451e5f0e820dbd45ad335940d2de77025fce81dfb9b3996a057b6fc7fb9eda04f8e61c44b12482b218e36b6168d13e88f088fe9b047c13e17e55e3740d77c6dadfc2e2da3f5af1780c26d9d602ae66cb46fe46d7f02bab6fe284e3d122437ea67717ddf1a89099e65df4871408df452fca0989daaf7fb1122965b7d8392e7ad35c5db57f5f8b538a3128be79d1577401913ac8ce8626155cfac9fe135714e372fa9d889c54d40bd40d45030a87cafd0bd5835fa8dd455138562a5ca2782b825c1d9648272277160b52f14d4c464f3da9374eea4945bd40dd5034a070a81caa078503b543f1fadd237d92a24232a1c8b9443b2123377e3d74a03143062e2643ec859d2db96289634d4fe14b01441892e44a15aa56a158abdd99e6419d7066d5a280a4697a82922a553097e1a933b7653a1911d94f7644a6ca96c880b2a08c890a0035153b15381ebd3bb5c3cd5ad17b415a2dd3c974329d4c27d3a9643a1911d94f7644a6ca96c8803226b220006afdee90a744969165c43ca6a7f0dc19b35b842e1c066c11cb719dfa876f6674c19e15f79bb9a9e4c4bc40d2c2de32c29caed3053cd9a84b54a94c287227e69cef544c48ec172bb2d11cce0210fdee58d0d7f44e3c08508c54c70d1a326c35c115860c2e5ea8d520e15f3543bd068947ef86a92a4ab803e3bf09d4cdd6f0ebb6ee4b35b7ce1bebfe258c7b288930446bc79c8768ad75369d6670c218f300c502838837c63afc92595afbdc3e4ada2f0acf1357f3db4998c746de2d760e0bbb448e0d6c7b0e475d7fb81fefac45e10e47278955ec8d8c7ddd7dbf46b4433e7af15fa22d6345a97f89b6c8025dafc0bbdea2c8bec5a791d681f82d5249255ca224a608e143d2afa853e0a2a21de2589a568925614892b6c68464346d55554352a9440af745c5d5641a4f23a9248ea35822997c6c943dbd80b48fb239b456abd56a2cac90add1ed0f0d2f601095dc7df7dda8552f7a42e404258598acbfc270861b432f89a3c9348ab5d2ae91762ddc281ca0a82d504fc810df90f895535488b0f648911b2ae8e6268b4aeaf66facfa2f247f433af775a1d3ec124ad82561786edf0aea84328d2811552285a89108a55128140a9531eada009c90a9e89a396bb709bafe2e92776653d86c6d4bbfbbbcf6714c5b73bb0844fece1b9f2b1edf47dcc561fee5d1bb4ff6ee9a7f599cd5d7dc7d5723209db57e7e32020ea43501b617cf20ede6c1b5145c7b1379df941c69714d06a2ca0de168edbf45fa77c657e506d26efbffffaf5a21ec777fa05fdf68f7ffffffffffffffffffffffffffffffffffffffffb24d26936994c9d80767d107e7920fce3e38fbe0ec83b30fce3e3c954aa5522e1f9c4b3e38fbe0ec83b30fce3e38fb9ca6699aae8c7d7026f9e0ec83b30fce3e38fbc0b0582c96cbcbcbde9c9f301906065f0b83fae0ec83b30fce3e38fbb438e7270c4cab151323e3ca2e17bed68566ec83b30fce3e38fbc4a03e38fbe0ec2323e342d1d7cb041360337966065f3b8366ec83b38f0bf5415fa318f4fffffffffffffffffffffffffffffffffffffffff3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3fff3f3f3f3f3fff3f3f3f3fff3f3f3fff3f383b18ff8ff6fdf06f15b16672dc25a340a499944c2d7924421a9248e791cf1b5e35a1a4d27149949125f4b9ec813945499cb125f5baec1a126c155f3e8da9aaad9fadd293c2efadd2ab61518fd6ef3c6925958f0b52c355e8e1e783b1fe90e03b11056828158c54378c5ea0d07a306dc6d46cfeda277f7faddd7c70dd2efc6bd4ccbd927ca2211be5694d38bde40d2f20d4f2ea2288aa5daedb9babbbbbc9bdede9af67c3f15ef6ad757abd56ab5fb53816b90b4dbed76bb5dad56abd56a2f2727272731e51abcb56bbba53ddf4f4dd3344dd3dbed76bbddb0babb3dbbdd6eb7d3ed78e9ed85384dd334bddd6eb7db6eb7dbed763be506322695da9ef5d99f6a81699aa6e9ed76bbdd6e585d55555583b7766df77671a3344dd334bddd6eb7db0dab2a56b94aa55ab1582e2e2f2f1bc5de425c8fee02699aa6699ade6eb7dbcdf67cf6a702d3344dd3f476bbdd6e38df5e36e7e70903d38ac93131f8da98137ca37b80bfe2605ed775bda5a6c5d91eabb33bcb4bd334cde999539813a6d58a89919171a11945f1b528190b532053b3b75e0045f736cd350492abb96bd696d7d3e7d379cd79c5796db546ab7b74fd6e9919323af4bb5dba8b5ace42464d2aa039fadd2f930ab6eb9d09d90413f0b5268427d50a2412747c6a54378187bcbede835ff846f7fe379eb4b074d248d3f4f9748ff6d174cca9ffcf204dd77767291ac8fdca3445b724c911cea296d538ef3c023b144446622fcefa57a629ba25498e44235c228525bc0221e1688b56c818af400849e402a9a4c5d1f42bd31cc5d32d49522ca146fdf7681461aef97d9a7cda9c45329fe8cff8febf2589a517a2f8d95a6badb5566babadb5d65a6badb5b6248a56dbd174b2daa2acb6d65a6bb515599454aa2cadb6226badb6a5b5565b91b57a8bda8ab44561b51559cb62378bb5d66eabc36a6bb5b5da5acb72b1dacac06a6badcd7777c0daba3d47914d098a1735323960b44567095410b039a26d7344a76dcc196d5bcee111ed16a10372cee86cc1029523da3747444d006f8c39edea641a0e7bc41a3dd6d6b8d97d77d8de7d5f2a32448b7bf67eb7d662bc51829034b38b1d6f9cb3ed9cbdba05d01b6ff3ade8beeceabeb099046937cd1c95525e188a763520d9ffbdfbe39328870474bbcb21c8fb765f6cedbeed10ff667b712e8990b414743a1a9544c890442a954471244f27148a8ca1a0a0a0a452652ec3949c12b64ab234cb5549246fd1debf38b4a33eb3b8b23b268231d952eac8ab92c86a65d2211d41b2582e2e2f2f2614019a79a804cbdad169e9d00800004000e3170000200c0886236196a641107bc80714000e537a525852582c10c882d11cc56110c46010638c21c018620c21862154732700868559b5b3201a75f7b59a2d0e44b4b25c1ac971e0cb5fd40f41f1e6d2a81d7dce2389573d4a8dc1013143e345a907d358014a4cb8e48de22c6e26d827e66c4fa65b7de2eb801824ea3792734cf0c2986a495bb2f50caa4062422ac21a8c28ebde7108931ae877cad883705c03a39b78b62f91cb38f29f6e037171efb2bda099b81390f5b10ff10ccd9d994076eddb8d90b55b061306e93d0069a4cddabb96feba2d895fdd45c699f8def61f6b2f9a7931a626b208db588084c62c68278534521d5bcab59c7cc45c8c9733a2d2f69c9bdf2a3b85274cb61fb22355e4ae62b614b5cebbb7492b6ef9764bc12f65ffd022a150b23cac4e708a4e046cb470e4f9ac01ee508e60278c2efe849cec74ba5414e6be0eea99ec27643bbad067803ea6675c0b60fbf4fbd29885c1d8a4ee5ec39b27dcef696b614dd166a4cf42d9a53989035d8d02523938e4fc81f26db64a9631b0034f4b50a028351ecbb387783a040388343c3f83fa83c9a179f567f86afeaf745212735dcd8b3c5bf1c0c0410d451ef3958620054e393d32955cb3963b735ebda8a30077c34daecbd368ba6a1f2b8e11875ed30cee1d41da278b5b0fd9c617b8fbd822fd58ecae453ac35fa27c1dc2ff6eddaa596bdee3580e061bd2e9ce7c0f8dacbd0bc52728b0a274e6eb4e11d2795f732ec0549ce849fb8b897581ddaa9a8c0f4d9a4758f9da62524efdd88acc07bc46a43ac59d362be40501d93de404285e5ef3861c4336771206ba50369b9c2be49d5a3649a11d35ec0411bd8e395943c10e9604d189c0432d3bfbc55cac0b509ca7b38a0b30ef400ccf397be620f90862bae4ec290c291bd6d1bbfe3ebf3889a2eeed342c8e1b454515556dc3c04c57ee004975bd6dc753eb48d9079c4c962b80622102605ff2f4acdbd30c3d3889e8561f891709d456f1a270ec7a481035b5a2d8bb35cc1014394cfba0d60618e84e34e85bf4ca50d034be5209dc4493a7449ada558f2718f2f1cedabac4fc9a1a18e5b5ed633395043b5d8dd42498a61563b1c27c2a5728e639689b15ab0b02a4de641e433c532630015c5bba366f289718b4997394054d45cc71aa031d5d13d21ad4904613228f706f93287dbb036e031411dfff4b28baf9a25df6b75a31eb1fa2deffd289dafc602b6c11b05635fadb48d4178a89d83dc841fc390db698c8a49b398c548bb3b45d3f52d80ed6de3977f31d70d0f03a53d4b89dc00c2e25792340521dd7c4e922079f987cb81786acbaa101037e6ad7bf78091e26f06ed604739be72e53d94afba808269e63447580466f1044058dd71fd2fd98c5983a6bca2654d6c4d2e0269f0cb442b1a3a87b12b715892912b00326997110a8e82045e701d9366bb34e35af6b0d2fdadc51a61f0c360c26a177d995a6aa8919ed418d00f8d51ce50fe8f206c61627f4c5389759c9e2aa830c812520d72b110bd5f2a7a3d556790b665d6d3d0ab877033e29fd88893bfa41d1fb45554894147a212fde40b8c8724ae1845abb087906dfb59381b5065f063e329ab4eeb8c445f0c0248bcc36b624c4d86b10b6dc8e50a3671f13ab0210e9bf700128c48462b76639db90a0c0c193e3da255bb4e3904ce3357816bd68ad6317310880ae6e239e3a8d037a1c138b506882004f9e2f89c22f21b39d545860a0b59d71f0b19e5d23dd6c8941d99974a3340cfca92ad12e9fa263da7183d91b1c053cc6103b745922c4626e1a424bd67b0ffbf7dbee8b102745df909842b76eaddd41fa981ae96a3e73d5d44857d399abe699abe68d7435cd5c3599b96b9eb96a5673d73473a979e6ae79e65af3cc5553cdbda699abe6997bcd33574d33f7bac936cca0da7c4e308a487bb238e435d9a71503fb938648f640bb7d069dcbe6e790116ee8d455937b6f113764aa1593abf7f87a8b65dc0b71a3fd3674bce499e86e5e641fc1574c2ebd47dcd029574deedee3eb2db64139602b33f43f0d91ae81f6fb087ec5e4ee2d820d99b26a72e92d5e8f720fafc3ff6eeee24f73af77c1a5df864eb16272f716c1864e5d35b9f61eef77b10e6bdb513c2f697228ef72ad84d9fb1de20fca6c1fc1564deebdc5eb7d72943143e922dc859cded5369f618abd699b127dbaaa0782bbb66eed5d3ca6d14f43a46bb114a8ed7a5beccaa54a792af44dbabb14d939fa89ed537962e0d6b4c6db489bbe4d6bb86fdafc69dafcd1f4c67bd3e6afa6cf9fa699bf4dafbf4d3b7f9b367f9a7efe346dfe367dfd6ddafc69faf9d3b4f9d3b4f3a7693fb04fd57d1613bdd51cd1187559546fcefde63251fdfa234a53a38bba69adbfaa6b2d2e7aab39a2b1ff929a70a77f5d2eaa5f7f446934facac4bbc7aa577f8ad252f4cad4b8bfce1075e32d7d33ed665d1dd40d9026c6143dce6aacd034ef43cd19b8f812988d69f8d3395e7fa853d99318350109c9bb3ec07f72003ad0426f51e62ec1d2fc239ff8954185c193ec1d6b6daab09fa32c2cf9c5ad1ab6ba955b41043774ff82f88d5311c2cdda9f03f76202a5a2d09eac395ffef15506c067ad83a63af0a81749dbf8c5ad182cbab508eaf38bb4bbfa7192d3cabd543ecb4db4baa0453e53ad4e4d8e69775e698bed83aa60452c5996a74fb74bf3fea6dc7b16d1cd4219300344f11f4173350324c49bf7abd7a7cf489fc5c625ecc40e0c2813c76489702b31376cff1e835984262b1e463113d7527b4181bfa9c410d37c5e331135c95815fb7c10a73828afa628c2ab7b032586c39dcfa02db57eb44a211891d684038ca64dad65d97a49ee85d1f3aad48d5e89bb81a1d5050a13fbdf251c96cbc973cfaf566a96fd5b6bb3c75127dee05fa1e9ef62290cd08a3b006c73efedc5a83d869d01ba0c0ba620b1415c0ce860e60e8617c7a84076bc7b9b2d71e06a74119c7edbe4dd66f3e6ef206c9d63be1ae6ddfc6b7dad676a576cd4bea7f8d71fd5845767b5ac51416b831f7c4559d44203f75da31679e5a17cad43e09033d9daf9809509ab439704e9c4b53c2dc82d731900d1b8c63a0ba0037771019b29f804513fb29837239eac4f0f4cf50e4a1a5d0d5d04c72f9b79b3f12f0b298a7cbee4dbaeaa6c94e9dbdb506e3b3ab59d45c04ddedc28de93a382d31e6ae9236fb941bf9cf2306cb25f7f3993efbaba8ce0974560efc1b222f1e8fbadfa262100ffa2aca0d86f389bfc144e64ca832462a58488ba2510445f50b9f6432901f1e016f71faaf43f4e6143172be90c79b3c8c08031853dd9e43830c75aeaac026de6cd266fe06290e88a3323756f8d6d3e60967b12b2991c3d0959aed4638b72a0225c54b305149fdf07efd5d8c54f2148a45ff042b22c3791b17810f1b8c459cba32b8c7a69cc0dc8f861d6592109cabc327b3950076ae87faa1b73cb1f2f40032ca32e5a4b33e366962c20dec274881ce2b5c7e69828b728e540c5de674d18ba185d00b75f36dcdb0e96c51a558164a3910411b622ec0008cd66cb4ade5d616695e61e46baac82e13775752b6291cbbfa1b674a34892954be63695bbff5eacf69b9e280451f8c077496122217d23fe9d71b8d0d1debf6101dd26f72adfd67886b1c686326b04359b3839127eb38384b96a4f8945c1faf6e5ef7e22454191ce4e5928ef10882b258f447266ca4379b9f772b8cfde590bb061bd463533a3c5690df4346c2de4d922055a8c8fdd3f7ec3b9962960b41ff225286a5836ea9a16aecda4190ee0650d8211d4ef2134a537c1dafae22689c8906b1456e1dcca82940b0b1b442334373dfbf63a7d7933ec3890df109d7028a43325110ec01bd539edb6175ef3a5fd287fdad1b712ea890c71657f79b2f430b7cd6a025f83bff43cd7aad8cc98d4ab179449036668693edf7032f987d8a1f05e3f4e27c005463445c495b9e1c20f63947ac7cf1961dfa4a9dc1eff4f7055784b033e9087d1bf241083c2e6dbc68d4e1b8fb6e423f63a5574281a24f344db71bb188d9b1f39f1c23aca33c31b5215f259884dbf98265115eb8721a3c1af285a460ffbb74df4c6e419b7ee9924e587b0df0803c7428c4b22ed34f4d0bd4781a1e3ca0fa66d91e9b5c523bc49133c8d58bd96c0f98a13ca28077bf92a52249d8f495729d6995d4e89605ed9d96aa360d07d92190a323a2647c7c126dc706b9bfda08c4a5a7878bf8e587e07a70fd3e6e19e5ff41b01c8866e75b32cf5b353fb682b8ec77080d5548e7d8c58b733564aef1c08961d3a32eb85b8904733079878434404e404d72ec1c0ccdbf60d679ebda8fbfb2cd46d7d0d6ce89c9fe3fae21f2c0d182e277984bffadaeffb1e6ec6f0e44aecafde5a57ecae29af8eb04eedf00faa7b1d078929499b425dcc17f67da3e3a431b5e07d4eea56e4163195e5e7647d48515a6922afd1c32d27c731372e7d27be09827c94e53561c3af098ee756c692cdfec98efba55922c29fa97ead647156dacec53c4b0d462fef5322e3475b3f1ffe54123b410b455ef37b712c180c3b83ccf9b3a741d7a32511c8627912d377634e9544525579fef03c3d72539fbc6fcb2ff7314c593c8eb013f69b0a14db4574d44c14d9d456b4b27f20ad6b8b627b958cd35d8a712242fd90913ab4880686d0a53e46d1570c74432b6462f48d53ca8429dd140860c4036abb7c4a35dce56b50bc826a0355f4b705aef252ac5070ba8694845080cd6b83cd8da57128ba6f06cda7301a8dd32f31af59990cb9a98d2e11d95ae2ec5f57471905a5b2334df6412e88ddb836d4df72182bc77e2fbe8514e883645feca0a7f134eed25a0d79e7697fb531b961c78750f374df0375d97f72e067180b11fb707f2f8603e31fe9b8bb917dc95331bc34a69f35b32463da4a33ff67a2ae5e2fef45c0589dceb960be78591b83ab42fa12814de3413a090854c1b2364c2aa1bde3db81d7dce816d4814e316af5920150fb1e3f6511812bb1195a73e29dc5e736dc0ce40e42683d78346f1abba6a4e274faba988bdfa8d90c2b2d968b530c4a2aec94c42e65db0cab15e0ee472eb05fdae3e7fd2680c1ea650938dd0ce22a4354a8bf9b4178921adb0ae77db81ff0eb5c05a76ac5ccc01269e60a340272a1744b5279d968ed33c7b1258525cf0a4579fd0cf274289348c9f81ad9b7e5c05926a22da9d7949939614bbf4cf469cc84a0a44d5c1c0e9f804ecb4d5aeb0ab989cd603af19dccb9ad5dcd0efad7842b97a1c6aefef1966230f9ce749e4fe47cd3a3dd3b1b1ae8791cb8ef6e182634bb703f49d66e3c72370d8497a08473f45a64bb01aab1f3fdc7ce0a2abca42ea52d5366f52a7424707c8c578be6067059951c75d2061057de42764b3b613a9da2a7dea46bf4354b23c42090b1519bac75d7709f047d93b6261808848858646fc2cd3a5c85a4af743fc343fb9d724d18699704b77059b18c69e5a7d6c47ed6c4270fd90241738233ae5873326c7e59e4782f4fc325313f7eff0c3794e12d4c07fb257d95c796806b9b90aebc654bc481f78544257d277612f63c51b31ec6e238bd1a57b421388c4da68b83413d4a5544c500726b7fb9333db8d2d41c2eaf324ba57da854406ce1eaaea9c6a46af1348b5d109d64fb0c99d5e99ed822295bae7ec54643bca3885bc71b877593f443315d0d3f253c72f76ffe0ddcf7a264db0b8a962210c81672b23a980a1e1b47768f5e4589da63ace047f251ba9243c9d1c0004b11536e6924e48b447c5841d75fc8b82ca4b93e3de03866ec444d110dbd6f81e275aac4551c50d9c8662c0e2a9d3e15a933460e81b9e61f43b107c44b17045dd7588baca67fb9f8c38caee4b3b6496d37a086e00f31e23d2e7abaff81baec3d39f03fc628af75c3074cc5e15dfe48e7c902e10257fd4b03138eb9c0bcd4eb2a7c24455a87529bc65e6e88220ede57e5aa9a727946940aa7900bc8aa18c8da498b80f61085d70ade2735bfff2f63a9e4c4a2b7d5d3410ef46080f25f9c373eacadeb4dd5460e806578851e16592de3a519aa3bf69333d5933582b0941e15b94943b08a32a40575b2b690196a95a49c8a68620476f301dbaec19f973ef8d137c7fbba47e15d4964dc9f9045f467e24d1d9a57c113b37af65f2b2eed87221e4089ee47398b974f3210bbab20b7bcccc6698d52f4f1191900d26309cf4f353cd6ca81b77e0117cbb0643556461523422320b5932895ae37ea12d6a318a83faff35e37511f2d71a6d07163e1d0c9ca9c5d1834b649ad9c18911ad178b12f37e3de4661dbfe23e3476419bf586970e6a648ac73517a9a046c4894f7b9f6c038e16f377bc5ca896370710ff2e3236bd16fb682173e1115789aa11feee9083ca686c159ad65960f3ed386b8e6bfb48935249d7d47f7a526472006b9194d3c3a1d825778f4ad2f28fcec60c9f02de27858b85199e9b589dbfb0802ee1439b4640b11018a17b276b1eae603a2de9108513b1f33b31e4245d83c8216221ec06cf0d249cb77427970077fce5f41998c2c76142789668c3fa8dff0ccd9b30a66754a6670a85c44d00252e1dd051dcd66824ca07e00fc816bfbf5bf34484da47ae3e3d26f07bc38019f6f11ecd87c0b76e6bde1c29c76a239ba29ea966aa75bf38170fb71802fb3b630ae9d35bb9da331935f9faef05f542693662e6501df97a2e1c7a1b4c876ab61df945ba1e1bcca4f0579e8e0c0611ab8a49e4e05924461bc3d8c2be8459aa78fcfd2f47777861ab8fd31a03c1879fd5e89f6288b4384766287fca865c7f74358b25b0643f38cb0732d6b7cec7bfd1fb1586f70fc59f0e990cf49bf02fd38ebcde5475492b400e061029f71563ce70bf260033010c1ffa486a61f6c84bb33e95d675b566d4e608942a397e9a9da4b90d9eee0930832c173fca5877bb9c6a0b71d93202cf4b8c8323d091d6d704797f22a4df2d0983a64bd54c121dc7fec92c96140e969c405e5f48307c6b9a768ad1104c240827c2d0f03e82b25da40073a1aeecb4802105afc3dd91c146119573fc5bf685269ec9450eaf947fb1a39b61abebfff010ee9f6fd4df0da743903fc4ffaf1d8e8ac22d678e827bf2a45f98bd1379d5cbce2c939e38145084014ee003b2b04debec5e0fefe292d8f85d8794a619f62e86ac067727fffa6dcccaae74e090e5694bf40eb465a64820c218f8cdca0b9682cefea2d9599ad555d1602376b6dc789e7aac7febac6b463f66d383200ad65003cbd4d732116e023a357f1d9da50c536ac18edea40f3de72ce860e040458433a00677ada40e5c17502434b41ce00b85fdabd0109b530b09b83f3f2056916cbf3266528564a9d9a837c8a3ac6674a2a1582fd6be0e9af76bc29e6619192a6f556141c1353deef31d28a14755057b0d68a4dfd3bae33407a6de075c4eaad41090025ecd8432a81632c0211c02105bd3a15b05cd1054f64f5e70f08fa6db25dcc507ae28962f39d60a2759b1d7e978927da37b5195e7cc08738c9b47160c86ce564a1b31ceff2a43d9a970175cec2a03b2bb3446a44f01f58c1079fe5d4cb93b1b114e746e7db526a1bd513694c9ed9486d7b20339798267113e47a96e593b550a436f46a4ac181cf83e7e4d2ce04b2a1f5856aa2f14e9cdd3e7c88a5a6eea4a80405da3c326eefc84da76a68d35c97ff7efef4bd40ae4a9f8a08cd597b09ee72782cc4c0f02438ebe2c10b2232c1cd84bc69388e821f3916e0b82ec64fa43e95c38d9d849884290eedecbae98afd4d2e1534fa5757166c649c3180ca940898d249076a621a7da50e7aa9c41d1aa2a7aa556241a5f725d119ddb85039789100aa9b2e98f3781a0e3909a00a2ba4aa49a3ae8d84a8801e06cd4e68bd4aca2f68bd4abfce750f71c9b199ccb732c260a3b62ad478d6e37d799d5d472166d2521d8a6361747b54abffe6bfe69979a9bbe69795425121b2d0289e10478ce74c903cca37f0debc36c26957665d15ca86077980d41b866bccc523ee1fac751372a870b08eb8a9757e5462435fa55732ef0d9dd644691ea57e8a41a933e710febb713750ff32265610ccadc74ab98448bcda8755febc61b203fb40347015cc3136e1e664c2d7f10592dc11f4444281e2ed905888451963c5e9287d6f1ab64630ad8cace6587c6f45c4370344099445e8c69d6f7a267d6991e6726cd27c3b7e5e7e640e20b9987f507c8a3a426dc6de055b49c453ccece23b640afa292ec4a20e846be5c9d3b1e1287329f1b3e7b10ee5679d1d82d1c1381e40871932ed278c003b17e050ef3529292753cc9d697b842fa17b2da9250a4ae29c6ac78e3edd44fd0a5c9ccf32601b063e8f654451953871b653f960f5ce44a0fbddd6e31433ddebfb336bfc657f036325f6c8b0f3aec5472fc2eea38f8c15d0b2bf0082338b2541e2661acd24415e60dc7866264783d12667ee6e3003f121815351aa7d01536c686e1ef2b5e72dc728d53544a6b6c2dd04865927487aee48d6eb75b4c3d183d23a2574aa156a762a9988cf9e60cd128c3b9455a327885bf37d08375b02d412f0b1fa1c7acaaa8c6bc5764b51915fd8bd22a189a3ddd08037c4f7db0a808d360168e40dc04cc906a19131dd1ac290be16ff0a0756f579dabca5e45ac5184eb34589777ae8d3d2411818e461c498f18e2cb890c60de3f0449b5e0684789c17f8dccb07cb02e341b92bb3d1dde5163cf54430d29081667acbb3c23995e0398797c35fbce94b822a3a532f5c03b94827a8e392cba44a5e8209fe3db4102d632b5ae638bbdb3e70679725f8dd85be0dbddd2b3a7a74e3d4907dc39bb804717422f5a2c874012df1aee8d50bd2936e540c459ad2443a998679df1447a29b44d710f4933c985d1e3adc7344b0d91e2239a58c77a6097ca772ce4a22000856305850062100e2f060dc1d0b127da8a8dca76f37588e86f260a3af0f6003b5f77189ebb081609a9453c44b5501ddef3f2100920b1691164b1d8fc8d502144b481d67a102dca94f4446e2cc50ba0eaad0ea23078263882d2c8363f4c3c448bcacbe73c3a230a00b0f8548344bfcf5e14bcd7f71b6af63c55f27b94beca35d257a829fb0fca35649343c30a0a849b7aefcb98dbc12648dbde94775e220bb525adb826f329018eb4fc660f7848ae4e27ad942b64007c16f038f7e28dc83f4cf8c50d1fe3ca34bf0dcc2c49b1d25fb03e02b9b3e7ae4a4d8ec726ddb36cfdfd68b29a66bca28acc0d70d96d97d053a56d86dfcb3123bf130d308f1fbaf73f69da43931bb1edd0f40f23f05c101d8efdd001bcb2480950bbed259cf2f974ae17ab0995cfeaee0b3c49dbfbfb87b343960949652e0e9ba2b454de766f51d28fafb8263e99d7dd1ea95e598ad32ea1cbe68ea7b2961d9754029fc5ede02b35455984736a47c19db3911617236c4a267d0f6e9cd6dcbcc6cbd9ca708fdcd27328dfbe45ed359f211e28001611271e88f80ed4e442db39a759b7e2b412940f8b1bdef46ea2f746029e9e9b30c4a88b9e861be4dba467605a14a0964752451d6430f29f01af804dd7a220d6d2609bad1b50e541cd2ad5cfa14309bc981140322209dd9c813829bb4c86c19738dfaf45275e59bafcbbe0fd7e59b40f4633a1769154e5af2b5e7bbdf5381011a1d09912b124ce0782e06e10c8e4c8060d9f679ff7f80b0a52d20f2d98d4e83a3658560bfaba8b4c4522147e846b3a1f01bae7a1add79563e0869f008cc9ae8d7ad1fc3f19450fdde41c8afbebf95bfb45f5a24ecb638d92e08f40c29671445071e8cb1f724fc723cc4768eb76b6bb8464bbad8f71b6fc7f29665a95beaa57b8c7005082557482cd888ef11d7af82adb3839e854e1581d86601f5eb858a866487993f869717da6de289eccbacc09b9caa65f7dd262bf821ec35f3110f4672f6294722c28152d874ad2fb48f65a4d139d434c32b8ba3243b2b43fbcd03bfb13451206611e9220b5fc69819cfcb839e21aedaa8c652285dc8f081915ffbb06c89f05a72b44048f2c0f0d45ea8ae20aae88508b1287926c918b9c4e4a1795c7d4c2c5088711c43f6dcf928b640f58ed0317c700724a8e34df5c0ac91637df98b9117a06156a51b56e7e44fbb82ba49767cf0dd66aeb7e443f34ccda5f6e314f2b08b43d0aa20ab099f88d9c8db96292567406c11f35dc101f0e37134391b05f1bded7b2e44daa1d18dfbc2c885c21eb2dd583b65794d4a45f005e01227014f568b98a74f965c02a83e44fc7606c1be9b6606124b400240220e317922e806b64cc114655c9188d18e70654b834dbe67045db40251204346ba6fe47078d499c32b0557b605bfe4b138797fa1f1d447a0456609039c7e89a2c52056b42dec45cad27a42da16b51c1523a2925f2a546d0b0b04ecfc88bb6ef42afcd3e7321b5e5c2906a19a3f9091d02616089e7b117b0a11c22d014e7e9239049ac97e3d26e2b4530021c66f1a51ac5134d8f015c6f185a37e6c019b9ae83102204b814a0fb423c5807c048024ba42ca5982b31c797924b077bbc53869b558a75fac1c1de2f93d02749f9b268b58658dabe6271127f0c5fb3acf845b2a0a58456647c49a82590737e57b2c9d6175aa2475d955258ef4aa66035140b23fed4f6365ff5e5eec3dd5e953b6578765f8feab0effc166afa5fb693a22e03eba1de8a116b7d455b36f5eed0f8ed340fe3a0e5819ffc6cac1fedcb2cafe60889e486865f601e9c0fcf80c3db3975d7bac7e9aa856a5254b9c55b3dfe0068d255530f7d2a15cf70e20dba03b2f465b129d2f581dabaef726ce0096ddef79918daf9e5e6f9fa51e54ef6a92a31782aa48247b3ad7f8e7509d2315589305e09c85dca2819d25999a67824e54eaa768256a770a2dc2fdb7bc76036a484b47e35897a8996b153dc2dae728fbfff4a4c4ef8363a1726f3c2fad7d99a52f829606f8f1ef56a5cbb63ba45856c83562f3d200aa872c7ce07810fe8a08f53274ffe2224842a892ceb2a76f43e5c3feba6261f3b4c57cc7f1f5d4494e602378948780678e649c36d806ceb258beec3329aceaa216156d646277baccc514137cb3a1306502793f19d89db77daacc7ca31cf8c7720387c3a6c5f16e94cae66cd652cb8b8451212d75136acfc759bcdfdf7275eab2b483e4655ec3c084e21630fcab2696456004b9d6d6d82d82899d5f7ecba262515f6cac0826c8c84e03050c6a8f7ee65d1cde098f196b24fe41ca76b6a6a4623467f5ce86612fd859dde04e11854b637dc661f88d6bd8b0474fae7ad29486c37a33c506206d181e80b69eff17f32c67284296ebe9d84fe86ac07dcdece54c8db359c3b3762e5b26c086c14e81c1bba75dfed79f02bb8931708603eb2ad0a723dba1aa662dcbd424844025a883d8795a621e87aa460d11708d2811c233d5756230159741e0995c0c97e6c4ed8b6ebbd57f00e71193bbed67bdd588c316bda9d086ccd604a66863fca35daefca8adb42317f2f4e1a28a5bcaaf5fe7550e67955868fad0f034aa4d763b16f80d4024b97c324414d53818cf5573fc030f08807048b2aa5835ad2a59a28313e34b29d18094a8756f02e372dd5b691db0221418b5e1d3b19253c5984bea4677b568a8aed3e4dc985e3fd82c145834d8a232b199e5733669d160dfb82c17e80038853a7f95b41515dbb2d14495f0b0d45a5a14a4c5b824355eef01f21a0227992c722e2789a313c7a13b149337213ced718142393477a7783498fbbad9ed247af67d812c0a9f09d2f328ea3ab39953734795c173b12fefa328e8fe8664de17befcc29aa1d2a04e129dcb24d9d7d05a2ff14676d1fc6d62623325416d25151f9e543274faedf3fd43738033876b58eb1880f5c61254b1c0e9f38aa9f88b0ea256e6c63510cf66c985bbc1485df41ec44fab84c9a9bc396f07bd64188fa32a103f422aef304598866f2af487acf777ac5e0a5a7aff965353f13afbca442cd30589902418700e7736b584246f535dd7fa6d1eab42cf772206e9214194d5b6d7ab84f428ff336e07ad129e67959bad70ee552af13316cafeac5a4e7797c84d9853fbeb4e211c5db82efd49edb1d6e9f40a4758e5e77b6ef5ef5c5812fe0c3c94e7796aca38ec9db6237853cb1835e363d632b209da6efec1530e8d389bfaabd31e59ff6f56b676de021eddba7794e5560b6ca2ae3e75ad96e3c090baee433a99f6a76df4f3cc991e96ddb5711e2174d1f836ee2fa45d7ce94bf9ab5a4e52e330ba27597d54e640e33def5931285bae60c35a54601828e5b15d4121f46360ca518cd49389b191f27e18f3eb6219236a48c9874af499227b28f30dae1a5a006d31ca201dfa13a0360dbe7ba2dcd11d05dab689c30438aabed8fe002af48bf053c3776ce0d9545974503f5d96d2e810d9ebdc2d9cce1b9cdacca9de0aebe145ccbd2f453b6b17a87449a5fc2d759a0c6dbc5c36212e0b0f67e8d8d6900246495399c39b98aa1ddea149a35b0878c3745344313e75e8397a96ab7302796692b4fdee74e9c6761e56c1528ead5f10e3648356b2c336d411fa0af370950d42e534fad9707d2f6345e63e69eddada6ba726dd030a85b2dca6ff1b4c50b7fa742ef8504ad251f046f62e8d65c734a0243c94242e9d32f995efaa285a050bfd2fd3a992551e5fb5c943eb8b527910c5b650cf6759db903828ea2a7b5de6a7240c1a8a9c0d2db325bf8d96b8bce22bf4ce97b2c66121523a4fd216842ebeb0b3a46f11a8944ccd158b8c53c9db5631ebaf8cbc28e3d8d7fb5847461ae0659bc3de8561d80e883220641917a39dba935d212ec17742cdc416375799d5db23a29c5f7d2764a4ef0c557c8381019d5e8abd783c8e4a2ab4ff10bdbc92e75cd75d7ec583505ecbd93226ec645962f5532b14d751918918d027726110a02d21add89e906fcff9b16202626b48902fcb3f819117b52976ba385671e4fc3c722d049d3b458efe4986baae15947be3abc9690f84f87876200473fee8ac5066e2d5c1bad1ae518a40f85a614385dca6e6f23cd2ab5a5001128ae2d6b0ba9dc51405f8c44152f749a17f583a771e07d3ab1cb5fd53b0765f715143b539a66459f33bcbb7ee7f72fbbea0fa9f7e3b73c6335d55358982b1b9348105002965e076e3da374d4471675c958ba4a30bb77d73c5c2c0cad561cd31981c830a971d465912ebc6464dff3dff8f970286bf15bd944cf403643ed37349d9ae4c5918e183c4a2315dd59125e6ce50eec533991ac740bea074e4dea58c4ec43337a743ce73a371cec1b36e05b808f5b4ab3e6c9b293a60c541a2d559895d93c8465f688c919fed19abccb1b0261a63faa50deedb656c4b9637c4a7edc7c47273bd7d2f0e875a0eb29af247790e64368076b016a3e1c592f4df5660a560734a8e49a2c7a8c13d95082480e2599625ab420ec773c5b68ab7ad85047bafc3adc3e75d5746521e3c07930d31260bde9f440f5e2812239e843b93a2911f0a128f4f9dd43cda715f77244c2e50a84faa8eb36cbeb9993b55f851432c8a0d338019bf82bb065a07827f062e38118c9dd8f0c28436b4f89de077d4606f5848d1ee45024b65082ac8fc8811b643bc02511746bb95d7381179a3300ca7b23a2e1b5985422a2de05c8fe4e8a39fa76031f1352c7be4a42542f38d99398ee8d233cfc4631ca45411f3e64d433595de354049654dd79458e5f5bc283bc811139bf76444a0fc277a54ccc841f1a27f3e580ad2d1c037ffee45d3b656f4de7533c66144e80f81e28b31de2be02060ca6578e24458a303205c31a0a07a80c2e4db3e619196d9b898709002000c720c11d1a86e649eefd0c69a99eaae72172f022a2f309f59937237a98e136acd3f0e962d7f0aac201c2e848717a6eb14c992a31b3075ab52373bbdd7bf904f9b90b7516625a1fc5af2508657aeb9bdcc178a82159a0afd10be82c0d851f61e543c7c65a4ebb0a7ebe17e85698b1315c9c066d18d91695c6fda692cd5e1f34d85df698ae4609836ae59d4808f11df0b79c6196a0600767292d5ba473beb3c91671c9c8d606cc3d84a8867963affc8efe21a31c18bae76db6d011baf46561a5d7ededd0afb0b5414d07d7641aeca7f8b90b01336166ed4a0074cdf9abcf5b1b6f262d92a2c3816e6c40fdd12a3f7c0f495ab79ff9890fb20623625058d1160f80b39fb507671a19d42a786357a9d0a9b4ec30a37511fa1c10bfd76363ef2882005817c54d15990c35761d69197e5ac185067d4f4561a9ebfb210d72013a31e56f57d2004fc8ab8998554a3ac0815702b53968914c035e3ff9e68484eca11ebc1508219664537625911004b473ce4d70a2ce8f84f388bc8ccf8af7a5e28fb3ba18973db16d6400e36899077c4cf3b6a25b0b3c7e17762a10780f1153758c2957b2126d11aaa5cf8ff319df4dc093f32d26b446e320f8df52ce63b654c3e2eea062f0aed055fc9adeec6c1ed2c9aebcc456d80edb905e6c409ad7141fc4d00806eea41774acbe0aad09d53a9c43c342ff43ce934a5e582f18460df847d331e4e3a701a237447fba28293165355ba0520a50fd78435b9d11f258d2dadb068c5e419b96135927fd8e0dda1b32d07d2f865f42994c879149bde118ad4597f32dc5ff2fa5476e291d301258dc360df6dfa34821422f30eea111f02c8bc2b199f35e145d05fc4908536c8e4165863d29e7ff3f55d08734c29bd5bb53f04bbff391966ee0e0f07a732225b1f6316f259b570098403464021ddd1a4d538648d247030a749a38619384c9dc34e1d3555a4b5480f730ae946539debec0555d25195b05e75d2ea212a2b32e889c8a3d5a5dce1fbfc52c47dab30b1c0ae342fc3c4c1e1e2e0b436e6d2df90837f66054004e1334c61a452fdebe012900011bf31b21ca33283f6dfc3994d9baa2186542a25ed9394d8f92e48913d8af3484707d2eb9fba62dab7ac1411a7e3a697ba561229fd4b8f09c2f13c52e7b651af585e3fe12b4393c76aed160bc0b9ac33d87a2b264eabe563980520c1084b6c431d6b148183690d1a34cc9448376645c0b7585fb78aab6e3bf80c7b39b116b00f0ea671553cc35bace5a4b1a085ab3aa9a0a413427e4021d8746e6ca0d6da2277ba027cb1335ef2b598d8d0165db7e3c3773815652e4ff3c86ff473bf08156296907344a4037dedc299391956136e3dc15028b879c80623530c23cc56b063acf47b818bbd09118115d1680c60eb4b8a2bc1dac542206e2b4ebe47dd14425345b355e857ccd21867998533028e93cd8c6f62e7fd8b7f10bd3e5afe2af9afb3bfcafea29edd111a1b253ff43a023c226619f8bc4739c115e561908b30f67b1d5a3485d4631926742c0059f5dee7556f2d4597aa1bdd7ea39943dd1bc849fa61e7eec093d92aad28b1c6290dc89f604f32aa21c48c51c04bfe834d48df78efce0f269afb5fcf00aa93e2b700858e040cab70c01844144ae66006d32a8a51279519dbc50f78295b86fef4c2934969daa806eea23919a8c54df369d67787166b5ada837f9ca89c4f23993bcf066a91b466dde5fa8eb3e4d76311b6aeb2615e29ecb6a89308b8d9d25b98c7992e4ccfb934725a25553240c4e101e883755bb8296a7dc6c19ad4d84ea060b11a0356c577eb8feb14b03e50fd27ac54b783dec028050d0f80b5110d5fefe0522f9f323c214d3c470668d3760f160dd82ca99fb74cad281da43a02aae6addbd65bc817697f804cc2b490c49f032d252b80e64fa0c9c422065ad9dd5272d7363a5d503b225175052406bbf91a084d01e1e93f3b05339d6c26a3bc52e9f9c9dfc84ec43d6da2e0002b406a29c6555e15af36b61cf0ed0c6f85ed7cc5fa1c72ef484af13d13b04655e9482555e55c71eb55c0dd1d6220f12d981911073f43bfcce9a7e3970a981ec54a79e2ae0f0674a63d3c8a34b781fe9c661f745523ab8f5017fcc3fd98aebde89051e45e3e7048911907842348de2f14b6bfe822180b62ef48f8caf15bb2175684c1b02ad848bd3fb6261c620a2d1af41c58938f5480a9269616a3a00a074570d8a4944b819de466d23ad1a041aeb0fc1537823bec0c510981066ac3eb27c85806e8b9c53171833b30a8b34172e011ea7bb7f2bdd8521e8067743bfd89e898ab8eca15307813da0146b713bd0bbec3455b819c1ab951fb03af80920c324545fe5a370bf06c945d22be44414786c6906960f22cd72b67980bda95d08ecb95281c35a7dd5a8111aa64acc5bb13ef98fa69db881a99c04caaea3ea85f6211823d60dc4c885ad51e791f9875b2b0c2eb1c48c9ce8ba288eb648361d7ff634d15dfdebf0988770e41847b0f19468f3a2ffc66b351431e9055ef5992c4aca18ae2825d2e7cc21398bb2c208b906768878fbdcfdae5e8407e4379e560e557c093c17daa5e08f157222a05fb57cffef8e7c897089291f7e0d0e274c0cae8f9d2496b4b445fed90572cae0e9eaaf8385387b977f56e4144cff5c275f9924819a2d0b6730dcad88352d730381425a986b4e84af7aa2f12dbf15527bb953fc55d6ba494a631885ec9b94d74055bdbbe480beaa868e213d063bae3ce88b024d7cb0f773da43ef945357a75e812f886a92abf435cd401f188644c459549188ea2d5ae4c80b244744e5f9055ff4c06314b628e5dd9e4d0082436481eab52a1a1d6ac4451a175988e4ca8a69bd2a43013419810d50142eb50da9970feb4d6838d40fdf5cd04544a95aa75fbf89c8cbadab0669a7c35eff9e574961e3416b48c635ee849275f21a2186a860f918c23559e0f3d55672acd574b2ef95c5a1180920787d1fcbb596ef0e1dc3d19b0b03807fbd812630fe9983929a0242f834119e69fbdec0c221d1879d8346835631c76bd60c2d0cbd45a329adbaf869e7d08fb5c9c7b07f3a8d45b1271b194c5382256020fb0f9c3aa5484f020b06cf23fe6d14faa2841bfb79b8aea20d9145d56fe11b317736825181cd1996c3fdb9c69c3bf25469fe9af4f4c3ab84660f789bb6ac96c60b51d9a4c272bb02940e608ec10f7896877114518297ee25393ae0ae2cc8f18b643314d5d0546a5141dba1ecaa9ee3c0286288a9b155369a0b36b797a6b05d3347aff59bc6f69ae0bb571c0b4ab0f6a47b3304e9796362ef5f90dd606df41354d27a85d7c90a909fd3855d2c24087de640f9c145ead76759510c61dc4f1eac3055d8762132a61535e3a5010d625c4161a01ccc55cc854d16cd0e5ce9efa7461642cc3a9188eb3a6a99a09e20da8b8998f8ab3c0216380b6ee856e369a4fee5756ad780a8fc64f03b07659a8659e6de0cc607932a8d033e5c1fd04dfee7328021bacfc273fe1b4d96688948789cd366c6029d7f03e681f640b9814c15187fc8846f2cc3fc241dad4ae826dd3115fc810aba6f8a88146ae91bab0f079d3b56efaf1c3344fe7cde4dd80219cce8b6e1074e30bd6fbc4cb6d67bfcfa6b5c7957ade5c03673834ffe4934ce08000e355ff5d3e04ecbc90b5387f9eb5805cc3bc5e12b8bcb4226e0b86822324b145a48d10f76c231c02d7576864dbdd19ea59b35612dd10b8068604ee4a9257e828099c7fb0dbe8f7ea0df9790cf884f172903c91a3781559950d961609be6f9c6b91c175b692bf35c227a551b57836ad7d1f5efe087f265d1e6e35360b109f24a6cfee2a317cf72bddcbc940d65e0b26121c6569da3db0e121333bb0355dbb872e404150e73d40801d9df081022e88ae667b6915744839c11fbc7daa100227d05a59a2643597602fded7fb2d4d020427c9a9a36b9b18adbd04b2514ccb872e8921aa4b1c52c07fcad5672e2935dd886c6b71edc6e0fd00c5c410019b61b7a3b34f098d6b3e0afb3da02f01b93bc08783db14ec98f9e58d77a006be9c81d5dfaf1da516ef2811bee5da9d597005a7a5c7a9f72d2dedfefcee867006c6f35ad06adc0e0312a59d61ee23569d3b225dafd0867342efb202ccc348bd0ba4772cc6fd21e34baa5db31aedc61fa817b53e4000c022c6af2f38d205ba092a81c0bcade6c76ed19898adf83fcebbe56a2987ab0c962abd1bfeaf8bfc09439ae6365a35d3c97340eba350b6ff9d8436c0626dcf96c1456eedfb11d3222f46f8e44dabd314a297840a608ec577ead447a78e0f0f56e4297947ed475ae29d78d12a01a7511c7e36449ac42edea2ac10453fd163fca2e6f69f9779d9f02c9183947d446fd52d9133955e304b6d112df17516f9ee8d00fb82e419bfc9a7df3de5d79ba3f4d3cf914ea2345b1e32a4192fc086e61fb7c8b2d15d8a774fc3be9648e823fc60b27bf74006627e303742a400769013eda8dedbbbf0462f40814abb5ac1992ceaa303fbb29838829c99e6796335677963d3d53bc8973d4bd65972df74992ecd400b992015af3608f5d1e583de7de86ce9d82d2d6ed865b89e6063cd51b8dff9e65a50654ee6346f6382ba8d449f090c093d02f97e9d0a15315d7edc68c2dc329567e7fb81ed27bdf8fbdf2371ce01af3b66a031588db0038de6c8c0b3724f82c3646ecffa796190fe6f804b703157061cd044cdb3fe63cfe10efe27376a099bd63c2cff6e974c2d0cfe978f78e9008c2daae22636f6d4289bfc870f6596bd670cdd7e531fcc338513c310e36fb4ccdb10e5bfbe1b78facf079773775b5474ab0a1ced517dab407adfa47cce51ede92e01774110110fa139056b79065d70117fc81c28702f8a5148ef83d8e8b4679d93cf1a224d37df056ba8d30e4f93572b5aa3396194667518a49e37d3ebce093b92d430f884b0162cb088c94a3742480e61c03c12ed66adbcbc39212363645e9e7af0da4f4295e7dc9a665de23f58d970c2ab104729529e05be6c649f4b38379ff7f9ebe384e1464411ba0e1e417ece077352b0a98eca3516d550550b1a38ce9c844db2e00a12d61b733b4defa84c1b1a1c9ef4627a69d1789507d1fdcb21f6efd04446f6f564d441c59db19a690b44355cf63b37ffe0f36b77dcee95be7e7f655b15e6700f7377145b2cf2995a19a09bb3f8c63b90891e8937f7d0c42731e956187d9c30404c5483182083758b93855a27814e3c239d6bf9c4a0570cc5b0aaf7f6f83925443ade8699838cef65f47487ce8f8096674f5624acb14bddc92d257d64962a5f4184edd1757d9a5d388f76ea2424f493f62b35cabda54101e258ec4468eaf9e02a602e7e5363e0cd8f828603c332af8df2cbd3bb203dd08ee23aca29c5e606d5515e70bb11929143274869ebb136ba0e2d26dc8668d32072e78ba1ffaf441bd00f24fe70a3ecc6f00d073222b3aa893e4d762d283ef179cd442c0e5d218eb788b869c6f32001184c51aa38aa4248677c54404201c607efd542d88627dfd79387b9b57d2dfcb3f766beaf20fea7a9882698af8885a0f5979feb7ddc7b172d36d0df77a7a23d5011c589465d8e624a5918b7601c961c9c8426e2f73163dbf4b9e1f967eb96e9c428dee93ec961c547222cd1022c241c8be2cde2e65a597784a0847f2a1b9804cc2168c687ae0d26b5e55800bf5d95ecc0a00e03e5cd4b415be98885705eec1663f22e4ae3154e2c38d3f94a2f021efe0d9c30dbd9434870f566df1d5afe4159a8c7efadad4eb3068c7182c5197609c8edf7a25105ae0f1524d4dd846ce6674ff051e2ce336c5f15d8473db1de64b99298dc93ede24352acf80cf59438644523879b311c2bd0d34b4245b38b1d069b9d8689d522e4f86244c60ca570214d7f72dd0aa7017fb7a3ae6e857d4a2899fba1f4017d584bcb6eabc74ac49ec2287e5d757bdb3e19a5a8f9d368f82a1e57619741f21aaaf48d425285a074c449d456f5ccdb720d2c9baec9a9ba1a36d5ded75c0661835b3b9521e7b9bb942cf54a7d23806120f40cb1d621eb2183e6a33af99deeb35bfddd51f3da579f5d8ab208a21e743314bab8d9262a4f70b83dc2ddc57d64321f00ca845c1e59b46046cb640da37762e2da4cb0241ad43103ba6f6e845a208e59361db4ba730d9973b86f8662a0ea877acfc750df46cedba07d11a51dca803c7ad843bd034a847a7985d967d40c0c040604030600809eb957a7b60d7e9cdd25fc6a6d15d29975ab95f20804c828c9debbadedbda59452cae1078d082d083e982c79736e8cb46209ca0c4840e4a4e121e6dc58898794374a2e6c45f1293937668252264c8fa5394e61e7c64d29a0ca7ce860d222c39b73e3280f5a80f5b4a48615ab37e7c63e13d0126de6bcbd7050b8652f7edf25a77bf5d6effded1b2626e6ca54942318580072ee8bd4ab612a870ba2c070eeab04015a22ce94396fe1bc754a766eeb653960b7d53ac0570e0e404b54df35cedb4a66d4658f9cb8b8f959e9724111323ece6d91be98882ce941266cccd5b9add257385f7e6501e5a0a425aadbb0281a308c1ca9418773ce8d272e64e0718483c9b92b520868f8d2e2c2ab86185ae7ae4db407375736864c4125113977657a525375e30a07961862ce5d7d2b5e8cd9088b3187878b38e7ae516f7f74d72b176f8e2c11b5da02f6d653a02768d9f5400dc2a7750e5822ca84a846bda5f1f6ea0530a6313016363a8004363546ac6a851dcead9f9249616903c313981f34acd7180cb01b4d64a872eedd6fa07743962a2caad0509d443968c170ca02258b9c538ca33716df68308acd57cc6485aa571f79488a01007f3d63a6a6b1dff99cdd83c45c95a6eaafdf2c363cc6389fb962d5cb22dad4a1377e33873667681d2cadc345d7a27411bdeb96af0e6ad1555f1dcca2674a7a579fd140e11661245bf639a771fa9c6f11f565bf80082360244a44c11e2361a51914ecabeb243d13a2c52bbdf18b561f7a8f75b5b21032d2a06dad5432245a66cd6429891ab71b327a51a7a56eb9da0d89782c6c4b7e537500d3c23628fc91fd867022fd792d21fd66621931aa2a51b2d75e6b99465a0f1687d6ee23096287068146d3283fbc9e7a0df2d7afb82d9365b24474e816b9e3705adb5a297edc2363e401d31e1045ad7138cbd344574badcedaec41549a060d66132ad5c0ba9d59097b9847f416b1eb0b60cf9e7529607d733e17d0a58063903aaf6ec5596bac8043e9bc391c83d4494b0fae91d286def87daa53321d5ef84e2a674c0ad2495dfc8173022d49117de8ea348a8f2757639693baae1c581d384ba06c083b5f18402b8c2f0cab7f359e6569bf6a2a78efbd2d7830ce18678cefbdb76a518abe96f6f4e60ce60ce68c31c6b5d6fa82d5daa5a6427ca03d757befbdd65a6b9f44d2da5a6bad6e7e17042ff8f3e318fcb8836006fd52e991520d6caff51674ea22d01355050813004371461bc91285dd95d99f12b951072830a812803b0643463ad6c01abd3a1217863ee26a2db4d7c8eef0bd1883b584ad6d2330e7306731e759ceb49cc79cc99ccbcc43a537ad0ab1740a1520d013a0e8d83bb0bb323b0a9b004af463c2a8037ae282bafba063408b6c54a8baa8dbb5fa25dd55b73e8212519a735824a90633a69146298d62f229cd292d7a9a338016e1e815e909fc3437a21c844ef3139508749a2ba01265a7f9494f60a7515ad2663aea24ad1c845b7c9aef72976f6f49ab22ad956273e6bb2ce92e7fe3a8ad25eacde96a0922945a4a69ad23c63a22f6a5abeb96d36f92e27a8d8cd6d6686cf5f0580bb24414ebae5f2b2d6f57e05ae38cd586561a7af31592813a66248aa7801dcc3a15aa0f353f7a270b54d0f5ae97191cbd133bd6e52d5728675da16a6d261b69d0cebfaf572be078fa644e19fd3dbdea15f9ab444f5c1fd5a07a456145c4a9eebb5b34002c6ebc38f5ce4a86c20918a234f97a673dab6975258a52efac59bdde596d1c254a3dcb747b481fb27480f5ceea76c966659585d9df15aa7e5ab2eab5c9a94679e01645dd220ea63cf9ceea218c60a36e113dafd215aa286e113d2bd562ad62cd3fcc355a9cc14b4bd051d8300c75e834ca871f5225a1f261013efcf16118566b44da7e82843deea99516d70d63b846f5b1e28a31c65b9ad67b63b859b474f550062bbabace8b2eba7a2d55d1d5cb47165dfdc353abae36b4a4b59a97ae5a4d9e4260a4b40a04bbbb2976e8ed3e84e206ed48d84c7190199da2eaebdd36eb4435cb074f13271a77afaef238d13c54ba7badb5a013bdc9a796c7094f95c6d9eed5384f69ca43a5696829106bf56cac51d51eb9f65e1cece5a9d2389e297d75949c8921485e8c71ce190cc350146733da9007f67794479e805ddb3a2011445e1a8a241d411ca9b183f6e2dcbdba4d8e23a9adaef6b419475aedc519a4c2436575b4baf855875a8dd6ee56d87ac248ad091edc176b89ea1ed86ad3687da4b5fa687324a87f82eaa592a7a504c65aabcdee56d86a7b637d8ac71d913641a0b4e9f1144a1b1c1f3e85d266c6974fa1b4e95960ac36b49ef83cb5ea72f4d5a9c9029398c45f02f96046ba9dc788b61508a57fabf5bf4ab9aaf4d7ef454135b84eb18e036cc0f2bfd68bce741c604315f55147218abefa00dbfbac534173285230d1b31f7a531f4f91d0afa750618928584150e73bf4ecb499a780268aa0a89b006d4745d1435187cbd507799e34d6eda1a740c6d90a1b804a84ca32bb318da82badc411660c58848ed7ad9603cf2c87ae4ebbf7de1914aee2957bef0dc38c33167a8be012bdc5d28cde22b5beafb5b32f7fc40fa8f5bf964ff416f37d5c3fd838e8a96e45017e14733fd2c89ad3c93f92e409b9ccf8deb2b495ce6acdcd74e8d96c56629ad6d7abea7c3ed095403e1ec9ba71d5e1325873e28f2229ce667b452547da8cd26b2bfe1a3ea975d6c381cc58ae750a0473c5f6d61b829e05638c6bfd5a97dc2041904b915aab954ca7c6213a0b31287a7d4c29f817cf280d310e970599524aedcce630152949857288cf362a949d82ba4a299d51edbd72ec95ba380c6734190cef1545ad7138f7ddae7412eac79608fc20e7c71fe65cebfd30e7fa41fe9a6b0dae4ff040729ab2e4830d95767a8b488c496324860e1a5efcd05ef4db26042c0037c478d30b50f24c9cfe1dc73b8edb7171474897dff1a192b7e3543512d4c46317733c76ed3b21381e03b1f2d84b943c6c84cb42e2638918951730f01904dc90e389cb9117bd169c362053dee604287936d7a0e46910437c0fc249943c10e2cd71b79b07dd7ce8e6e24dff8dc8df6e98f3e6066ffa004a9e9941c9c32e54f26e4638338ff35dc9c389b6e7117b1e1e0fe2c1cea3d4ebf5609c3c57aae06c35ad9141caebe4e152e6c21815af321727dd7a1efc8365c46f121d08fda6554a7e28997abc0486c74bcc3c66c2e43166d2e5710e373c7601943c6c4dd374bae99605f010d2e34cd98b2132d0a1379d9a34eb4df04d336fbac7f0a6a368c19b2ede220e7c84317d690961b2c3692a71e14d0f40c9337f0028793c5edff314e77b3ca8e4f538a1f95bfb5be6af7b1089a14b64e9af792b947b7d849d7b89c4f9bb73ab946bf3cbe3bbee681afb7bc9fcae575eb16e4e8394e5d861aa0990a3f30ed981cc963132c67a38b7feebf41600e98c0b409ec67c386eed69a085262ff812c3911a48ce14d45cbd008d989316acb94f2ffc2dcbdf1c839277c361f8c985c718bbaecaf310b9e1793c57f2788e30bd70438585a31db29c5bbfcd16f636176fd107ccec1842e497c505acd346c4c7dbdc4b9e2dc4ef38c9f91d1f51f27644d3fc79d3349d5a78d3c9ebcd216bde741125cfd4af1da7879479ed40fe53f2eecff71fb0bcf90fee53f23e5819f235f16bb4ccd76a35edb5da15395fdba9e131350fa1e622d46ecd690d4b99afe9d4b0929a78d6bc2e5922fab59a53f11689f84a1a9b8a210684e9ac353dd101030d598ad8a8495d7991e1abe1668573531f094dbeaea0b4b09981aae6b4561bfa5d0363917aec21705357e8af8b567e7fafccf05690fc7510a6699a6e3aaee49918bcb692f4da7b4a9ebe95bc9a8e539f0ed7975fbae0cbb3fcd2495822d34b1f32e2e1cb0e4a0fc1125d2f5d845a59fa089cbe2ccbd22b8eb2eef8b2327d09834fc9972037b6bece52fb4aa79b967d597b5f5a65b978addd56f23416f3d8c51b78c020bef6fb9a7f2879b515467a68a9a14bc9961224270ef3613b54e1d10298b11acefba4f4d7754ade75b38a913779656d7a2824d985ec224b9224b7c80cc8af271d932e3a09f2a46b276d9eb4baf2e413214f924fa49ebcb2f1e4159927dd2c79a4f9e3387ad038fa902daa4ce3383ab5f2e39420f337896da283124fc3fca5325584aaf7d7714ea5f4d777607f7d884acd5fab2651deaa8a86bf5e96bc8b6ffc367a8cbd3c76fff2d8459fd663274b1e2e79a311019ee6b4c6d39c26963f739c25c2603f73dfcd7cc8cedcc8888b9f752035f3106ed12cec673058e02ad371c58613937376844b09c80c5c74cc4873cee8d5959f417d3dc65082feba868251240caee4cdc8c09297993ea7a2863a25ddac068d5be8e0163a1bfcd6c1483a3a3a3a3a606f5d074b47ebadebf4aedeead039bf758e7eebecf8ad73550234565f3d222e543939b78e16ab2e6facdce0f0c5e4dc3a586ec8414a892bcb776e1db135695e403635c705d5b9759a44e0a5cd0c37aa98d9f23ab74e946f8bd4961e5bc43439b74e181d66cedbad6f9826d365afbdde82bd75ba7598d85bd758b6e8b2b96cb4dea2a26b8edffac86f9de4b76ea1654a8aca0e2f39589d5b3b65bd70c31c222c60c9e1dcbac71cae2528499cc86c9d5b33bd9d7a8344eb07123152e7d64d1fc81155b68255d29329e7d66f6eb461d1d5c3ef4bcbb9b5155ac2ca9cb7bba655d3faeabbb6a5a5a6e677cdc9ef9a0cbf6b576fbd9655d3aa79d5c0a4be3cb921568686d79473d76a50e99023c46a07a832e7ae294dd130838e346e9e1c39778da906333e283441ca689cbbb622062670e024ddf0262becdcb514516062ac318325e6ebdcb51a68891ae6bcdde66d32cddb649a5e26d85b37b16cd1f559a1ea3e53ebad9b5dc8dfe69159ab7e9b2efc36af6a1c9951c3a969ebc9b94da41b5173c814d18185eadca6d22c87df171a3a78f5a8ce6d32a550c6e205178d252e849cdb6ca261079b2a45b2b4093bb719f5d5260d12314f4dd09cdbbc35ca5ba3f4df259c8bc30a55dfa5188e92cd5b2fb7c894457e974d7e974e6559fecab0930b961e5860006381e4dc25125603a4abc90c32358e9cbb540a2bb2552606468ccbccb94b267a068b0725a5155c00e7c7b9cba636b4449b396fc932b28c2c23cbde3ab9e62d29e737b944d2f09b2449f21781ae1e584e5a76c8e2e3dc24d2882d3856472f4401513a37a924001d5f36f4d042ea880be7269940a0c482960cb030605e9c9b7463a6cb8f16689a90f0383719f5360a0b489cbc8ce5803a37e99b424b4c99f376cc1ac3de8e366b1cbd46b0b73e62d9220b66c72d392399df63eff748f57bbc2ab281032acc922f3b769c7bcc81e486992a6f5ac0e8e1dca3d20a1a3dbcc2cc80d4e20992738f3ebe6ce9fd58aaf1c5eadc230f568e8ca13164ca8ad8b9472f698c2c65619ae1abc9b9479f119a111aee372d8a16458ba2d15ed068341a8d46fbd916cc6c0b663dbf679669669966b31efe3d3b5a9acd66b39f15adb8c5ea48a2452a7beb624f3cdae0b7f8f45b9cfa2d8ae2ef024e52c8a2b49c2993e3dc22d28a223dfc70c1050b48326c9c5b549a3ab3c6aa052b2a58e716995440464c0c5ea670497939b7d8f4564a8f2138baaab47ae716a35080c16424490e921d5de7167d6768893373de86642159b8c317747e872d7e87337e87307e876118fe4eac59d28206264e64443977889480177c3576a8e2c2c3c9b943a59e245da4fcd040018972ee900a10b02730d6f000c605e70e9b3e3c9933c3c816234d66387718f54e9a6248a3e45ba3a5e4dca12f0b9805dce016700b0896d17e8347200882e04f481692c7dfd9293b65a79c73aeda192a1fa1f99d9df2150c3078cc79c18289d396f30d0f1632d0280325ecdcb9e96d0b1ce76d6df1d53a86539170d85b8cb117067beb18cb16d51df8373e42f21b2f0df98d9d88fcc65711b07286ec45342c8164ce55304dbff08909c74fe4aa61cb8c0ad212ad86ac712b29ea242998185bce9c2b800c45ca4c32e7ea4f47afecc4391f2ce758a3d2e64a9339477d4872ae4e9cf3d1e11c75a684957dc06a616927eb852c5b9b21513c55ba946e447a18e3eb01d13bc710b6d210317e0029e33b891cf1c80ae1b3e6f459d33b471f5ba94f183119563bbe7ce708e2a9ca15329f35206c3ebd73fcb195fed429323e29a6730c51cb925c40fad12184f87c40c1beea70cea7fa0f8cf30746efcc399feabd7304b29502f1b8393a73ee002ce88083ef1c7d9a900971d25f73ae3a5dd1423a71ce67cd39fe58f1c10444e61c95e21341e6dccdf104e91c61a5948c71f22a038c1cb221289ca367cea95079c09095733e2bce0c7a9a9e2b583ae9798e189039a70275c242990a3f30ceaca3f6009bc4409773d7e9743a5dc637a8f6224cd889733e2f38359943d3d52ec1e8af5b9a739873982b26b3d5fad5d51b667763b544f5eb955a9b57bccdd4ee154fa9e79c73de275813263618dac08409a8a63037739cdb99046cc51cf62c8ae1d344517415664933cfbbc6a825a88739475dcced9aab316a89ec21a59e735893727bc55312849f0b73fdab5a8219a417ab19672c6c0032112ac776a31add68e5e5d5086bb1678c45608cbdfacd3a7cefade6e301b9c625dacc88ae5ea3e8ea33ab71d6a28bd79f60dfa4659eccd32f3f66acdb24187144d7dcb846531f7100e268ea2b1e63e06b2ef75134f59cb33292a5198219e36b6ba534934efd38236b6ef682a6ee53a906b4d67befc5f482a2144da3a56975db83c7515403eab9f71ab5ba1cd134324f7a5c330369039b71c42ea577c65887c328955a31959e00d97841bdf54a56abd5e4e989eab8a4565e4b5fe9899c010b5dabdf0ad0354f5bce889141840acc49a950a5276c062c343d513360a1ab8fb38b67745667e26c667338036be64ee1ad67b1d91eebf1c31320584fed0e8aa6581a3fd19b6645d19b5649d15be4b9d25cbacefc161d4b63d1e5ef7acd0a3d6d5a1a77492133562cd8aa4b6a0c638c291572ace3001bb4ec8e6e6c8932896be9141b300a8023b03b9ba3140441d05e2ada5dad2482aeefa0a722d5614c79c474f5a07ba36602094e101f3bd689507a63fbb888e9b44eab532f79f649b188aed6fe98dbb50c3b1848da2a07b6f737084fc16db9da5915770853eb22d8229c15ba602a07d5823d1eb2bb2b5e1fe2401058091cd8116489e8d3cfba9a985bf2b03dc16f1590deea4660779583fa55c6d12d1176b13aceeeaad7c6587b6216b896e162685abfb0781b041684443cad8fc8d759123d8e95c45e7d575de3ca2fdaba4e15ddb2a3ae73a54d2aba567711d098691a5343101d74c8b9ddbd7e8b2897246532b4d0f0b1e4b462062a365ba0cefc48c3e2acae714af4367a8a6f4e04bba34edf3eb5ea8a7ad188b6baa1ece222093275675c2a4930121dda28a3755e8851b9d83229183f8d5da0f2e540660b4ed2a10d4ba6755eb0417f803ad2a215180da455111f49d668d150487b6ca11169fbe972c5a6197d5a1445abb575a5438b54ec101d6ae969f2a9d55617dcc21699acad35326cc9a9557468e38bd67901062d5a8dd12515183a8ac64f1a7c6ab5650d8ec40e67d33f412abac800c5c8ddc87d22ce88cce8eae7f5f7a2673dc63da6642f3ed3e3ffd07bd1e9abd71694038c444b54a7315e3c5d81831aa82b393e186a1c393f80a21a5316c54248d6b9ab526d9a4106c6922353ae66a038bc204b142c1e33d2f468972f3e2c73be085972ee9a65e2054d569c10e9214bd8b9eb1636d452d126bae645ea94fa53c751a3a73e445550ea2350fc3404464f02e8a1534f01851158a2ebe3e98fa78e82fa8ebaa6bd5ecf066593d5ebf566d01c3e671749da8ddf419f7def486ca3cf8e02e933cef40aeb33d5b2d24a92c53ef8a66c8a707dae4735a9c777c5b1f4d92b1393cfb7dc1404e7bb4255df10aecff90aeb33889c7358150b51d68bf3d91ef9f8b648493fbe2d8e2526ac2f9edfb64710a8cfd9ad6fc767b755d9627dd6b2bf169f2d18906f1b26f6d9dede8bcff7e86dbe2fd24dfa9cefd2e7245a06f81d8740c9db4192e56d369bcd7739b7d9925cbd8db4a9b17908964884db5c049bcda9cde636174e6ebcfca45cb8387fd810464e922d16645f36dfa1667edb7ee7e777488cf96de277c67ec7f18e8b51507e0707a8df916ac1efecf890d497df71104ade8e08118e1321c22992322fc21f50f244e49c6edbeff7cfe71ce77247607cee489bcf19b1fa9c3ba0e4e53e2879b61151aafc085751f24688b7074af3403b0e04647320a0154cbeecb04549ce0e5f309c401f00bb42038e0c38c2da0045c1e18112f0dbf6b69fb74101f2369b0dca95b745a1f136f7a0e4d97c67676767c71b50f27678250f684a9437dd344d0f22618944b8e943545cbc7933d198a68b608e304dd30472d374d3344dd3eb55c53275a6699aa6699a2615afd7b9ce53943c9d98cbe572396740c9cb39951def7e963c1781808080fc35f81153b87e84a3287923767676767676767676767c01256f4744d98b7005943c11a28dcddb3c0125cfd641c933b16c3c7e4cf6187b50113eb1763c46f3218840c1a8d863ac228f91942cd10e8e1db509eb710d72802b4b04e4d82b9656f57afc26873566b4c6a492cead1f339123c683982419a69cbd206ce2b0b043111f6bb0921b1949985cb93202e7dcd4d7ab518f81ac1e080808c8392879400e72f03b2eee6009f91d3f51f2766c369bcd66b3d96c364740c9b3f9ae91690edd1a058bc69b2e9a58496fba51c9338b4a1ef6ff91d2e57f9ca8e4fdf8f8f8f8f8f8f878d6fb300dbde9072879a618124979e1b19b2879d80d50f27c8c42dca0f5217c83921742c8d56b9ed7dadd12d95c7b90f6217dc3d46b3da6775c6b536b1d385b11cc19772973058915140c6c95dadadadada0a4311ecea7215294008c15c3e207b08823ab62e8fac21f2fad5d9b42dc4199bd2d3d6d3565757176e6bcb09d75a6bad15c536ca14a43aab08051450e84ccb7c65434c5d74f51830fe82bbbaba30be557430e842c418cc983e6db9e8a27fc11863105f01e102d7c88ddce84b66e1669a31a614535cf1bdf68b1c8cbbbaba5280d081d1cd185f298bc12b8e2f488a5b6ceec520f81464d36e9492b4c6b991095a04ba1da183a7238438d385bbf495a1ab577ba33c6d3d6d3de5e8d1d193a3c7851e1c3d377a6cf4d4e851eaa1d1d342cf8c9ea41e193d317a60f420f5bc00d1d5e3a2a785aede73d4c3420f8b9e157a7a73e4dce2e8ea373802c018c9ed0da6b71e6e3cdcca6e6e6e3bdcdadcd8dcd60cdd6b800d4234b504a5e5ea49d6b1d2d35ad5171908ed3297eb63aabd4a8b7402902780a359d9eb0eff186a0cf4c411ae5714b5b634cba906209c381352140c94b4b4b4b4b4b8b8b8c0281004adb22af7deab775e469cd1c45918e2d9571cb4b8b8b870546b4a581818d6b3729d5c5efcb8b8b87ae8fcb87e5ef7de5bb57e6af08f8bd65a2bfd5de1e2e28a32c24a6aaad695d6947026d24632ea5e926be46a81c675c7686517d872dcd2dcc86e686e63b73337b19b995b991b995bd86dcc4dcc2dcc0deca6c30dccedcbed77fbba79b975b979ddba6e5c6e5cb72db7ad9b961b46ddb2607ad3ba61b95db959b965ddaadca8dca638905a4204c37b2f78c799e6a277cec5eff511e071659181d0ae87efd55adf8ab5d65a5f8d20c9581d04154af1438b50f8892a2e0974675d871279c0ee2c0ab7a8ea7635a92659a25d65fc09faf8aab8453b0f9080c369bdbbdae17062097623c04355690708293d0c7217dc91184bde0f8ad33b7731e721ac2285915de35fce74be713a6a85dca9d4206226ce421104719e3d582b14d04fa69a8b0b38dc0f082dbd3ba877a8003ba19d7b9bcbe9e4723b5a34c28f6ef9514a290a151310294e73d118e31144516b1ccebd0ef9bc96b2328b9106d5a74236052d0a419ce87af7a1ae1e1d2d1d2dd5a3113c70916c3dca3509cba0a3b77ca268ad16c98b711eb4b33bbf41eea28b7e64f45a771e848ba1b16882585e24529dd18f1115a3664898000817b09eb191911bb99191d6888ba1a98f346702febdbbf45a4be9dd699dd9ee87ee7e9c9e20ebdddd0e3ab07dd1203b85ba58a4c20a59af3174c90b29171529b01b5d19d4c6d038130505c5d1db66b30ee21aea682545f136b1e18c0b77f8de203d04e292f4bda0484f84e35596118a8a8207f1ec73b82b64e9ee0a8962034abc076668a3a12b64bdf6c0d8fa369b2dc74dca2dca0dcaedeaf6e466757372abba35b951dda66ebe5b0e3726b7253725b724372910371c6e51bafa0dc9ed88ae7e337283ba15b93ddd88dc9cc617892e298638dc8688b71b6e426e416e406e36dc5e706bbafdd0817645abd2550c2905ef059331d500db6c3bb67b2f48d2a0589c8f8072efbd57a4b3149eda9d978b73ce60e8240cc3300cc3522b34cd843af41083a178af19d16b566963ce5924b5ad04db386612c4b6191749e632bbcea0e3acc360ee82e168062c455c9ab3297b71c6172b450aab31e7662ffe68c1cf8fdf3ece99c83223c5aa44531c6da5348b3338d3a5a8324a215605ce665dc4510c694122a96d25d86ea609bceb216d766734b12a3257ec238d56f2421f4912631cba4edff2ea5288555d7bc72c2425618c1883189335b2a6c9fb538482f6e482d000da631d8a269668fc50348e3a1428c6d18baebe71968846cb898f3fe8943fda6c3c64904d24f56da77c000936db079dd2041e7691529b88693bf54bd22c1b70bb9526f0681ef6d45ab338ab8123bd3a454e85dd893a146774755c2936b3ce2c71250d50a044614da15547da5d9ea2edce5e6bad1dca4b4830120cf923c190602e94a2a28e40d1235134ea080cac82a9bf1042a860473e9fcf377a19bd8e6636cc9a442691c92ab15236aa3a55272556090e5068bea10a55a3f0180ec361980c93b948aa312a12ad4947231986b35918cec4d0462d7d605ba825b25fb7b99bf45687c25af77bb3cd65246befbd4e8a157441c565e907f094aa8ad5ef12c812e86fd62432cd82d0df2cc8ac49649ad9300b320bc234527aa224291d69945a6b31b6f75a6bad0dc14c29b6d6adb594524a29a59496e468ceb4dc7b6fbdf7de5b7135412510e42181e78661ad59287eaaf7da4ab0d97b71cef7b6503305c1e0a724f0d868570c45b7ba200f22cb9998e9893c2695f45b7bfd5800a2ef53ab5fef9f747e6af5f5e6f6f8e221850a10c8a12fb0d9d719dc17d6978f8a459237449ce2bc215323ba518365c7952b676a9853b37e1b0d7d2db12cbe9818691271062f18863a31cec4e03576c30c0f4a7e3a808d39b17222468e08667c597c419136527c6af51504ccca8befa9d5d78e97f2d4ca4bd6db70f484e836fc6ddb607ebc83559bb0bfba13ec9b00f71b855eaff727e06a6f8e94670209ea9c1773340d2ef95bfcdf62c801d689ef17cb2ad9246a5bb04a33ec12c80a558f444555611b559dac94cffa9a90e36ff4a2a397f137feacb08bd9a9dd89a198db275c303030bf10c05430b09ce46a53af3fe13e06411154e16de8d7ef781d749bab48d8ba758cc7d15e0a5a9d53ada0718ebcada0b7edd2abf380ee480c99a07fb791d197106c0876576bd2584df2d9481d06b723316482b4913a841a238914d30ba0147f7083c3f97218c7f1378ef497e8684914be3aadf9e2a8b5ba0af5e2b05e7195c0b5514b50bf4a5fedcfeeeab5514b54b7f6e2b0bb9aad759cab98ec0787c1e8fde313b8c0637c71e41ce94563d2bed0dbabb08c309d3847579c39b7e21c6d8da5134f81210c7b51ca4e58029cdfa8048d777541ca0cd5cc100004010316400020140a078442915014456922ed1d148010629040744e34948763b124c55110044110a38c310000449031c4186688684701ef02e9e9f10d5928c8ff26b9734e1f04f79c4fa21bef44782b64cff33b2f6b34431263acd1a9cf0aeb41cd1374b78a6dacd518332b5de98173d1dcccff18a7a54196f43e579d8bb618906c0021fea9ba38a1c086fd556f2bf2d9050d1d075559a5579e76f5447f0bd6e87211202510d85d8595881c7c80a9cf069a92c668ccf222a71e9bbcbf403a344f859ac653afd79c2a37306d68952ae84a9cea103090d4819e2475e42caee6426a3240bdd616122147a06f8f162b742e4540dae9cf0a951ae0206f63548e2cd3940fb1a7cc335351f3b3a9b0dcf0d93763ee6a4eea2a574f9138897f8d438c8f7ff22edc651a75cb9bb6d293cf3c220ea9e56eb05738449466c793c48c33b89eda1105ed81bb1e33a5c36c4a2719471d5114ce19626ef769a5bd894d54d819fe8b05d3f510f43dd0149f5da6038dda77a442643ab0b63e51599e9161ce58441867c52513c2b3f7bb16344810dbb61d34195290ff0a7c05dc48d608632fafa76d7055cdc17dd1753aff69aa9979535a61dea977a427894a5710a2622eaeebf1269e240a5a47f2ea109131451ccf1ffe5367a0bbc0a74e8462888149fd9606fa54e0cd81a7e2bead170dafa0bbf65fdd151b543692095e9b3bada3fea9dd4180af400f92fec9e014b979b1ce02c019a78a4d81a6ebf117450246d3e46d4d91462222db459b2821d3ff2d38a851bd4ed6252d21902575414b51a54a7c7079afd43ac95e726375c11e5a26074cd93b76baa5463bd34c99f0a5be06f13d7f9641b299b4b0540fd3d5f306a64824185b587e75f765780bb9f10b5af5a41d2de15ad4bb45bd39cd2b07015f22f936702abbcd3e99799c8980b432e7715c981b376d5954d9de16b2f7089c7b4ce816db5b0867681f271437ce76348080387345dcb81406080b278d16e17b82935b40f0c32a64866e70b824dd6ebe28722686245f67e4804f6826c8e047236e628985b7827fd4c0076121ae477f46a93d6e2fcd286b270091dd88e0f6b06b9e503f01b7514cea2b52a3356069561365b8df78ded3ccdebb3c3832556c3265dd675174c865c54126cac226118c4462cc1c89bb1494bf36ea0fc7c11969937330578fe4075fb4234985809b38c547cc283e2af604e57f9b92c045976e04647eafcc66400617cacc00ced91596e35e78b57da2bab2811a6ca34993c11cd7195a7e471da8dc4f355c0235dba8fcbee36ac235c09ecad96d75bc3ab31466a426550e0d7cd2e114d44591a58bf87e66b6a5d12c2adce5ef6b50807cb8790678d5edeee501b427dac4e5d01fd93045cd6c2ebcd0bd4d7a5ce1159d08bb5025a6619ebb839a2c1598255a546a12d5ddf8a55412d12fb7dfb5ef5ebe6f2fcc62d143aba186ee84747d02ca6f002e10839796a9972214da513cc96d8a8ef20743ef19104db649d8aa46cfd5bb732a75763c156900c24655d942392247878222feddbe5cd4492a468e7e58600646d238600902c6ae3f6194ca4ee348b025826e317611eabb0374f7163aa038095d1c62e0900c7c1eacb408962e14eb14d3d4d986af0e9f5ae7eda2046dd85782ecb2004d1c01212af8486d0bc4615f192d0d190f4ce0dc0d5a45b2af7ab322aa4bd126c4d58f2e205054395d51681a766e069a12a9004903215614f227f740c11152ac9f9a54950ae162a990b467095abe984f0c45a1d3a904ced3ea25c85e1d076d587630a090096e0785e4ada6a63a6d30307b3986ea8237b4409cc7a869005bc3f18876e8b6099065d2a32bff2c0993a4938c188901c3655b4ab97a3e6cbbd596533a859cb90a283f4c85959b856f6fa1683721e3cd4c0b2727b30fc4611d952815ddb7272b351990a9e1da47219bc3a71043be0b787142581e7837420bac917928fc082c0a968c0588a048ef610d1b8437a9c139e5fd04ecf810a2c990baed356e8ac0b532300cf46c83eafd85a31a1d9018e8b286eb4de7a286f41664997faadae4e246620b6293bc0b29820723861972b88a4d71081d209bd183749a6ca0a0fb035ce99beec0b0c56a56c974a49dc25e58ef28908996b72f783a72da51b76b13a454666d829b5a7ddf1447dfa26f9fd9eec236e9518f8a8e646daa25a8d6a34de7458418a45b2373a36ada75d4cc028eaa2786ddea03776d7d0234d5f48ba5e978ac7f68076967733ccec9ac03bae1917267a9992d530734e3fb0172b242493e3d0a97ede939be4ccd985a74e7e96a2e78121dd4f4132271b9f27c88bb758312218532a6a45a5af865d8d0cbabac64c0f200eb3204e2cabd3e53b0b52b7d331c5860ed66fa8640d7ee748c41dd75107e29dd162ed220f8934be8d15245c42e882fb05d85305938de2077cb0e54670f647cd10bf606c98d0b35d0de93ec28fd5202823706cd3bc51fc9d00b6d13d7d94f6991ab77cd6d085d724059bca53c9c96288a65a6662edb3053e85963b3f8644a0564801814b8920a2470be15bf651c04c967c9cbedc0c03385c1550526bbd37691bb57aeef35096c1a4efbb7b29635d56bd9f86a746eb70f0640ebf17902f9508d8c40be744b7939a80bdf38c0507189344cedd299f4299d6e47946c5943bcd46c6c8e96a27d673a92feb743fc26cccc89ec415cc0857cb3aed15cf3d2868716f13a6268176ac540fa5b61c659bda78b7e4b192b57e8ce1a6e8ea4cc66c1790cc2e26ec21577411ef4b6452d4b3d33e31a3100c3be2f04ec19f034d005f9237c0cc80f3029925ab0b8684d5555f5be48876258bd6825235c18d2fce326c6c1784c0cc05a6b674af317e86f3dba8030860608f33607a08316e67a2d689622230719577369d94cd12d111b5f3c0c69569c1722dc3a0ecbaedbac7d1b4ae0dc958c62f28aad554920536ae125189bfb908538dfc1979abc495c2bb5485b7457c34b740023c22dda52b160f8534a7ad78e6a013df659838866803492a3ddb419876fbd6ca6c22b018bb11099b2309a8ceb187f0ade8497cef6ca94f467012d885ffdeafdc4e545ce2447dc8558743fdceb7693032ac7f4b3d6d8ad96e6de6ac91e7dbd61ca6a9e27bcfa55d26e6f7b7f0d113406605f37a29269b3433cd344c611f75d5e3ff5e14c44416cd106d7cdcfbdb3eaf1dc0c1c38c08e80196b817e41b9907f6bc1ff55a8f66380e86b2f9314da79839182fe0befa772d52b9710e04a32f846538fe9a76e836ceae18d224b41ab02262ee97e1e87c49f12bdd6e7228811b6046cd2e2796a8f748d8f91b9f13143512ad78cf3d5b969ce1e98c440ffb5fc26c2a627d08f00b32b0b34866d164d97e8614d805e7579dbe060a76496ac49d30a1b68179e05868a8a7022c4e63740d22b9564d5f0ac3f845a78fe947a20374e71b683433e960ef70d14a14dea7088a6f86f5430cefc7b307881e1576d40c765272b293f914a872492f56d239b9f18f39a4faf2973fbb98420777db576d778751baf4f58445be926862d826c27523aaf904a647d9112cacb90cf34f43165edae6afff410a825ffd6a7ecce85d5e1960ced819f6e7d0fd7e3aac42c89038a2090bfc95a6b5872ddb2e7f9bf68b71b81c1a7651e225361f0fccc5308f2755b0a38b0c5ef2ed8c96b98abf2eba8e5b36510481f96c1824660b83f5c6eedff13464422fd8e72ecf5c344f42ff07e3661d91eeddec7f641ade2bb2f41d74a522d64ad0a085c6a6d8283d55e414f9cbafbde423728491098938c1c39f48924c3508a5e37bc5bc400b65960b521580f00e920d18a0a1c3cf2bda02f58edf01ec80eba2be54bf1ad9ff3bfc2e1234733c45ecdcc6381b250a879d768296c2ff31d40cded6c9a163e35184174df29cd41615bf3c8f0321fc73f8d7c05c5d93e062443c2ac470aaddcefed66906a31e6fa571c465a86a1982c1238efc183a547ad5db8c4027473cb12dcbe89abec70029a8518ce90f672dc7062e3e6f56b6c2ce681bb5e58e5df0c397e4b1c995ec18cf9f768b6e53261fa94f97715602bc0639822c4795d076bb957327b17de77ec7a2b5c0c2b87c6852d36f519009d72ea3ebbdb0aacbaf98a37103c3882dd44f1c2718a4274bfe7f9c9d1a2a34162adb6b43e2d5fa62d03b8e712f2b3f889112ff7371e686867c561c548b4de07715c6f9814f552cd6a0943c364a37eb0d75a4cb8b5e0c17f2345cbcc7325692eca54c085059665b05c0603cbf80063b1f3d9c8d03c990480f062e5bc8a7aac5371867f445580b10b1b558973d1edb05e8948515956854c9de6266a6bda5fca4a3f9959dd804efbd6215acb918a9cac9d21a4194fbc2dac38c821501597304ef0c0394841a67dc257abd43a6d4ad108c01eab5c7efdfecb03711ed5328b7534b32c621db671cc42d15fcb2cf1492bbcb03190c6b8716dd1033dd2bc8765dcd86fafd15b1077e20a4319462310e0799059b6faeec8bfdf6088b39f6105602f3a9f3eabb6d14b8a2cc31ab7bb4c1a6e799dbee7bf98b49217a6068675b0725cc0c1474542d27411b02ae17b1f8e28ff979473eba20cc82c727c979b2b5d78c1d51fdeff6e0e46d218251599c15899b10f3994c6cbca8e4302a324e1f8e69d48ef16c581a0de0f0463a27bc84e871d5374d6eec8b816629c258026a6f22fc52322e1e41208d9dccfbde60fb9a379e1adbdd36f57b88cee6042e07f9dd53006a1af5310fc5c099f886a50238c61e287df30bfc432ecad37fbb113b2eb9936fc6703f8d8e084dda90023b87685aa3dae550cdad3375040ecc19ec69c46fdf929e63e05367443f3bceeed21120389a1a2a79e853d52b06fa196b0153165a20d363ae4fb6a0a38cb6f655ed4b34420fc7cbff100ba8c9ed865f32c930f37b2c14c12ad42f166443658e79824c6e3dc8c4d6220c00d9bd0c8e22f7b7916654c2b1f528122f83cc1bbc3030b17e9c7a0e432f9e06672a6c48befc603d7122f2d9d19c2c786e95f1acaca592ed0f67e9fa77b08467ed8217481e1597e6852eed75310beb865244b1af0707955f2c5c06e46247c0cf6ffd6d7f80e41642b9999cf7fa218c698c6e968270c55e745e5ff4a20ec0331041fb5bbd68723443b1b9926469d79ad9948fbb0165ee1c22e0e430aa92d7af794b2f4cb32b24213b418c6b5e7c7b8fe84c22469b2fa0b9ea09bdb8bf7eebf4aad2ca1ec21549e84b44082c8ba12a502a21ae94769e296e78f9368cfdac80f0e4e9b13b7c94dba5015b6cfc24d4a91e1080ddea35b32a6cc4a4d395d0e2f35b602227dff9707a9df7a80c3ba3a1d8d58e27600491ea66180a6b08fe621c3816291c147a9a274a4b83da92f6fb83c0ab6d0ef1606744a9f2e6ae5d470f829c01ef189e61598489b0b30c93f16628537e1b36413530446a85451f8df9d0409936d219e8423bc2ff02ab4a1e78b027e90b50a339219cad82bd54158fa0b6870802182ec13a82d9f3d9761a546232b0a677d8aa29ee55dbd312e91a31c4980ac098a930fba0cdb397980b9725597e5205feb08d913c7e8e96ac3f4ae382de170bb6f6b1d9d8e37b5e31b8352d0117246f160fdb8847d91850de543ac84c4f3d80ddc9fad385f72579a7e9462eed90c63c12bb10a072f9750c7b187693c9f063bd81669edc183f3c9b0aa4ff016bf867938ca78fc556a5221329176783052443bb947581fdbbf41d3f6a1ec0c3447a277ae14efdbf69ad4551458d86c5cd9e633be72e94deaf7c504ec64132012dce4a6d1a6a0d917df4eee155a796196fa50c9dc080f6436300b383759ca67de734bc54772e59c592a0444754a999d2561b19cbf6280dfb1682b5061de17ffd4e150031a66fddf0ec9d23105e54222433dc2795a6496e2b2ace303cbdcd4eacdce7cc2a4b143d403f261664be4ee29b31a212cdd33d08cdb765abb0f2e58f5399533cb074b1ba4e7228b7e26f00c63f18023186dc727e32dff97518ded73690d3f17d6127428dc2797ad38de1c1b82c6634bf998a21d47642ef74977eb633398a380fc540282badab46360c2b40905b664ac10b5fd62d5f8905fa61b8b290ca74078d45dc70131d0b9c83461c76e063e28573131273feb7678788c2ebf3b2aa08159e3d36c2489a5f5a148ee8791ddaba2ca434b7dc79d77e36799d059d16513864b304d69cba3bf241e0b4103a1716261399bba4f372080256239626c3c689ae1d73e23bd9788a1ebb31edccc2919464915beb9e215abdebbadd28756454a96a1b5e144b601fc9659bb8841179bd7be25fe227bb2f06206f8f77b7cb8996d1a2c535d718ca504068884d644093975c5e9d02f12f260641aed9ffa27e3d510c09a28c4d463df9cbf9fd6204355ec35bbcedff2847b56f90042b5d2259538246635c55b5f442bf61af979fcdcfc44d46fa2c6fe72c6af2f344a2ad2384caf8802f0432524e9356132dac085630208035175841dbc01068a6c4d000f703507aea4c35a589a62352c53a00f46ccbf8ae59f30325899d7048489c5929682eb48e388a7674ac777aacc007204f0f3c7a8956a5c236ecda2ec2495b5b263aa78c0c687faa3e8da6a4a5d539fee7632923dab1959531672d2a70bcda08ba01efbba10d190508e4d9f883188da0bb6ed01320e2c02cae0cae375dd705349f302ca90a695a4866cd7eed58797f94a06fcdbc26d8832d88d89677a051253b6c1df6c882d2543ed550fd08f7c7434308aaa7da649ec05665294904fdd4578a258ba0a119d10a89b5da274e74f708a781f3e9e9c1e9b41b9f84717dd8992947074ab1dc2bfe8459ec340fa9e1165a77b51e6b3f0a19c34722fedf6237750948040fe126e9ff0d50f14439165463da56fe144345623434c56048baa1ae81e1a463682362ccf14faafd3e28762601f8a98a9fd3bec163c569ed79ebe4e5f4433a21dc6ed4bdd2c3ca6f90e773871a42c58dd94f66aa0fb69905e8eb6e08857e166814471add392270bc69aa57101136ca2e0121ee1ded488f01a8cd2180c11ed4159c06361315df7927e9fb20a5c815633a05f936aa627ef4e780a47a3fd2877bf81d323433f1a7233b6bb8262524c46273e897a3c8be3fb71214a9c208120177dffa0865fa29f2e347269fb02b1fd1da71680c58d06ba08db27ed90a7ae40d060824b8f55aef7118eea0c1497f168243c6c6ea4105d298a5e8ee016bf2af062da3dd102a65a9053a14dbc84a03570561b0418436f76d15d95470a43db5a8e50d15b2bc5420483671f727bd5f5fa9e68b9099dd1249425b3c5c358f7af19663cd9a020caf9662e6cc73b40709efdaa4bf911c470569b362a248e18020872a5c37d090a1a327a3f21961c2c605374c2a0de9be3f28da73d6736147f99213941f72a7b8e2a18ae6b43f13f29527bc38ebae74aab3d010a0d9171e7e6cba2786e7ed58eea2033610cb11652ed9ecad016745fc84c6e119006801abadcb6921746b28d5f3aedc41e94496bf61a971dfc1c95b6e7674f47f80e09a87985354a003fe3b650590aa8434b80772deacca8ee7a649ff22de2f288be7b51992373433f69a22afb7b8172212dba7e15f364a2ce03a3cbf90f03f689d21f727223fae286bdf7a8538ce8f18289cec07b6874b41e388229c967c777684515de8bfb37aedc884476fa87ab8283c1c8d9491d9a3cf1285a56280b5c01853fdd0ba6f9ef9c902f0e26fa9e92d6eb44b1c59bc7ab93caeef89a0330e0157dbbdcfa9254492d8c853a6a113df350ec459c35370b2a574591c81ed7ada50592515d459b29590cda9db15a2375289d9317e2b4ccf324bd522de794423f83ff50365a6409d2329d99bf1bbff745ffbaf6d21e60908acc987be61eeed9d887b8267a708de7d89d588bc5318025baffa7c255b2cc271ea8342a17c75a6a6f73feabe40f7ec7083289f86fac99922da8cbd066041083abfd57608ccdb2462e5b89cf9f3ee5a8162fb3e33cd8eaeb1f45f8494179035e03c77dad1f2389bfc708b64858e7688e802ae1425151e5a8bd1b7630c904323a599de93075d8632c2e7d9f41f476b68c09cf8845b574b5740ef46156f10891ad4cfce778a05ae16ff67f0dd7cb8d84a1d1deb7c0de2fca06b6789798c536e58afb836a4de6acf4b2bb7c8e5117169e7a1e2f142677b2d22e1fa47ee128a921ad95130a110c3af7c49f07082c85997d0a4fcb594426498399e8491f43117167320f3a8f766b844c3727d9c13ce33ac780354dc38a8646222ba0da930761a6882a814cf45bd6d3bf4ec937717cccb125a800b76c1937e60ada63ba4c7ca012c6e5e8fc538db572900b59e7bc97d98ed277f0a9bb34868d4f6d66b24f1e99bc6917a27b9cd7e5b341396592177f751b03ae239dc699af8f2d4a39ac42145877e91c1f0ee7a9c8d1f91c4ddf4f2e7e5e22e4c0c8eac460db60d03d1da136608014af44139ce112d06236131eb846e954db4f450d4aa7b6240a3b692eaed5bcb2e2684d57b2279343a45400be3220dfdb420e6bb9f0d6b88c774795695e70d7dc114964bfb079a37f65f386a915566cdf18515d5f56129ed2232862438d04c4b8593055edda2558c8676359c74b195ee36a2a7d968d2ff634d007cade3b81436ea3d74a99b77686e41b53be0cc466d6e14f04b4397dcc9bbc755564d3c3db754ace740571c0a6ffee3d7927ca72293901b8185d6561a59a014f56df9ed6138d67f442f6501128c37a9cd8883402ddcb8c07fa8b0b3c601ebf253b66705690580f5d50f2b13b4f0784bd3724093e367a4695d8e115e135dc880ed503219c1dc8b16283fd9003b4926fcc32f670414f235cd18906a9f454b69b2a6c58a180dce204121481bfe63a7b0b210b5604bb903fb965d1a04b8503c23d0a39260539f256ca409eb61f14a584f0310209ca3affa0b20967b3a66c4806e4414212e5eb0256c06818001273a441c20585c874baa65c0ac81d5b3041ca8beebf6f89e74f456e9a1113f415e87e0aa79a25fd861142152a8dca84eaf6408aa41491668c00c223f0d88cce2dc02248493e73fc419b8620e326a05f6e90cb76ae5f99b19cb9b9ac974d8b7a92ea570e7741eaad4450f17aa7f4622d9f519fe86a95dd0e8fffbbe7f079ec08997ba56ca2feb671ed8b5d45f687c65645977770ee4f901edb0edd0c6ad2cea1e2ad3b1ca9165698a0f4c88587520bdfea2055589378e95901ebc1d1fc1712572efbe107b3c14dfcc692da23007a1d85cea4b50bcdda5aa51700d6771cf505dd8e64c4f53b25a3cddd8db02dd4f01e044ee99b2e59a44c7d5aff464205144cf60b3a71b41b45edfc8eed786d32ee67cfb2e89f08c39045148cac6c990da90deeb9da4ddf1e4942e601e5d8db1b961777fa0bf78e8f750938ee2c16d60a3c3ed806cb7b0f54208f36cd210423def3094175955c8203f16a7bda8e64326ee227f4572f48729b1ccb9581916a43884b4b45cc7c68ad26550e668036d7546b91a08d22bb48df29e2c7a672f24812de604ac9a43da3aa66055c2b2f73871c3f019cb50e71a44c1d05b6daeb156f70af500e1d4620d384ca0fa792ff13bc234c4bb6a302ec48afdf088b6a3067ee7b4241a9b28db1af824a63e2f60a48c390acbb5e9fd49cf69a36a8b89d54029f9dafff1fb30aa177c4ee700b91adfb2942e9eeffcdcd2a63a5c2a876a5e7e147a43f3c91bcb10d3df9255af5f578cc35656122bffcc62146899406e62ddbfaa717166b45c8be6bed4b6cc08724f1d015bb28aa998f87b4720e6881082e6804d345109329d10166cbce231d83a8c05c28a45a5672f8cbe5a1b44430ebd69dafa9c4e4c270dee8b6b7eff3475ff808201c497c080c4edd0b37ef4d6b2b0017fb59909e3c42c79066b7a35e0597ba58039071f412537132bdf77a8cd0a0ef809bcf3074694ec62ff748cbe0d00109ff7bc022d6fbb47200c4a9c043a239e49e3d595745b5be26657dc5339b0071ef719984bee5fe80979c04c373981c2ea80086d0d5ec77f8c6a00a06cfc177582aa435ad1e019637cd06ff5ecdd19805abec100a577091185d9fe0ca51e354cdca922af45f050cb8df985e20e89146f55a161056c4c16d088dc35f06f992f626fe32fd9b3626879b9f8540cf1fb705fadd0339b12411d24f7ca897f7f481cf0c19f38e32868f61743fea76b61f7ba17c77f6fabef026688677496e81e908faee0788fcc5b44a04fc329e2041b53af9e5b121b1b3a3e164312e44fec6008ad12c2a6da802ee853f02051bc4a6e46b15e0927c41fc53a2203d306962f168837bf0a44bc0c5f0746ca2e5e2b98f866609019d6c91af5716349fd026d2e3298915c36bf59e5a5a23c0d3da19230951177775d7feecf074f07088d9cf7be0d4bed51d6cc4904117771242d2c55f2891f28fa054ab5292ab1d9a24ed2b60b2f96c9d07a4248235b7fee8dbdcaa507ee87b05051fa44b216ab7bccbb8a098d554a67bb1450298caefb3404f65489726253064e0424d6faa5338ed78f6ec97145ed035257fe0cc2f389148b326937a17952da36f41b33d3aa89607d8aae51e9ac31ac1b32b7bd8c78e5c925f886e2974d4895057cd0616a4c28426faa2ac7d01ae8e9f226b94bf253b035ff5d30a65a4986d91af8f60b696f945c5517daadd565c0b3a5b45b084848c5601c27086546c4a65949282c6e8c649fd0ebdedd12ddc3dac2fe15ad952dff272d08bb058c9181a11a1e2c45c2298a42f081926faf9a4acad2c0d38113568ef9a78858d9ed266c9eca6fbbf7dd04e5417ddb1aad1e2d875e509a4e9facc096366f65fcc37a366356458127d83863a24faa80e29343db0857c2014e59eb2fc1120b8cb4e09db332dc004bade517b2a575fe657ba564d6270e7c8dcda27d178c1c18395efac9255c37b122c3fdce9caee38c96c7bb6e57c2d70db3b1b353e144487deefdbc214f2e89032e30a96f410fe1dd22943d5e3c388158dd0deb57d0e29d350e53ab256b701ebbd838e37a97c51483a5d20a2204f037c0988208e1f8676780089da7ed0aa082f88d8871688f6292be3d2288dc178bc95058f94fc0fab84275e406c6be0ac0b62b7d2ae45ce461244f1f15aa16c20a0245d4b7ea442b1743930ccead6876d95eafb5440d5b56156a6888a91a0c59c7c2dcba734c2628c4d598e6479c013565e348ab79e33f816a0d551b1bd015aec35307382cf041e4a8350741f01a0345f88f554e5aa2daed7eaecbfed2fd319d8355e4a7e7155c15cd0aba187d79a8184a4f32ce9c81cad7c91d926859518903904e5fd5a4cbc7de8ea2d5f3c8cf0b7cf41cf92f18b4ed1e24e7d3261f14af7c2381b394e375a14f51ebef1c7b891121a6ffcc56d844add98ee6159691bf1816d1c45374e431a5be533c5df340fa0d95353bffc7094497ef1acb843b82aa2a6af5ca5839e3e8d877ece3289deab088fa3b6d26ed8d22d0674b45471762ebe3eb48e1071f1852ce09a84944e48987e0dabc8fac4febcfffa4a252da1e9c325cd47a53e2d21bf48ae8528a07b0a0921443e5acb83add1841675c2e6194d2885423a4a619b1270d5f00bf9159831592944fb256212982f8d4e8aac3a760e81955b0557be904f8af03ab1f3948e66ae88044244451c889b9271ea944d9c489063d57462c5b901174dff0aa29f4fb5dcf4cd5ed908c320ffea6bd88d31d5a2f1aeea17fce98936f10b7c21108d0be7a769afb7142b32525014ec2c504aa01bc44c185f29a73f29a04d86d99919099c7e757d210bb8b8c895c351b09c74b420a4a629b9f4917bdbc1b429a74094e65b4e61fd614a1e4f9a0bca3068c196d9173c96569c2e73035b9c2176f681d4a92c1c16cbc33104acc05436c3008fa641208c008fd61b1ba5c29d3b7954ee599f631978d30d6c74721e43460390844b0d50710c1de26a12a09072096be9f0e208fcd021db857aeb0b0f2b9c7b2ddd05b139076bda48248ed58822404e40e272c1ce2e5d18bb8e9b92870f911260c1928ef1796333f06e7c40cac95dfffd17ac00192cdc951befc597db677a1fdc36082c566c01133835863baa23f2990a71a4b997e2d144ee93b0a91875a59392d3c54b0e87b17b5de427b2d93464f3c283a65aa4a25952b9fc553b12ccd8e9172eb70e690f3fa222de3d28739dba43c3e79253b66887ac4f9f4a09aae9b62c7f309079c2b60ba11fb4c116461a0596b945cc7282d00caf7322efce75f9e757da271b9a5dd40a083bd185df84e635589690a595e5fd4772cd8fa313958aea8a28fdd0c2cd69a30d1876a01302735f2263ae5b905805fcc138afebe58c1542060aab4f8d3fad345d6c5b24ac37dbbf111101ec0814c3e56da82ac1561fd41ddc52f84443004bdf61c5ec21596ed059be17cfdf30b030cf31076558b91dcc6183e01e4eb299419df8745a03dc3468cd90dbfe2089f3d0759048710ecc987b94f2fa01b068f6004531902d3c4970abdcda0949a90c8deda60bc8de05f173924fb3f092f81e990825bd0c8e572972e1dc1874eb6889a1451afd3a4962b11a682b6a9f2f3abdff3e28e04e93eb90082c18ac882a8f08782b80d41284027bec42c9815f5793b32792ae6aeb2950b6f678820880322122cfcfb80d2004f0b10c364a8139e27dbe6a1c7ccc1e133468e92d70f372e0c2406c2448a8a79b7f03a58ea8911ec2445d4e48f049157f1f73760f1540e5ae1bb528df6e1b9c5722abce3037ab875b7016a6955f05fb08af0a02f493579b5662c9973115c8b8fef596d92a1ebda44957c35c4e5df1743f2f30e9fb81e67e6045cdc8776c3aec3c2f82399d96e853e1939004c684dd8840b6cba8120f40d7d87c8b4bdf597b730940652cd4ae2d9317a971c0fe8f007923c0d33f9e9b1613dea80236c6d3f8a0e2fa20478847c934d7b5d1cf8bf74001b368e6a5c33f22737e5f7f72c7664ed9783698b9d6b7efeab0bf226c74f0a71ac2c4b36452eef74426f1f013bae8e18ea99eb2a7d659aa1500cc8262050ee117d899952ddaf07a2a5481edc903b1e08193337c35fbfb1de16c9432cab419eb4439cd2d3a50b063dc0bccb370843b385d87e6f2da5c2428c996cc69344a89efd5eb61cb82083312d84076aa26078c3ed1fedf4b81689b023d4eac17a0f557192df61b6a53384cefc4c3397cdf2317dd6d9454dd6614c1e37c7567618e3835f55e7e3633b6feb348b8fe33c7dabd4968be894edee345e4f9522d4d18bfdedc77cbb5665d33f4905adf6be7d141662ce9286cb7da0daab97c76eb1e7fb9c84f06945188579d13a1e3194201d718bd890bf9599715309fef4e27e7179701de72ff442fb9a459b561c14be893d2c845f060870646d1587d3d2b08f0707b2908d8604cd925923a12d02993c9ce7e5f85839674ef2c986d6e654d3a762647f94c50f36da6765f4067e09874e8a877d823c308a44a04b0afe64dbe64bac3431e2f93192e2055a378e0bb80a4c001b21f7463cf5c95aad90e2228c8f908db455b40bd2d374cddf2f7a6599163dfbd5963fef1ded16002494e1ba3c9fed023897cde1519b0590413bbdb7660ca452ce9c13bf06a3a3008d9329714be932c5b84ac3c2601ec13ddc29aa0ef7c6f924cb40f84fc02f744ecdff09fdd86c119bbc678161dbe617c3fe4bd80e50ed6c36a463a20264f5a98537fc427a7c4c77c5402786d89473099ade2cee4388377f991b981b9041738c405e7ff58259d2c9a7123b403185aaa83eec41ab703a0d52b24d9cab539528a7267dd597c01d6e1146fd67473c5291f6e3de64306b15a258abe4137d8d44c013cd6888bb74ec5da31383c72dfbcdef3cbe36b655cbda74676f940bb9769f8b85656030813ceaa3489de2a8b8b5c7086549c566b55aec8d7d97055659c65abf2c8e0fc455695872702c8065c1236ed243eadf29a31d994b97bddae79a6f239f4f3145234d33e68768bd4606b09a31e7ea835e7451caeec77492201ceb6589bf13e0243eb8f3f8f4a90962ce5ce189c50f473e4545b91f9570e2ee1508991ad920b9a3b6db2322e5dd787a7325b67ab849ccf4aca3fa26a124f9dcea59c4649608e12e378c2e1981d449143bbc2c06ce1d4ce53deade73b4082df58cbe0a198a0aa39efe57956b2d1b9f66aa6ee4f04d2c590c7b4e4a3f1ac8f0505b1c994add644de618eb18ec5a5376e91a4c4bdf3a8e19187b66483fe63859d4637d304635db9597609a2437f28519ca49c1145c4986a99f55d8f1261d9495d48485c2c30c97e2a4652e6e0954c46660f6804e8f0471baeca2eec71e63e93000a1764c4a3c040b9f39c82294b801d56287a8f843b5cf4bfbc2b70fca2e7a00567ce9608e6767cfa782db60905d581f3ce447f77cd596ce2017d18114319e8358a425134e77afe9186286073d779322082f633f420bd866873d4fe9eb19d4215d1bf119b50e65b67aeabe49ed29dc05a2fed0890c042a42f561dcf4b893da44b3576221b0ef2aa911038068c3565ce303ce94c7111739c42ea431a2a529cd4df23d7ecfa69bf6fda218be80f05b26fd00d13bd12f2d4897e3df06431ceaa56335c68d02bf7de39c24b052f67c7240c126983d6c254f9ced702b7f02418e651e5012189557226a627ed16a899341ab2381529de574a16bf6aa1f5b167899b39310eaae45264dc0d77461f20f093bd455c182a1d70532f64990da3dc0b9e7683597981336e9006c930f5b615a08fc3543ec03205691c06ae086c8100c60e08a526e102105a48f7f69b7b2fd30731d128ed2225a3ae58e5bb638e8949580d3406a8f2a5aea5910c32514cad377c593c69be8314eafdd311cf6a0f525f12cf41ecc94ec3e70db21c5bf23a4f817caf08edbebcc6c6c16b5015040fb39cddfa5847683e3f723d680922e7b1f024c4580edcc4f89a75bfd4d0c11e08d9a0953b519bebf16688dcb29ac1561e6cc1507247e7acaa1c9d91be395b269f03b906839cec1945977a834482b26c3705fafe3498d8a50f1055027f60c89b5b3649c5dd074faa4d6dd197a6564b6055b72f3c842d28b881e1fc76ac3b6c11e9e51ba04592ffca86b3eef2ec0bd23bfa1307bd5e844c45ede73a65fb87db96a0ae05ef363eb5d661ce9d027bc5e89457a66bd457250e9492e4e86fb89d0f137de0556179c203a24ec0e19f1156a578829744350c87a582cea4cd2c0e778ff44bce112cd17c49edb7381804427371f0d2492d69aa39a1ed431fd41addad86fd70ecf6b7c2f20744bbc2f9896620883bec68494b8b6eede4e8cd338b31fa907949e8d5d02205b7860800ac3c5904bbccfba9d19ce7eed9ff64abbfe17285350eb7fbba4dc67555c41ccdb5dc9941eec72e78108996212225186283350e15798f4b06651461520c7123c86853fa229b448d16808820d972d0362240b8e9b23a36bb1422f1d024670b40f2e526b472d958b943452053ce94346cb79a5dc368666f669d5becad046e516ff20ee1599939840fe6beab9db66d5a3bf3318f3d363cbb6a70ab7ed5ac232330f0cf992cd75ddd82991540e4d01634f70853192f90ece149425a863adecc4c683253ee7693310c5f12111f0b7f164f0be013f172ff1f87811f15cce4d4317098f99abf5c5a228c56ecd6fa580a50b857ebf7fc28d2cf6d77457597f1480014457d484e270cee270572d7e69f939f94dc7fcc87e19c777d08800ce611f7d840ccdcc0594877179e368e90425783cb91c950eb31a5deaa245f663c59e0dd77dc0b3cd763920f66d6c5cf332a25cdbd41d87b150f1d12686c5100129f0d0ccf3649d9a81064c2ad714bd56865b8b3490b46aa6e116d08de3d2059d07c5071e7bc0f2eac3dbf9ca595a8030304ccb64ac907733a5e7899e8f090443af9af1db5bb241d2b39010727b06799cbfc17999ca786acc443eb331e983fb1528ad7b1389834374973be1015cc08199e3f68c8fd9bb00f767fede5cabd9883babce5c4e7f571b597cbf0fdd33fc6a2ba542c6dcb3b66a62c2c6fd08af88601cab16217314f348976228a1e014255ea46c9e85dacf9e3376322315524d8bf65c706b34b975dd723dedc7f331f5610e0190d34f3777961f3d89ff2e46f8a4ae545bc059e8659daf1fc5c18108c60153e832a19c3ff96fc212d95fb68ac759154fcb43610a5eafb51da4776ab83fc0710db5b0212b90d61f5881864e9f24a68298c4eae2aa9f69b18300380070210aebbccaf89b73f61e0cb3d397a895c1773d7941680790bd1239d6e4151bc0c5b97100031b817b3f3930ef444cde816208eef6276e5c5eb3dbe779f534e0e04f5e1f422af9a9258876e952a9fac954c20185c3f4bbe396d195b18915080254509e680fd59d94320a99b941a6251c1fe8c22ad9cd293c3e72e8333e24b8217af7da1100cab8c0acf22328c3fa57292c43bfcef2b29f2a62ec4ad3e913d51c4e62f3eb9dc518c8b2af7b8787a2a8106d18a736a1192cb268e984d4c511f79ae2d187f886205f1e72a3f6bdedb7c7f41e5eccdb40495838777aa465034d1435164713b38f36644ef23ab828d6bbe9da6c8e46f70b07a1c17fb2d61de2efdde9e43a72ec7eca7f224f529c7755433f04c9a06f5a8aa699bc45addc4dd71926958d590eacdd950306e0f6f6503901ea31b3de1b0bd7e2c853b52c1afce2250590f32b3da70bcd162d2bdad0f0d8e46ce150feb300ac9f8d2b7d2d60d0fa3e5686a8cab9ba8e8502328df1ecaed17f1d2f50af5b29425d50e17919fd552f53cdb9d2abf068c43575c6c1e12a158160596cb7fa1b2627fc95fe9a323e320dab5306ae4d035e01cae3b4ddb9a217c59d479066e8f5791f084e17798753f498bca161f48bb937cbe2443366424360b6e928ecbe99b8657ad86af7533adbd5713f682088e072de72327ae650c2cd6efac5871d61057e89174b381de9168faff3e59b4bd4a61a7874b33a091f68d12bb24fd17ec840a867f6a8b7e96244446b44fed1030621b4c38ec4fd05ba35720f797f37227b6de1e7b60528409c16e98bf945f64745a301a363e94416c34a2e710e6efff7c7cd67541c7bcc36adf699de0198bd9121e3a366cf410d8890d4859013712f4096d2b4fbf44bef516771befa0bcd1811dd2f3f751ece82be2dc8edb3d70852cc61c77c269daa1970c949bd85872b2d8bbcdf61e70a74ff450c6accb50e17c2cab8bcdba8e03cf207a69f11df6b4bf34d2156d8b521e6229f0cd73737aaa97eecfc1d98a9cdf91b7cdfed8bb3264d2e47853d3834b278b20b8809b10002d3620b835a4453024d4fdd6bd1b3d1b34f694f6bf120e699460e96409d75332057646e51daddba53b8a4a3facc4f705470774815bd2fb95d86e290c59904d36d0cef24c3ebe4acfc57a4a6fce3347ff9b49fa7a69db3beddcbdd6ce3ae3afaf3bb01f271dd3d5ac859717f0e5988468aecdfbb11f3a106452661400d659d4cf33e9fc89110e353c4b7ed30dea4465b02b7c2ac819f182ab3f19cc49802c384358f68580c8f76d2159684957f4f180d82e1c89ff43b046f6cb860937c8fc7be58cbd62feae57522ce6d94351617bfa5da6aa0327678bf81e7bd9896f14f1b9bdcab41776a94b4ba32087b5293fd26f06daabf2e9ea3d6e7a33ffff255b974ca955b1ab10d9be90b6816f18f8c92e747e7b3fa22641b23dd437ae9a3c7c995391eafd3cb1e5794f6386d94e94011653bb6c298638c7eeccc8b95c538d7a8f6319d592c06272cd6d121a44e5b21a7678b10e6955d2f5746d74f8a60b0d494f76ebee87bff88febd74c801cc06ba3b88be316cc0a7f2e88c322ca6ca3bb49520cb4f94fef7a41f346ec7f83ee375300a01ba81ef6f8da14f06d9ff4f8c4a143a16d727d4c0219d3c7692901963caea4f9ddf39a208b5bff660507f366a3963eb97f5e3138cbe017350e59e58f936548204a8afaacb8843459263f34162a260d722ea833283651309a6720c233c88ba333122cf449dc50a45a20181504701070511393c7195be9be487ac0fb3f384b09f2e490f9677277c55264742d509551b39130c7f661bc1dfe211660dc6cb3347c125c4c73d244af5747ffa761ca50241b8816b2c3f589a93bf2139f0b4ecf8f5e5fff1ff00d8c639db58a507beab1ca3351af3a16a20ea3352b1682535decba9a19e3a26f4b76084b51505c365d45722dff850de8083217aafc1cb578cec3624eba28f881841a506969bc7d07113719efd91bccda70716ec9e98ef01401d2a37c5978eb4c38898922b9f9dfe597084f2f53951afd0c5ca1a9233150e5abb17dafc4f83b68f82f3838464661f6603c16e1f1386452f92e0ffddc19a31aae1ffc6502a00af83a722fd40da46f9461e048604b20ea8b5443e8618ca2304d010bd1f02a12806917ffa9c07c3150542525b63a9564afa37636019c021ec6de2966645dd6250e6ff803958ece0ff7d39bd6841b5eca6e69bb1ec22c753d7f43f00a3726441a21d9597cd9220a972992ab2851e732bd99fa375259d1261d76254b471a7d17b9a449044e39943848d8c153ecfa476f1d59d362eaf30cc3bd5e988f26a2a345274e55123e2f596f4648a129f78ce14bf68d411fc981cc983a89d418cc98fe777a91fb50f2d9be48de5eb7095981070f6b891180a363d61abefdef8812cf89009631a8b452bf07806353993a478ee078ab879a40b8abc652bdbcb2ff267121f946d24298e94a09d17925fe10879a2bc7e16ae62748c069722a0aac10854370f24fbaeb551e7c10829c96dffefacafa2a62846a663eb8faa5ff021aba4ed44e1fa4608cb77ad705625f2edcf335dd8c56b8cfaa10349404480ed1079e6c7030f958162a51877bac2bd8bbe7d09d9dcd27870ea8e6972fccc15d24aef238d04dc059fabafba265c5964fc83040188cf369809d1566ee96844911ceba812f6632d3a281253f988ec0505d58d537694efa8ab382d724d84d3799e3904fe81856e84f4288eb7a073f13f4d53afaa68a3005dfa4af55139adaec4729e563d8b6dbdbc105f099ae744c357961af17c0db3110b92419db1446e680ff6e43617e8e00f96aa845a8f9b72befdd67bb2646962f259b167cbe8c07f16022a662c5aba965fbd614b2810aed808e300cb8bc01a8a5797b9871f2fa7a0606db9016e12d538724881bb0ccd0ff3e10aaf741e9bfd18920612ba8ac4e0061a2cfe0787b6f4068059cef5d8cb55833b1a84571509ad2db7856348e8f8313324ae21e5aeff9dd49a3a968f468dc9538767be9aa4b1863548a95ada2579a73846a1e7303c231076e260447b8ddb1033b4c1545ea89dc3b7c858446168db07f5e2c0b88eb7186e3882aac607d562358328b50b3d64b2e12bbe9fbe7a381a307ecea3db8dc6b1d8e458e27b553dea31b1d3c60c1d649db94d38e1c87f1480ae5a9b0b7680d461f97a5a79baaa040de33dbbe5c23405f33715a18e1ce72b08b7d293542cefd5a38e45cb13343a98cdc5e76c1c3070445b828677744d4a08fbb160e0bcf76cfcea02dbb7345d3f92df42bfc61f55807bc006a307ca878a8c7c26c8f4df28379e2b1da71930dd24ab8eae5a762546397250f8bfb222a971fe17fb117cd425e78a3426d89b827bc16ae9b0caf4b6ecc03bb9e17a6df10ce275e59fe315d2c352a7886cb638d2ee6be479f2e6f20afc0199ceb8291e277d29aa9a13860cc84be8bd5aadc62417e7050d5bf9ed84fca73f7c2392481779c497d1227348648940a6754ff0969a679cf884c821a30182510f389a1359625f4fbc1b4ea876f90ffeff23fded7c2e20f88b3e52d906013da72aca0e61bd532224c3a65550c2434ed501bf26de889784b52e232ee716ef565782fb7e9f2008207fc0d4d90501345805215830855782ad185d27698fb02694ecde8c18dadf6c3439c101195de81ff633668cf91d764eb9fc7af00dd284fb94e3b1be92fc96d7ec41110362ddfc88f5560d2f91940919285cbf4372146e1567e5adc493d64a076cc92f2366ec4633a4350fa76ff7bbee20bb2d333cb8813807a5ccb76f1a6acc20aea104e651d403162f448017edf9b8348ec412522ecaf9400ac16d840790a5411cfdb977f871161d27e4243000cee87614e076040bb0422301ecdcf24eb9eaa5ab043af63469c5e1a76603ea9623ffee0df86ad5daf9776435798bb94aad2a89f0a1d431a2262691790fcc717fc8c3930adc820ed65f7d30f77c30f098d3b68fb2bdedfc0bb4d79bf611bfba7a4c9698b303088d5fe58dc54afd5a914964e974ba9514f9cfccf2e54de0c05fb4bf890e55bb34c1932995d923e2845e04381f3fe65ef5ae6b8764ede8a923f3ae4328c37256c2168876330669c626e10135864d237c3cb317e4273901cd4c8b8a765fc7b4c46b7e522d4a7cdec9194a21e3a7473e8f84dfe228b4490b9cb725ded47e985c4b80fd6d299f3d0832efca7308a14e682e46c0ac9acf6073d516968814522e1dbcb3685c50e5aa69153ad1d88763d6128ad44de019714c90fbd246ac8e8ed2336eb13d3ba61cc5de202dac63684bce669b073a7395085bca61cd7c2d881ea933136efbee219ee2ca20d5ced503b3e0961cde624313e23f5644c16fd582ddf7e3b169db5b97274f3b59ae2ecbca190f8c7d90540058079c859237d5c4a2f139c24e4886598fa9cf4cb9f6df3194ed6467718ca346bccd2783679ac79e110351ee04b429a5c07d06251f1f9be41311b51d39d8b532fafae0790c5a5ac6011aed06ed3c68cc598f0ef439b34e9c84d45c923d269f22816289554d350c0a0c9f6e38a0a0eb8492981b0b3f08c311eb2f395ad1f419f8837ba97eb1d664e2f0f8f6c1487a2d998ecfe6cdb25a1e2b7496177ccd8c706f65e5140bd3c9ee32557ba7ca3819f2b1b28dadad6e2a634858667338db045ec66144c6487199fad901c2ac8b1f10cfcd292f6314ec7ce98192b44233e16cc14818ba0cbfb70f6fb4b2293a0c57d69db494609e4b3cc26d3599055250d7db2c366a058a3912786df6f4ebd5f66a9a0be1c6888ee03b219dd41510041269a23289136ba819b19445295f5af7f85a4b9879d5587517b16caf66dc622d52295527801670d91e29cf77384b170e96f44ca70dcc0a49441372495f4099b0036b77d553ac0a4df8bb3125cae57e95c131d4d07c746f0ec0c7a744d881ec94f3d0453ea1a0557235234efb605785656c2e584dc4d4ac2188974b363a33271330140de601a97c9ee923928335777d5d6fe45cce10cdc6eccb3babfbbd17d2ae4d1dae227420a31d1d0743af5f7e8cfea6cdb4596e80df564ecc94eb8d7d7d01d1cd178b97adb97d35ecb2ff6a314a2dcce94740fab1bfcd1cba38573420e30231068251b29738dde480dd8745c08130b3863957788f74a46e9dac12d5bcc1513c8d48ea38888e7c73bdc7abab03d786a0a2d16608fa3d617c665989645a2c54cb8157d8def0c14709fbb06cfe41867746572dfd299b3f2500a825bdc519cac44f8992630deaabf8cf31367c8c8428c1cd935fc2b64f8c021fdd41f730a3b3f2b39976e99e4edffa1124c267bbabf5540b4c116adbf290e10f29d6304368a8fd1cb4c1953982a374b12a23f072cd843a01e6bd4b8609207cdb5c381b9230298d017ddfec228a310bd23b9d0fc37f0aa07b57b9f245d9008741eed2caaf711dc444e8b1999de4b136cc862dedeeb63082ae27a32fc27e55b447000300565f188fd9475789dbb906c1a7abe63b12b593388330016900d8caa5ea84d3724095f0de600ffeee1482a6b0558be5798ae260ec2af075eb07a7ebd7abcb31bc8ec4246bcebba0c6343e5e7fedf02e7f9f5ca2bd2eea855e9fbd3d7a532faaca01970224731d9b57caae0e3b3a8d12e16e8b179061791a3cc69bae0c42740afb4049d814044281667c018b31e67936027ae44778c6c7d7b37099f26564b24d431a0cdb2bee3f0c710ddef073cca22568448180df0ea75a9ad252a55514186b99493fa2e17f50788462afa18a8906dc301b8f04c2cc71e230b005fa7e89c5d43a3247086c4598824b3035845943bdbecade8737e63302286355d8513a515941496a32858f7df232260720486d622354c3d9433a407c9e1ae52d116721d0f6c7980b186496b0dcef0c559563f4c981cf157ccccd965648c850468c8d12d6704ce11dfac36d8592002ed3ad68a9aa1eefa0130f5784edb28ad93553e32417e171c42d50714c89d20aee0759c2c7ce483fc3d002de3cba8250e1107c364f0bd94f8458d22ec04e4f144b3601a10bef98518abd98b5399f5237140dc93792f4e5fdd2942cca545e8d516856a6fdb618e418d35b00e8048207438b5ea8905bbc0ce483e622f9edc1db9d9dae936ffb0364ac6c399ad6862bbe9a519d54967f0b4886b889dbc5a82929ef3862f5b824df31e0328a219bf806d7fb9337d4a6bc0c886539667d4ae38ffaebcfe540a773d702fde8e228f5a50a6a4e925a44e958f4bde14f25b5063be0bb36f6125401da006c67062a8134b70063047bfad1e366d5ce8e32f9ba462df156c58699c80101731d86bd8763ef5642de9faa7fcf7dec41c13fd0824ebc8c770125eb65fca93ebeb04dd7688951430d324627a2dc3edeff189a96c38c1b92805c00969d9888ade9034f183379044197258d4de257393ce9541a0986892ae45185f98b68954c8542e202ca9eb3279762c5ac00e6e7883d7a29191ac33be0de26840fb23e97686cba62fc312b4cbd16ec0547038c09debd33148a1d3c6fb8741535734a160ca715e4cd555ec39101ee7aaff4bd23a29da06fa8918f8ebee42a486c0422391480697d7092d3fb2c66ac9daa2b6e035164bbf29e407f815619e59e39a872e873f170434943c8046ac0e4bf30fbd3b14270ef2a0638599918a92e93d31fb1802e4b334a78c44067f00028a453863959c000ba091704ab7bd07fa7b1e0a0e84020902e8244fe560e2999c7a7920146972a9822c8d4cecc723e7445961b95be49e2b3cf7d0fa6b0875c3b6c8d57b9c156d65657cc9018d9c9ec286aba18ce26b4fd9f5a2cccc25e6bbf53a2b955f59fba3f7905ecd773a5bfbeced8dfc2a4ad93aa970574a412298568dc20fdfbda2ad9f5f2753d4f8988c2c96e1eeaccba564a8778e8ea4383e92b6682559adf0fe794e56900180e71add691c9202b7e6a7682b136e2af47096de09b4bc1f13ba46b502856f0709967e4c9c62ad88fe23aba10e6487a9f1f96749afa0c1f0a062a90cab54b095fd3b1d16583ee147419dc07d5534e5afdd33940a82a766ab14a2c025b73b60a44628c2d50822f82fe7556e7b2da8c248339db895c94378c0d96edafaa947e671ff9dcc2e8de25a572194ed52867c87975feae97e2fc661961487a0ec690dc0dcbe99a913ed6528a42e5717daa4f13b9611a1f1fdfcb822a5bb48e762ac7514934d95a3502c2a0874f9fd591266989b4d69e25ad8b852c8676d558200f49ed1b5cfba1cf9c31cb6db723e2e710d676a1a2c59be3a20a52ffb223199bb6764d60f1636f61fe02e7f2a43f0dff6fc5943172d37a8dc23bb5b7e93f50dbcb7509775086bf0284ac82f3911461d95de7e714e42b11dff3f6e4a28861360bc4d87d66163945de8c24a8e216568e3de8ca52c238a38b73a70442d9d8aad6887088f1598c10193e9c82cd59c3b5d019c661a5f02f17af6bf9400f256b63a75938309c130d19529a0710bb8669ca97ca7bb37d7bcfb58da7d5132f357650dc080e1b3eeed302aa31ad3ad52e5de796314ccba0cc3bf64b0d2fd9d7c618a4649b5da32dc6475f104f7a3e29fca4a2b826000ddecea892dce7c0a622853685da24ceb02bf168d49b428bf04564f48140b663cc4811889f6cd1fadd168402893f0fe6ef19d284246f207a5531f853c52b15b586033cf7d1cf8b887e6515589a474ae1f586531c171c9dabc621b3982a0193f5447bf1aa4c37b6f9f8fc95a0ea398404a5a46eec60f41017a66169200592d8ccc09ee7e0d20f5a75ce512c82fa6b8f8744085b7e1460c106786558479b7feca42c15e9c3a3abfcfd09d3d9b4ae2ac0f7f4cc79fe92264fdb1f0aab30e36543240e3667f26458e040868b1c6d39e6b94165ef54be9e94d8363e1f568ccda2470a71782ef5e0a8339b09ddbb11f3638d6abee0a141f9f3b9037aa9c091d92786e03aaffbd06c942e2b821fbd66c5633a4fe12dca109dd0cb41493623a306524201669499987b4f60fc9048be134303c72c50d9232a34d74ac405465d80c284da6edc0a28e2f1d9f411409d3f6c59c737b95129f4919d0d162da9cebf4815b43d0cb00809b43b7f8840c386f49da831741541a96bc88aac92064651e0657b6f522ea69c05e4d79018789fb41556515eec0652ed08936a51ced6b3d41e009e85f48b4c52d91467e9134845c95e8cad5c21e58dd35c106a573fb8a66e5bb8cf7a87642848bdbc9c6eb5ac03016ce456946dc4d478fe41730ba63608d00237b9a19cb0b938c9d368d80fd76b1dbe26e2dadb9e03e3c9ccfc7b447f16ed7d128ff3eecdc61c6805046176f193e86a5d673ab32f8f0df63ec4869d20400d42f10776eb7d23200578cb9fed14749c0a369c8cad33bbbcca68e88b17085efd69e2e551c5770aa3ca79c64a407028d11c929dcfe92a381c9b3da5125049b345e28c639858e594eba9a9068c73c1d592b3ecb1b5be8ca798966000363afc1832a9f1fc8ae7493ef7ce2093b6c488195f5840d6db72b75836764f721e4f05658b2c37d287b03458e4f819fe453468a690eb089c084039dce0a283d8e7f87cbc9b9764095f567023371f2bb4aaf931bc1e8fb40f3cd7da33756baba8a0b32840c212e7c8622eb2e1632db7f1c10c8f4f5831b91cf21ff5e24555cd764132c41ddb917e663a965a150bd9f6db24ee50bd3dfe19b77551462a49b1dbfcc745c79a96aa164a15e566d09bc640644ca5f5b666c069898c752f963b3d0d2ab3cd7ea15529bdae91a99497bcaf51f0367f9a1ad0328617540626ad1b25abd05c6bf8e7fd9ef907f6d7081d5c03cf4ce01fa54714a5c300acae21cb5fab6d470842210446877f33379a5ea36959aeeb543b0ec22af8907b4b84dada6bbcde19c780e8fc7f978b9fef1fafe3d8ccb455196124bda32c4384876a91b801c27663faebdd18559ccd077146788b426bbb79449cac00485049504ba3fc7c03f61bf96674e1c2c2d5b2c0cc1ed9fd553a03c5e2ffaeac7e50857bc599cee2277fd700ef7829a4f4dfda282fc7594527fcc7f1db918c19b376e3abffdd91ca10d0cbc79f306d835ff09d0ba7642a7891d9d3673b10f5d04279db82197df9edbeac4c1606305c22ed6c65ac638cd892b1af9af23c7268db102b98b9d3fa73d0ee4353a917e44c0f41186e02295397111f4e7996356e789dc4315df8fc11aa205ac2f5851583f2c1c9c60f5b21358297c12ac20f90d168f5f73d5e673b84a7325068bb9ba7295c31bb95a72d57455420e73a5a388d51265acd8580961d58395965762a5835513ddc4aa1784d5112b206dac72790355486022aaaab012555c5e4c1515cca66a498ea18aa77954a110a44a47f504951a2a292a2a49a872f82e5430642daa22180c15092fe50c1251ce98693af3c3992c2150213342504a851e553a42d8c1e6278449131b844842871825d29ca0a4052d1f1227c4d0f04493134cc6683122cc9db172a649cffd6639e31353729cf912396ee312e34c1c1c6ee2701377d25596dc5592247f912fdc4951bcdfe22871c6c27805b30313b43de4e99013f36f7f3831feacc3accff145527113e74c75ca71a7f8acc3ed7a99214e8e8a90f368f453ec0314b4be905ef12c4abde2f44a3e80cbe48b47c25d4ecc31d227e15ab9be128c9b36232b63b4339994a81e2e1b07a07c6068686868e88156c64246f862188662188645341a8d462b92b15881d33ce71c2e4990f38cd2ec8004e6046d9f518e52685c3dc10435946a28856186916164181946869195f2032d6610e47c63dc1837c68d71635c08eeac95edd3466981051760ae2f80758199d912cc983163c60c0988861e48693f4179a306054214940510c0004e1b268d6ba30218d5892d2833b880004357e8c4074861541a63a06fe81bfa86bea16f680cf4d0032d46fe1a94380161187270efd53ceb129779288ae22af630ebef79bd7a4d40a95dec08790eca0e4c1a0a28ef0270119094139b39e71ee61c66b283721cb98befa591d5293d2db79a8fab2bebd2a591930c28f7ea1a7907e3c85d9819e85c8ec81cf9d0c89e79a63df0e0010f2021a0b58640be108041460341ac138275fa07ec4b9816f93034720497b3688c52eab085381bdb52e79cf38bf768e9e972efbd65ce39678d53f29496c3b5847dd1fc08ada3c2605cce1d24b0aa962be1529a8e8a20145c05ac8a5d4b3d2474e2b01c56354669c728fa4a8d518480c6282912a523f8ee24fa220e8387171f60f47582df871442314c081c0214c77297fb6b2e68341870bb42175caf1749963794265054e18823a6be8c19336607060c183060c0e4f43c5bce7da424a4bab447ca231d21654a77a422a43ad21cfa06ed00fa04ea0675026d026d83065126d0255025d0245024d023500ea05ba8116811281b740daa064d836aa159281a140bbd7ae289d235860913e697b30ed3f36c391f01ad42a9d033e8144a046a061d022d830a819241c7a052a8183408340c0a060502fd82fe8002512fa80f6817940bda03ba01740b1a856a41b3a03ca058d02ba815b40afa43a97cf952bab01ec5a43d848ca4e7d972ee44872952a24081cae1090e4e9adc6083af061a982c99410625bda724680ca8130a03fa02ea02ca4391a02da02ca02ba02aa029a047f08bbca8bdec599ee7c99e2dd4092407dfd062d6e2027b851f9f6c53397fb1020a54fde313b497f8662ff28a62868c3486d3d63b817e80f04fcfb6bf8663ac50da3b6c424a633893cabce6ec3e89fd253fcbf564384311ff0fb3ff3b77562c8a5cebd70feae987cbf82b0525fca9ed67fa24ddcfd495e9779f343a69cde2c2232fd7cfcac2a4c16324147510cbc4043622d168619358ab9934425e336b689ff57bba6fff81fc4ce6bb22865a75b5bb4b8489a82bce6bd961ebd30f4ee239de12e24ae50ac4e24599fa3861f8ac4c3abed4c362f6734f419cc748906b5cf23695edc1da930725c5286398349e548c657c9957ddf7faa4aa780a5b9dcf04001c4199fa88a07ca6ad431979cdbf3ce302942a0324b21ff52d0c672f53a71373b3c6ab8798b3b072faf0ae06ca00c0920c094a32af629a673e9fc4d23043f6734948416bb8c324054368788d7a8276567c89f2dac249cde79365c9dab4b95627e380ec081912b224644ac8969031210bcad8c88a901921db9269c9d2c8d4c8d6c8b2645bb227646ffa73591bd9962c28cb9a9204d5eea0887649433f5905655e67dd2989c742b15b58202bc55661adb057582c2c0f6c16560b1bc5eac0eec052617f2c14360a2b859dc242b13fb60beba53f67b7b03f368a855a82d463a77ab22924d98eb5a02482b4fb76a7ead4232a4feda942d413d4212a111505b5886a446d524b504d5083a83b9504d587fa4305a2ead49dca828aa43f578fa83bb549d5adf0a249f535b1be17eb94aaeb2f0b5a5f2acffead7ed6cffaadf593f85fc64c2f706e8b6bbee6ba85d926956db65e9e7dcd06f5eddf9a4d8a6af37575fd203deeecb94dcaadfd8e6b83fa20f796f2e76f4209407ef82041d7c3d67b1ef7668ec0b443041df7b6c361bc67eb7522453e003e3cdc254aa238680e8a023d511d34455114d5eabfd51f0df6e73d8d9e41a9d02ad40abd42b15034fd799ad59f677408d40c3aa2b0151d8392418540cb5ca2e7b8a7e8a98322a71c3939729c1145d9a046f4e7a8d67df9aca88abe5814632047589753734740befe214a000cf6fa04c4b54e549ebd0ed447e9fc8c549f1c4620e8ec1b655ed90e5b9f82d49ea002c0d6a71fdeb39abd6c3699bc3c7b7e644ee0ea4f41ecfff2ccc6c4c4c579f0252622c671f481e9d3f1e1cca6fedc00655ef90a332ac3d5d569668da0ae69b4a222171a6c50e6d5a7c3d627d5a7474d008d56a40092462b2a2a32693c1394d7bd0b9d2d0c68006c7d02e23d188881d6d772df0b8ca77d87ebf54997a6e04139931c69cffcbddc9b8e0ff77676b93714dfc36fe0b7dc5b19756fa6967bcbf23cdc1b0fffe77fe5addcdb0e7ffe55ee0d766f1b8c0fc77d1001f0e1cf49752bd8472d7ca63f2fa9fa73b2aa3fc7b1eacf73aefa731468fa739dac1f8718cd8c445ca271aaff3a8e81ad23999d7514622c73897450a8fc54790e8ecacd9254d971ca6764331ad19f8f5a978f3e63d6287589c4ee5a335871e1af635e84c18f990b5f6b38e55beceb1f8e0e28f3eaf2a0ccebeee1aa1f10ee59bbcfcd93c958e8508b5c6b31f409772b56080909c572cfa4c289fb1c8def093971ff03fe0f4b148ed0f7bd0c6c0c20457952c06ad86a1ff4d87ee84fa0053444980032e1bb593db9345593aa497da46a52355a67aa75a63e4ed79354eb4cd3a4a9d629e3595c0e9daf0eff5e2e8a5c461e4d1a9787857b7319a99e264abacf0583e4ab7472722a9d9c9c9c78bc2099957574a43f3f6acacabb0c15249d78b5325892bcdccbb3f7417d3be517c5253765217908506a476e6dd95ce8cf6dbc2d1e8f942a23e5c4e3f178e4cf4dce65a660704fe1f57c506e82bc20d973e3e305836e829b97773cb2e773c30b067bc8cb44f28e9777afa053cf47b2cf2b5d4ebcd00d1924cb201974ea2de1e51d2f0f114337c1cf227fcd67059d48f679aed14dd08947feee89fcce89c773e2edf26c055e6f496ee2f1782fb28593249faf856ab7b4c056aaadd25a69afb4585a1eda2cad9636aad5a1dda1a5d2fe5a286d94564a3ba5856a7f6d97d64b7fde6e697f6d540b350509aa9d82229a429a3921f178315dec488c17ebc584c44e880d891189a1102b1233126b8a951033211624b68b9110f311fb110312d3c57631166248faf3d891d82ed614d32d79d18bf97aadef856c2aa67b110c3a79e29352b5d42d35a89e51a9d42ad54abd52b154346a963a846a4625429d52c7a8645421d432aa943aa5b2518de8cf552d754acd52a58e8272b1dc91940564ee655f7692a1f2af5c3396bc3bca505f13f194afb97ef716669b8f2d98675f53d27dfb35a51f99a794abe1ce947edcdac37095745fabe1de521aee0dc6e4de747e8697e195dc9b89f314ccb60f26b9b79c189c6078c1857b7bf1ee8d0c069d3cf1a5bd144a30fda551419cd486fedc4c6fe8cfcfb4497f8e2275d29fefa438f4e73ae993fe1c96e69042a54c521a2e510da9924b24433a43ba4407b68302c73c535faa434aa53f4fa194a92f852ad9b46704041aa5bd0b0840ee34ede872d3ce8e46dbe1a5bc1d1d06e40d3298b56594779988114e6e2797550092565454e422186a172e888208d080dc4017b908725054e422f84e3d9e4e9760070c20b3918c847c6524b9857bd3b9ed6415720af9488e917b2b726f2950b8379e3c249f9085dc1b999d7292fddb517d43c9d936f4e738fb86fedcdc4dfaf3733be9cf516c1cfaf39dfda43fd7d939f4e7b00db5996c1a760ddbb7956c19f60c7b096c4707c59983636edfd66153e9cf3794eddb50bbe702082c5d98b9dd6b80033cf059ef92cf2bd1686dcb799a02d3a71397779708986d792be3e7a5bbfe411dea6090e7c40be61def6bb8c7b9e96b3818b331809e14e54951638f62a8b4036109924c7de4d206e33cf8520fbf9c821d946b1faef2a09ff1258aaed0ace1ea38d4fac545b386eee438ee143c2f927d2671eff52928af453d2bf9b33231c15c7e701451138e3a6a32dad29f1b45e9a61563f9f6f14fc783a63d6dfff9acfe5c0d62d1f7fac4396c7d6202e22ab8e6ec33fd0fe67789731eb42515bc87c500886f5e7b65c002c170778df00b5c8842c75c85918e794f28a407f530545e57e8f5b17cfb40a9a9355b413751197f8eb4ff954b94b38ee712912a686c9e57155d27cd3a8c2df7fae4c3f5fa44faac4c4c22f0ed87ebcf04af8e39e6ae8e1d70c4bb5336de357a0da5638eabfdd09fd780f7a6b93ee2f5c88066b3d4d499404cdd0236906a818758172b68464647b8cf1d9579d5f9599fe773d273328392f98e51e773cc6bda75fcbaae5f850827e658cf3f6bed622fdf34c2ae9f7f5b84de1d27f7efe29b3fd72fac47bef7c8f7aaf138ae6188b5e6a3d6235e431de2fcff1f6a1dea8c3f06320c726a303dc7778efba9f1de69c769cee572b9a6a5cff9e6340d798f428e7be72eb5f9e6ad3a2ec7a72feee2999317a7812e6eee12797fb19bbbd690933bef7418f2ddc57073321311f7de221fc771df0d04b4cb13bf50d73aa4b0e1c4e95e3e5e75681bcbadc75d8a672f9fef6fdf25aea5be36bcf47be79c4ee6f5a76d35d714d01d875227c5fc21bea2c8bf8b9aec28bc98457ef70e459c4bcc66360c1286617886e3edb8d2c64ed73c6badd4715ca9fe4c8ca3388a9a8ba2c945760ce2848de338b2228e18b7e43851144dd3344d3eae5f2b4d93c595e5ed6317c53bbb5c14f91ef7288aec8f00705a6471c4e831ba66452aadc73164b366738e7255fa16c7477eef38eeb5e4323ef37bf7b5bdb88bc5897cb3e5298eec796b622ec31556f6dbaf4801dd435c0ccd86d0c31858fc9f0a39ff73ce55a4ebe9c41ab7a2df8fea39be3f2b74892ee731e1c4fcc220ee9cdf07db2c811d0f65104c6c46e85bfc4b248b7b484e2cf21076e8db4a1f71493e067b0337700337700337d0c56d882150e99b8f59029b8b2f76e4aeadb9c8431ad2d0257afe63e8e7fafd03f941f99ae70f7429f441b695fc7210b2cd24b16e5705b7022897b4fe423d67c55931072627590d9824e7b197f0357af172c54b6f92fc974492bdd7e8c57d5a309db74adfbc4adf2150e9a6006e95be45fc9b82aca444a3b52de7692acad8f866516c87be15e201b2db3d72b2288a2c8eabb8cf661697f6300cf58c6f4319fa97a2eac4ed145f4e0ca4917262d784d4ec6471f7de6bc1ce11f634cd99ef6b137966b5c67a49955c9bf0d725888cd4c411e24fcb17120989a614038974f59b25cd0b33e05c0ac48564503063c7c20858cc40fd4892438c169a233f2d4ba23491c5e525d65f497ee2fab9d972d5c6324986048f70b1f294048e178cc86205b442b34bc1872678778cee3877a5ae15ee465d609a1f24928c3445b4dc24c124273cb5366d648e01dcd65deb1c620c84965513bde04b0b85561214e10e52052c8aa28d9c6f318dd55a2b1c410157579638b204103e2ee10821f418a205041719aa7a3c41624cba42f33788fbed3896484a31dafcdefd66d11aa2444e9af2155240c2852754b8ecb8a04b971d5c50b0e261cc98716a64fa6d6112af76d862640b1912b45cd100abc969081b8060c2cb96ef016787966e24c3e5a9c70390236b8670b3d57483d48f148e88c1a1d7ff8ac94b847c451c93a4ec318965af72d0557663c6a40a92b4748c3cfd66c952933595025d3ab2b0e054e3108b3867eff6e5ba59b2c07c386a2ef24b94595c0ef19fb95c4ec7d2632559ba36566d803c5c82535b474a602be0d48903eacff18cafe170c07ae61b60aa2d2fe5b9db70e0eff7fd565f9db89d392f81b25e537e0d57d47fe4d885357fbdee8fa36b10ba768958dc238eb28b6c05f405c2c81086d7c33741a7032754d5100d29877e02951591a6a8440b191462e9684880008317000018080807849124064220c975e50314800e608446645e36944a83811c87611802310cc34088210018630c31c630c65859018621d1758f9e40655cf568492e53268830b4c85596ea483e2a7c0529ed05b523f8072b1b45bc3775096368c12a525c21c92916148396214ee46d21bbd7b26d45ce439ca429e4ee8ca10b31c9e4f34c840de024274c649540856fe74ad5544f8c9a039b36e6dba314c1f519bbac4ca66d3e49fb7f9e12353e6811f76a705c4aceca6371278ca7124a7cb7acd7e0468bc6657b679d0f1bc2ddcb474043f42d199f5acd8f80aa6f17008bb7c01e5acce891709088e6e0371dd577939fff3d050ffa94b78458d90404d2ae17f6f23966b21a80c59f520cf49de1816fa786162d4b271d3c9f8f72c16544bd85dfd8555b0ba27f0605951ce69d87db4a1cfb2d668a71eb3a1e82c0ab6f42cb3116c42b09cb206e381a93db44427ead706c94a65fccead8303a15d687e29921b14d5615a4e065da7853da9fee1432499d1041888b30cb7d96428b9323aff9287b5ec874903094b77af846132d44b7c1d7e413a4b1307d811824960a0dcd51b46a4ad5cf0ade66e66f863af956ff782c99568a4ba3126cfcb9d7fa70f8137277153710dd938fa4aadd19ab019495484afb6556dfdbb0cef275bd43d1f63052921dacf1f364682feff2aef6f5dad1f4d171ee5f63e30eecc856469207a5bbb6e28ea41d8831d3e98824ff5fa23afff93fdb77e030831740c3d9c3b1fdd1ae83b2c0a2f97716fe4c2004b02d0dda2868543a221725ca6bf7047c52693bc2d95413e12f406857933899e28bbf12e74791c9fc4d0599b8fb08dc6d483c1fc400db2cf2949e8afe65f7b52b48023c802c03d3694eff2642edb5dc130f8a40f73b8288f6a9061c5439cf19e573c2858d02d1b7477a2457cdacbbee7979446362ea43cb735ad4dce848afcb80ed8140da842cf3823b68cccb057ecb4757e1814098b705622f71103af66fc89005913a2927cc6e7f5086e6a9807421f9515d340b2616daf4acaa8b736985a1e34b71b041b75682615dc72e8e5e92755ecb1b1488bdf095993313e2bbbad6edf5dc26b8bebe3812eb298e89749b8530fcef01a80b4dc4468e41562fa8e28d0d07e2ba796c04c8afc3e92471e36f40eefe70ad64d1f98385a172f37f657f0e7d2b564db8806a752e3ac521c5dd846866e95cae69f02f40984a621c66c60f4963e2e1a3beccc5d5860364eed26004ad9133acce70b5267aeb627f03a659ca448f0f78d4fda94662fdff7f12fc14059f280a192182fe20ffce1d9e13bcd25f99bfb50da725a124b59f19457438c6dc8b1cc5237b16f2001f358416d007f6bcf10e09558cab7ffcf6e45a520301c0d5745adc136d0e350d7a11581f89c5e3f00a35469582427234355220e6c866c8f98afe1da885ff891e84656f0027a07d625a0ac3eb099486c5de7b1ad5578106b919f55dec7b4127c72ac6d706b7b40ad05df6a3f837eab71020227b741712ac09345bba6e8da24f4b449cc516f0830edea7945bf9f02764ed5263cc76e228c2be10fd7ee72fdcdde09db01d51adcccfdc1fcedd33dd0795ba97189afac6a51ebf0eb92437c7aa54858a26e228fde80321a8f33019f20b1904e72de5ed3456568d41bff7b990fe9e96a088be1e6e40dda589b2953247ca5ff96fa2c0540e23b0d5d9dab55e0bc09af6d04dfb87dbbe6ac1b0d4a2e6928bee1caf68ae78982d4c58ad14bca39bc6182098a1f6f28d2cf4a94e7bb2e77c5bf34e5811c69ea504549b7854cfc4c1d55294c51d24269f2597e1b9cfaaedc2c3d092dfa663fac713f68866b88c58f60e8207cd49aea0a94073b34dba34a0716868592475a8d466c6f10c2c588bbb43c748af556b02f95058adf2f4c4bbe68e84a36ee004ba916119c2d13e2c780b46343d437fc17d92cc7db2b60997bbedbfffee9241ba117379421fa0bda600965091ac5c17d623a455ab8d899c45df92e5ed676001666af20313c2db8298f076a41e4e170b423968ebfccc8b19f11bd9b241dd1ca6f4f4af2d371b700bbfe4fe110ff8ce1cf8e78d3d449f354564f33e1f36a327eac4155a1bc631019be06e0409927d20a24ac7110413fbf76a38c3dbabc3daccde6f36e9fb34cb9c1571e2ab70fcf87c12208ca6a43ad7744027f756005dbc248fabc0bfa3d491c9ca52120644db68b8ae33fc92b08804c1d9a856464548bed2d4471c1f24d8ce98c2fca0e71943ea61902ca80bf3b042717ccb29cb706559d05ba36a2b6230cc568e325e431c30b4d9d86b3c809a149f7320a8a9a5d19281b6b1266f8e0c81398a5fa95066cd3bdc68445b2cf8440ace5e4f6327d12bdfebd00bc403a4d840c239d4fd7cd852ca313871251a097e6cbd8aa1cbc3354cecd01b53a8d72842e754cc97a4260c408615d18e1835a95038c69674231fa20f25479cad5156a9244d9c8f10d1d6e72c9cf3cac3314637fb243035248a1f7525a171a2ae146c4488dd1aaa194bbc02e8e63e42d2fc29a280aa0f628fbcc81de63a40bfebc8ae2cd94e8f77075ab8f3e3023c929d1de5a1896a205cdb06dd8646c8e4b95f3d3f2a47c19fac1d8cb54b4b3b54c3eea3f8e03ef2b4cf7cc9e92afdbf0de9e90620499efeecf632bd3d68da5396cdd87c7c842a4f53eef63c071db55df5b3cab9587e8b3c1ecd9828d71566b86188c458fe1288365799360f49da1e2480cb09e0388af03a006a2ccb4e1a69891041b5b53199858e8d412baa6ab4310b6004ecb88b7bda8c41c3984f647227e5afcae463805af362e41ee38cee0e5d527cf5514502656f0ae112ada505c1834894e516e35ac046bd49060d86e6597f24a87efb1d4e3e0d31e9462cbccb62613cda374c274529f284b6960d81173064b9c63159aaeaa4d26f861742d3191f6da6e8b0a9cd48c59194b34c41379590b4a60b9822310b76e607becc43b8e63b72a510805952acd495cbb32c0dfb0c64f6a39d7432a55c6d1f87979080c3819a19b4eafeac58ac0c05959ee6fdb927c959bdd34dc7ae24973d0f792208fe3691037f4aa96be244de4e227c7646a57eb24e592e838eddc5a351ee779cc5dc39230cf48d84d216cfd5c54a8c27583a45b328c875645b325f3054c018f50f21dac4412053e4271be85943ab62cd10fb5157b718743ca122986efcb6b29a4407af8acf5eb042b5e01aacfbca5bff0b50a02c9fc1906f29fa4b5f880ec0b2ad4e5126ee4a691fa4a81ff393a3759df6e35aa2e9218d2c99733332acdc37d36d08a499b793258b60345c365180681df9cbf4006930734cb9e2e304ebfdb1f1b1d63680584cfbdc0b1fbd2b46d2ad8813be33a813d2c5e699d78ca498aef524f632c43b93b4da854845cbb2de09fab895a8b91a340eb5cfa1c572a18e8efa54a87a877c4acabf2d993c4b621ac83c44388a7c42977ea17b2cc4aa4dc2182213ec8fea9ec801311e6d6391264f553923a2f9e4cf3d766c12da169f5f56377cfc7f80fc8bc2164304dad7d78c479504174f2b5d66ab919a89c99afa1486c33baafe5d7385eb959bc40d9c8fdc4f94c61a26ccbbaded593ec2839becd4bb76717a407f711ce946e75247a6be5e569b79fa05039b050a8b0cee5f7a8b71ac5260a63fc35f38382bb6767af66021020a935568573ad197afe9c5c9994fc42a17bd11efa1e34073c96a4f2e087b79c93c8884a508276d92777af5b39f2d01b6d2c496949a83e92c80b09554d3c4767f1a2901f0b7052608737a950c3fc0058e4262e9c57e8b5c12656cfb5ae7b784b89a25378dd8c300c240c8055f3041a7a1e3de6364a8982f569b1b50cdb582f3f4be47af2ec44a582b5983b8763d2ceb62f035eeb0adc4722d73ea07061911f023b12541dc8f2c4de5ed7732afbbfce0390e51f8b1de85cea6df0e21f8d1e0a5d3d37b52ef8658e6b6446b613508dd54d656b7a3230e83a58439650671cb8faacc0516bb862aa503e3c9aa347d2dd70ecc2a06651a43c56adbd724f0a1a9f8392cefebeb260625a15ed81247a97f100f966be7121565c1ee938c9ac4e19fdf4cabbd217014658daf77686bf98111eb046ad2159783eb323e5956d96fd3071ca522127e98edb6e52ece278bd6c9d3432300ee24f811c4324074ad91a6b244355355d14846b59cb24cc8552f2be1b4453c87488f2d6e72ca527bd09f8ef2ce72b6d504943b2c07473b2a55a69cb2aecdac7760344e5b074f15a35488e72ba72caa8e1da01b989d59613b4255f33868c834352fa2d170e61a5e085db96035a46770e83249a343963ac2c238b2c9f5a45374f3301075d8d5fc448a96ccc83d0df7ec9ea1e560cf3f2c0cf57b9d31a81228a631035ca71e99c8190b01d574ff47c00af9da5e0d9b370405dc0a96a91f0488ca106c882b388bc3353968aedcf999ab5d849cb1b395c5a4842d1c648a951b7dc1047c1e08169d98e0d857a1bbcc0cf7424b60b09ed4690908574d8301cd4c2db902aca1191421fd5d1f51a16fc5ecff5089cc5c2d6c81bc7c004d24a544e916ac4fa5abc9794c22920c619b0afb96d67ea10b588b71c97ad954499b9a2666d959700549843b0d6cb14bd4b7385584e10d0841234a8b82d2524401169a026e02f7c41cb027726c84fa3c28c83d23253aabc674c9eb0981199bba54c600493c065b9218d4bb7e59fe4a9e67c24ac6b080ebecdd74a92fa72774c5e982f4cf94080b38ec312d1d6846d872ddd2735e3edf29a9a2ec1420386c4cd906a10efb4624ffbd55d6fa7da09c56d6b8f1758a9c273bac3042cb8e4e2457592171fef827888241f60940add95d7570ffd7b24dedeccf08155c69fd72d97a4faf11471e9a33ef96dac06a9732895597cd49e10bbecfe446a0deb159ec1cb86c4c28cec0dfffcd68ae0242d514baa625883fd84231a966a3224d87d9affef31997f4cb5298ce0c42885755d5e27614c71c49495f0186c57ee83bc8cd7d9ebfade5c6ede905297dd9395236a3a1cfe18aec6669cc9543800e0c5215ec6ac83b7b55a8e4198314d596954a7c58c7dcbc06c0967a70d99e00bf1677a30ec831f6249850f7d297567055a0f3f1c75edd4f36a371ab1733421637220fd4b5a900446a99d0d4f8bd11e7acd505ba1049b61fc0cc561295dc31f22df935184939aea7e457b7b5b5669120e4d116f00dd3fd28e895cb9d7b32155fb1802a366e8c9197e2ba32a89cad5373757ff257313eb7bbcd623b17c3388129753b23d9fc1a29115a1bd9f05dd2001048059df43107829b38ec28913c55fce84788a61c31205ed31eaed281cccd0898ab6eddb8e86e9aeb71d8f861ef88f3b23e88fadf60ac1029fe90834ae5d8ae7f0c58fca4c614381b60f62df7a9827e98e892449efbb66a78ad72ed1d8653d267553c6d16d4e1796300e6542c50b3392217f36137ab73b86334707b7d0503987df54fea2b54dc433f638baf0c5db263997580fedcec5592b24579865df8ae4f348f9c4880999dea0330807ea110087d20afff9150ba5005987937e2a471f49b5544a7f06e369bd49da9d4e4fba0285e534f93b24b9d79dedc8655ae18bb27a51ed22574861cfe14ce7a601ccc862b0c937e2e28d905c675f5c072c12a0bf51b09c8f9b457d2809a5f1c75ea7ac252d3a676a7fb0344f7127a9a94ddd1a167e636ace60ad97953f6105d426666218ef08417c5c1343ce218f9ff8ace5e60bcab0df00b42010e51e75f04a4c0737f6d9b86b4e9639b3b0be261e20d75d06c0586c40c0635c97ac1d9b79bfb36cceda1a3063a3577891427578d772b34e26ca1219ba998173c594341d6ef6d7ac60c0bbaa2ce526d6a8a32ca0c3597a8d5298c13cec101329ed64770aeb35d82e855072ab5dcbe66813868ae906ca590cce0fc0c5d3b9152685d657066552d9645a290582af79c3bc7d1e6c8425d496ca5bf8f67ed5040f94b41a17f95b68cae12c0b2f4e7002334b9d333acaa32b7a174ae35ccc214c3732c4a618e5b06ac4ee22bc95a6158cff4864806ed004b006cae19167ff31efa39658357031d5263da8770347e1312f7346f97aac2e1774bb82021aae26d77e28c216af6364e840ce61fefa6631af550238d2c75b40b8f1ff752fc8f080ef35f1fa4ca30a039586dffc7b34b4c7016b736ec6509431a0c10c3b339f09bdb3f55e077b5624b3fe8d11a1cb64094845c70c6c7b9c392e1e713a43c3c587d29382f570564156f7de1428be965baecad2698ee1d9cebd98d4010376060f4024129c9dc826fe03982f38b14a3ad55357f21ac4f66bf67263f7f17c082b0bc661d0fc3c51dc5742f35eb788237c1d99e9e3f7a337bb8c02138fbd96c62e4d1dbe604c179fb06c69159a10568d711ca09b8656cdd641f700c08119194ee3536da0567dcf21309cef55108a647494c087d99d9e7a98125c186ffb21be268029356d714de3e5b70aeea1de6d9440abac8076a2bfe94edf8646a6f8632e67933fc8c4425a811ceed16c022de298f9cd1631794326eb15f069966a9a8b27e378848525ec513c32613f0b6407d08ceb65ad570efb9df2433f2e0897dcb6b7cd18f1ccba4e2659f37843c660140e0ec389aad7f46ef43dde1997878eefef75bb260378f57f85f8112d7519ff52b36f0ac3407eca7e3b6b1ee0589617f64a5c7e39e905162c8cb011e48dbc6b79631a7c49d21065de1114ff69c15e47077ecb9270d9660bb8f063205d4ddc70d9c661c8d0f90cdbaac2dc88295559dbcc711dc43ff4ba32705992becd6120ed988db96b62a1d1c972449f360b9cbdd84bced2d95e44d2d041e5f8c5332ea3536591db78285181ca0ab07b1999e2559c1c2661149dca9d0e36fbcb7df8836ff570699a935d92afe9fa00ea527b2bce6c28fc1c4ba967934d16c0f1fd92a1b72aa45c6073124b7d8b357d6b01b9fc22d3ac74486aa59f143a2836123843ac93bf3e7ba7ae56f6477dd2c1b2fb2766749acb6b069f6dbed43c0d254a2d8438d9f72c0f379fb0a94b392d8fe5da415d797ebbb04b39db3edcdfe5b50a1c747b06705d7f95b0089c11dc7428c581bfc252665b6c02e5ea5bec76003527329b008ae7a08b415f04be29d85032191131454d4a8e755ec9a14797bf6962419942033a5a305eaa8beac51dd41a596d1891324a39196305b17e172e7cb81ae98c3f28dfbb45953efe044bb9cb57759907b6703e9adb209abbe3c3cce1fa3e02ca6a9e6d7be88e3f7e077363a34bfd4a4bdedfd6e6ed21d4692dedad371606f7322635fa5d9fd0bd1cbaa07fca13d91d88a2e3c4f72f28ae10642db9ceda2c5aefe8618127eac5883f994bd5752886056485c4d19df41747a171874319e0c2dff04ff046f73ab45b5f4c08988985cd3b6ba3eaaa3343cdb7b7b640b2f47b564d4f9701d20d94d3c0ded50163d3bc4c940d5cd4bf8c63f35c43302433bf7b059b5c8d137611a90ff517771df7eedb12f57dbc4df028d6b73d300e9aecb05645c9b7395f05006a966cab9766c07effe74df4407185cf8f360d8392e31cfceb279f6b563d3459c7c2df617cfba99f1a944eb4e20aabccd5c91b7cab899381ea4d21cc549d3eb93183187b64c22a72d1a81825faf4cebafc2f00fad28782faa20208b3978dd145c098209741d3d29f6937ee638f7c08cbd7ff2db62cd6b7442de25fa88d41028a84fc356cef3724df099887520d58a6ad272715c0aeb914e98bf30219c94b3a5f41d417a2a54b60c0b50aba31a5717651fef67e483939cfa1058f96dcc05a6b505145940b70c48c934fc81806dcffc02fdfd1da77767f52304491003a77fe422e424ebcad3ace8421328ffac7eab454ff81ae6d2dc53f7db4fef06fe96b09815261292fa2dc15eaaa397d084e3bffe30e01202e3854508ec5f36d6e98802979a462e87848a1c8ae97670a10b00cc3eee2a7d4e7bad56cdeeecff5d9f14bcaebff84d70fa084de43731806fdb20675a581d09b0c2692d371f0ee75fa89f3fa8b1a6451b0dbab019e6bed88a8ca4a28173976d61c1e37e11073380449a94eb4a719328dc74b42e1c93c9fc1c0d7319f48d7cd41753fa04f56a68ca6467fea5108e0cb1dea11ade5d93cfe7de95dc5488f05ccfd213ce446a6832938d42c40a0ec94110c081c83532cc6e45524182df9f08f414fd38317c332e17033b9d6340c95ddf0dabf460256d69cc78c9f37102aa6326cb9091fb3d31654056b3268b72a937d5ec28c1c82ffaad440183aab19ac66929e58b9bd1076b82e00e0acff4a99229c1a6122220b8cf1d49cfab2ef257558f141afa8ef808047029834fff894355d97d1e701229b5d4ee35b325a70a38e1961cc9e922a5df17d45c21cb181b34b4aff7d147570c111407b8623ab4622fb05fdd15462471af126fa55d913c5e1b3f5736c07a6697d8f785d87085630c731059bacfcbdd8d678daf6473a881988795445a1c24994b662c755196ab35ea75c721b55f60ba01114c0497d78192ce2155bd614a8c6d4df4624b5f2e41d5c76430fef8b8cad2a2560c2d4aaac100b19964775639b195d98416a02385f3042a7e909f69700273baf37445ac2623cd99dc4df70cbc39b3a3cfc0219dab346fc39b119f3576e460f5b23109f791e407de240c80dde6d37b83515e4faf265faf3af099b987b55c116baf4a284497b2b60ccc1190f2637330cd631293f45e89131711ef6a045d412018f23f0252c3535f3db7a353808af4506c6899b3960a00448a97d6d7e4ee57879f99fb5acd1d64e74d79827409c7c041df86f35e7806a3f1a520e49fea326901f15c1dc0225865a1ff4600723ee957de1e1d571b783a2bf65bea8e3559719057bd4c8f26aba13af6b3b9b5d55c4156df4a28449792ed34fc199cf14173309b671a20f15f90d31711cfd504a082451efa370290e7935e656e511d0269761ec552429ea5749d1192174e83a7beb8d5a6fa92fb92e1e6c770824925ed396ed134821f72bd59970c476bd7dd23144328d92e904a85643ab683767385649a87d9ff6416ef3fa6b0c08690c40b956fdf45368a5fad4dc7e3071e3c139efe282274e0c7838a16da3240ce3c9d04d4f95cec758a9de301d2e1d89a1b2752cb751c5a82d1c2c8ebc9ecffc95921f336f17c0cfb50e9a4f048b903d22fd64193d599d3bbcd643e838d72cd6af0055d7db8946c22235e4298d8dc38ff8d4f811840ea06d10cd9789308546ab5554319ce21c3a6a3857c82dee1e5dd30b48c64d8fee9baf1e9914834e4bf38f871e88e0e699e1f9202fc67828267546a1e55badfcb33da2cda971b0c1fe053487f87daa15f0ea268241aae9f03540d109a5ee772aedf4893c15f8b2caca08c460d2e7994d724d11a631442a4d1da92ed995da6273515fcfec27d388e2a62a92a52f26ab5cd88af9bef1a87811619f671270e82a572a45ece4d8ad21052ec4a112521c4d052bf740ad64d7b335c47c5cde3753400cf42f44f2f1b7ac52452b6a57293b82cc21a0b90349684eb40d70936f9813503bca3e40c35526b74f493537340d4024b472c2029fc22ec7c537c7fa0557e66693b39408dac1eb972e2c9418e73d6821fb5d6ea8fae3a41dc23144bf21c2c0f4979043ff09b64d59288e06dda237c037f446af4f6df07169405ba06e530cf8af4a975a3701814f8da05fdfb9044a749e08cebdd7847fe02a043d8698c8e3bdd6b45e6f4be1c056215c9d6754100b82ebdc49ab3ced282e5290c2599b3b88e0fb1d82513f8a7cfd1302b3040983914d004db4eb9400dd19226843e040e5160d41f0b650c6d66ec6bfb21657d52db97a4a072ca2a39d4fe2f19d7e54591622e80a434337739ec8822e589ff63c432b16b66690628db9523b51ecaf79506a912e3bf10600e1e063a98131fefc4995d450f07732216b55521419ab66d634de5e642e835b0726019886b09bf45edc3bd09f3f511d3fb50e0ee3316c9d052ce99efbb94252b5ddaf4d1f2d2eb6322906f4be891f5e06bcb709df30c267e559ad72c58b98d38c7a9894de601d5dbf2dd57575b887eb92a3b129aa439def8e11fd66eb0e279c700c056c410d8753983bc0983ae9c35e76e5aee3fae04010ef0e744460f241d642a17e0786552bb266d689d94bd01d7c59cafed4d4d0bcfcb57f9f367c77173c258f540f473b4cccf298c7e4ef4678e4c886f2939442af3336254acc2902884cd5c748ae54d1a2fb450d472d06a00ff57af444373242a7162b694bf56a57d6a1e9cf20a3308d903f215fc09a4a10d7635428cae27521ab880e22475704e961bff9700170fcc1bfda3e63024014e2559df23f729cac9b481a7d16a1ecd04d8370df979337a8eaba8ec296522a7aff6837d9bf61ec0ed43a50203832286ca91c5e2eab1a2dcf5b354386502f07d2d580799305a02ac9ec58c91527b3c8f7eedbc98cf770f51add31ea34c4a823ab3cc6457d420ede21547d13f7d2860aa16122299741c9f36de441d138556d0dae83eef445319bc8928bb885356e27adfa2adb9eb38b0fdbc16f360db97fa33b1c51bff40971465deceea723c414ea07896d3ac42a2c68da5859d413d7f0ffd17a53f37593f3464dde618eaa1a489c6513a2a9727ce10d9fe87466d6c345f3f349022ca1aef0764efc6c65da668a47e99682f396aa6c144639c3a3c7ca967fbe792363723fd43f6f1ce8127c3b26cee2b040eb2d6364ab71e7bb1af4143c8bc454b66f2eeb0b3a142def02baac68869c3287e0f271128cced18ebc1f2b7c54aedefa086cd5ae0139e27827310fa3c99a28207f75b5ad3d323da3037d0651fa3ff5aa4b6a16ab50b8835fc64594308bf1a1688351a4e3c34ccd57f93d508ede0891ca307b1067385652e18b1e139b68ea13aaf0d10e458ac3178dfc4ace174c1632833e01daa6e833e3ec917651974ff5d1be81910a9e96a73570fc51c075b1c2eb8e7bf7cd2a2fb42d80c53ffb68d367b3e2af9254b6afb8e1e6a5930488cc8592979ffd63df8f7133612e420a8bab704d8e903583b80826e9317299fe9d3cfafda9a9c44e6251f6a7662c7eac2ba2bb0dfecd0722f7c0aecdb4b8df27f082ef5d28332e55e168943cf016847da6a26c647bace4f5727f71a21a65de3f395d2266c1ba2d109219b93d2ca5ee22df92047bc9b82ed3585dc1aceb8a45d12d9e31d9666dce0d3d3d4639507c92272ef5e7e6e2b242ae59c84c8e1e066c20e08a4ad9ffbdae2377075ea5c9fb8b9a642759e983abb1cb9246821ce6e785c12b82426f9c0c52cbdd815e394b98b5dbd74d1b2d898b4d6b5fae383b1abcea5ed2853d5d7c0d3e8a0d061a507851a96d42ce4129fd97e0835a1ea22655fc07b0d69f0d71323ecf316bb673915914422500f9ca730e9d7fc50d15e7ff5abad842911b6a481682dd8761e05f1fa5a48fe51a8788f0171149d1554d1e5afa4345282e3fe0a1d86aed24bf0628c82ab0abffe1998b08befceb69773d57b54f54b41e7fe7e664d9a6b680a0f164062dadb3bfd92ca3a5af62bd3e8d340439a878f473a342148ec1f1d3198edb0b76a7ea907eb3919f642205e18175ff20aa04990093accd5316903a6313c9490526600cbcd68066924a9b3770d9a1831f251181824966a0d4406af57aa2dcf4481770c197d1bd88654e81f6718b01c155a46846c74577f1e8c3ad197932052ce92732f6204231d78da6a4cfe7aba40092ec5b2ada6175e641b3f5cd76c38ff6f1fed8259f2668e372595453c98ea17b4fc53cb15818675dda5f1f87c0fc9c2433dc931704dda6ac106a5c531ae9d52a6d6b8b02be97bc188d96f871349fd9357758d5b1d7cf8ff95d2de20c7538b2b010d2b5d534a9b32d4fa5bc7495d5be2244e3931b71b0602357c81b747e238fb3ab5350407bde17c2c364760f8fc58516cd56566f90bfcde3b6b1bb93e7ee30593fed726412bcda51d47690df8af7ddca3919492dee5c6a34a25f8517efac9587c3bf7cec61ddf8cc18eaa168c954146f0e3a401020bc75c2a8f4a88b023e1e524149ea531278d297c3427b20f7ebec467d1711e282d117dab500338cc68431934160afc67440330d85a691d969107802020eea80c18b931eec858b20beeb09fdf78b11db45439d058d64d9a67fbf0efda553c631e7a80e0d42588efc01fa9d3367c14265f7419c49293f743272bd70734ff5e2039b49d7a38f5c73fe7a08c431915ed5418324debd5ff2ab2262747ca7ad1d5004b7b6e3223c68ee5d85cba7065e6815acd627599b9d17cf2351ea25dc49cbae87fe18b780a029ddb2fb686d26f1c01d70c0830dc4d493a45fc42cba4beb5a22e1f428870112477a7e163189c32fde5067068084470f612f24e573fe85dd451258f522572fd3fedd7e0fbc776572193cc5ad8cb948b0eb55711ddb3c4475a1f4d3c55e54a02df8a63eb9c76758fa23b29f63d1763cd565b781b5a700c3f029b8ca768f74a84fa8d8fd2e70c0d01e63fa7d5388a0d11ce6cd1aac6229a39aca29e74b354ee51ea5d0bca91fdb7ca12bd07c0605617808ab616148da97071ecbdcabaa8fe0498bfcb4c41244067e8feecd1a0dc2b427fbc97fd9af8da45a187a13a712b75772a730189f49313be5a68524154fcab2cb9b30bdea7f5cacf2e66f007d3f79524ce279153403aa49832fb27e195f543c26d44b2298f571880144aebf40f32921f0ce25921b978d8e8a77be6f477dd3d1738d0b2238298bf6d8bd7bb278d26a5fdb5f35056b803a1c842003ce48e690422cb5a60b42f59d6f42d60a0762cb34c809ee65e54be92494bd41880dc4788a13446cbda260008d1294724ca80b7044244e4a1951122a5e33243314446b35863b4acae469910aff0cce1fd80929695de18c0721c3396db537d83a92561c9f2d10205cce9b4ac4f68afee3b2fd6f5041fc64afa389914ba4cab31e56578df528c279e80f05ba586a9c2ac65820bac3e28d5046eb58398f5def42de3feb2cc54496e2766b3c5fd3a9cc513c5ac6cfc5f25a647d4acd59d05f5127e913f3cebd32032d7b294d2a8288338fca365cd4c53a0177e07c75cadaf664d62ea0c67b8d79ad559f640fc2fa0a217fca6cf1ace8124159e10f00b045813a5af03df624f642abd66a538e71585f585520790c05d62b6a958b02390dc6141a93ea25e0dc974f489996c56bfaef18cd105675a0a704ea15f026b2a63b0aba1cdba8cf86e15e92e534796b63e99a2a520c8daac89cdb1cdba52d312c125c756dd115e7f3f73d768ee08c4ee6b9bb5eee190d59f92106b6973b3e6b3c3ce3eeeab11dfca6ed6724ff77aa779f42b64450e629f6279492e3de70a5ad19ac53e01eb80079ee3dfa643e7788d2f9abbf6dd7317c0389c4febe87b6c62409e31547f0c46cdfde7974e4f60e3d5d3a6d9998d6ecf91b284bcf94ef0cd1f2d3c516c5a59b51cf9877d611064266c135d4e31065f6fc02d0bd5719672640e44ce927a0e17b614414acc952072b9a88203316f0742f02d0da433590b281fd24b23e6f3463811ffa2c579207aff5b8f28f569c70bd24e2ab33e9d8f60e790597d4af7e68d5e8a5e0a5b49e8a9a970dfaecbf12160d50826a8026c021d041d322f2a4487540657c66ec6561fc45e604c8da6dbd410d4662a2af184d2dd56cdb31033a669301d9d5b5bfc4c03143bf3889ccae6df9ede544f110192efbc415fbbfc86cf9766a63e1f3a3b3bd4e36edc64dfad7eeaac44fce8d4f6278c8fa983aef9111865eaf7c7187b9b06f52db3e606ad7f9549276f9e1844aad68a8029932bd7f8b8ca057de29f28df8a99e6b27933d775580f23b6cdfba57366b2103069c56ed6c1daf301ce1eb7d962ff0f696e75acc61cf94b0ae6a77233c9d69b1a2b698284a5d384afc0a99b2bdfc0b84fe349e6824dd0c9c4da058839a589499bc97282ab409df0eef02d2e9531c1e74b6bb6a3a1140aace0eaf188b5ef690e8a6fc1c6aa84f25ee811121b82429ba02c61ceb68e3ffcf861d465ce248c69203d4d82eaff80d03f0346f4cd3f9446faa20c4f33831ff16ac72a93c641a419c64decb30810b0dc3f3080d2f90154b9b5bff663b4bb4c6bfa888f1abb6afe932f3496c5bb8e36f51e6ceace773b0329318cfbab3827fe86392616593d556b7f2bc4574d3c33e2a7adc1993a1aa33b379b16cc273292a21ae55acfa3835484edc159adb1ae247d3dba5b74b7246f88680854e202aaf755655c30165aa5035ad50d5ac0a4af1c465dfea8daffc38591dc620d6cf8e76aecd8cfc058717789abdfdc5a9554800cd7b6d79acf7bdcaf007026fda845ecf313b068c73698a1f2bdcc1628ef05609aa17ea39584ecbdf796524a295392013606250676063b6af5c94016b82d5bcb63ad0bd6eab0b6c7da1dd6fa58fbd3c24e3679b8c9b950f3db6899a3ebc99a84ae0750eeefce4be9e038182ae5aa0b1cf6d70f6353f62f434ed7636f8e1dfeef60d8a270e243de7c7e5ee001438f0320a0eba17ddfc3e1ede7051e9283a107e50e8080ae4702b2cfe40e0e9437468eb0df713539da649c7ce312c5d1c8740270bf2a02c7e5c6aa2254df794584a87e5379a064757fca6ba5b8a442de521e902053e010a7250d1538b49937b2f540d9f9805d0ab97a40627096dbd84adf33fdc206488f9537cfc77fb8e937407aab3140099ff7248c00009b01d4d4a43297400db6b8a96cd7a37fc7393a2f9ea103ae6c2977b52d5f4ce5cbfb55fa653711c614ec9156b17537f1bcba99c3e6d25cbacdc9c1695d463c7735972df0cb6f5746a9fba6edc8cc9b7f3dfbd117d772084559f63522e3f537ca51a15dae7b2918c1edcaa10f659ebf5cc8886b81c3ee12769798236669c459f2a550bbac607f5924841b24652fc7256e00cb9baadfc6410934818043592d0cf3b7f799bf7dd141a0dc9b17f6f710cff75ba171d8df91967eed5658261b6589036d64b7d36b6bb1df4cae7b9f5eb79046e96fd3d299c426937da10c45a83e63e223fbb00d7088e77c0993659efc8c49cefe2af19be9b72833a2733a6759c644f5f331ccca6ff04f4f87b326f73b9235e7fc7bed776f3ff5f581729665d95592a953a6cd690e4b796edc73df8502873753ff15f794fb68f7b791e7b282c25632c77d0cdc73dc03994b70dc5caa4e1fc3709de6db955fb85a751ff785e0fd5ae5772f3fc579e1ad216b7fbdf046bedc77de7490d3761c286bde1099b36f2ed138ea5f6f475afa59a0866ef1fa38e3c5bcad6810c1ce340fcaa1733a77fbabde64efb973773a1c6e2e70ff0c71b2bc1c8cd354dae65ca1733ad3190b5799732a734f7ff63efdd90794332f9c977a40790ef16cbf151a87fc1d596d9c0b4a238d300ec6b97d8cb46e91c619638c51feb66d5bf470b49777d32ad5b22fae629441e78b40b9729101124600808d1a32312b1a3364c0c480f18255a98edbb4ecda4aa7f45e2202b72db2b2b2b29272b22aa2bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb7b85a4bbbba3d8a2cf39639c33cebb74efbd59467d3ad76fc641cf3732fdfec221326a73c96a772edd4be712b4563a95e61274c9c1a65fb3c9d530a59558638d754a2bb34a2d6a9555a416555a91562a9d5f7d4eadb51facac601928910e910e914e121d984e121d221d22239452a79261b57eb413e660b495ce1e289d43f3357ba074c226cc2910b02d52905479014a08ca9523942d4f5baae408654bd39627b624dd0e70b199456c0bd2e450d01b0b6825a233aa148854125b6177cb6b0b087086b4c5f564ab6869c2820a26b4c4b4c040cb0bb434c15a946881e5e90313ac25e8254b148c293184402507597c806fd0a54123cb144c650559a25011ca9294e5284b5116a26a65c6b2e8b0eab68c804998598266f03f453d3df59395f954057b73067faba7a6a7d813d2bc22c8075e3c1df1a4e409f61484250acb1316272c566059ba589a6031eaeb8214604e6019c2585e36b0b8ae68b962a5872956ca5cd290564645e095d7952015666cc1d11f95fb866dc5032e9a3cfbf37896d91ff4580a82b23fcc07b213414d8172ca2107d5942314a8584eca110a1413285027c85a8e50a08872dfc814d84023e40d998e17de30e5943df017657380fba3b4f4d26b69a6d55ab36ddbb65a336da3d756207091f33807a56c5a4691c03d54af2c5d295644150d11f59010d71d52830d317b7b33aea3cf554a29b76d1ba5cf755b66af8f21cef257a5accccdab81a32fcc5d4f08161cf6d0100c6608e3fb3e2efd1722574fd97663c8f47eccbe1a1cf421426c3cad54ad0979ec8ee2a07f2ff56ba9876044d1d2ad7bfbbb7da1e7ede36398ed7ed4386f86ce5a0fbff9186b72ea43b08b35d226d37ebc456b7a08047358f3859fc31a99bf8f0bee36ac5f5e60981638eca55ef256db58752b81a0939393d3cb56f14a75a6848ba02f86bc80eabf760a79adbc564a79efbdf727cebdf77ea17dfb5d25955352a27689a383e3dff77e3a9ce5bf34add31860ce6722484120597e04298d61e619e913d9ac7d70e646761c07fda3df449de8fe336e63044309430466b29c9e00ba812c9f7a4dcdce0e094e78225959f2e2c88a0c29529c46d802c6e5888bd0e7057fef74ff98a5acb2d69fee3b4ea9941fd7f1c3a9418931c8c148698c34d2ee26b99f1ee5fec221d25ff54573a0542828074afdf54a41094aa0c4d631c621293189bbfcf324940bafe2593815cfc2ab78150f224eccc5e8d1a3475963a3e3d97f9331d22c94fd0b65885ce477c3c1f620aa112967a553ca1f78c07d23f21063bcfe4d07dd05b19bfa0a357105b781e21584a61811e0e9e08e2dba923fd597afd68709b96c6ff6aea274d3b8d760bc461f675ff8956e4f3b1ffaa9dfdc57366e84e171f677e48b17f30dbb49e84bb9934257ca5b4d6d489d73e44e3a27e6cdfd29a7aa0870e74ad79dfcf61be5b8bf5f2803e74e6678323ceb4e1a0775ef9fdeeea48fdc89d26b5030cda77c6951bed686f66354d1522a18f345fb2aed7c66957965eba99bf7d3e3a38399bcf75eae4ba9f00b8cae078c97a982cafd37e585f32953dbd1f9d4b42877f585b92f04baf34a4dde5879f3f22663939165166f644496fcede3bc31478693af6c09d8f2f6cd2b8d83ae6eddb82e2539d5e4f00b8cae478c2f5ee936b29c57669579a5ce278e698b334188094971244551aec97189289e721204705c220aa7fc392ec14425bb2655cf7961e790fb97fa1dd439f6eb10bff7e7f729fb859dddcea7d6bf3d75f5e2d9c0f537dbf55459b39cf2c29909658a83e8cba4bcf5788c0e58fb42a01be30b1997b3b761d32e015bcebe46927938b9dc481a47ccf4b3ae87fa5b22f1630c9f98e3dd7c464a07e5e570ad02cb7881e7ac745e6165bec03dd064b8e0f9f2278daf7dda7bf7c28d59d3b2ec7e1537163573c659f3a30ba60838bc795e0efb0d70b42fd841ae736ee42eee2bf6ae6cd713636e19654cc1087ebfd7d64f52f1d4060cbef31d802394a426775e6e7edbcf3efa82fd7b3ff6b59ffcd956ecb6ba5c2e827b99ac01403c9cfdb69583dbc338b8d5a71fc2e4ed756c2d9f78c35bd96fdbd7784bfbed6dbce5f33d369dbc6ddbdfedb96d66fb2c10997c8b1d76e0c1872243433dc0b4df5659f892c3981c252bcee9bff5bba7faac9b1cd4bc49cce1af7d23792bfb9a4a11fc2f73e28f1963fcd3a3f3f1fc5ae9e37edc30eda5f6421cd4b4df714de3fe65071cd6646d9b53fbad230f98fbcd2badb4d24a3d4bfaf167f6af9d228df62f3ee0ecfdc31aa7bbbdf4686895174eeddba3a17353d6de7ae190ce6b260e6acf7961a492b5e7a8e0b09d74985e1fb59183dac7f800d6be9d348aa36ca7ac7d8db7344d7b1b4dc92b6b9aa665ed5b48fb09f8cde4d1e20cb216a1642d4691b52ab2f65183ca1a1728214dd3b4ee0befd566b4cf02feea74303404ebe1fe16e331ec222f0f43caefe5230ccf06209e9797efe5a5d7df8b37e7bf783586200ededfd18103c3096bffb203be5913eee517e22bd8fe86df7a29fc7556bdf5ba8faa6b8da8be6ed23ee05fad42a7d97efdc203e65ef5f6b1cd9e7a3474ca3372bf7a3474eebcb08df27dce0bb3cdb3c1c1fb1a478507f9fe7de9b5cbc1fb9917e308bedf47f7defb5388b7eadf08e594efbdf76dfc66f2dcf701c45bfe37265f2f5f56be1f439d7c97f2dd42e5be387141d8e21637d5b5dd376fdbea716c18066f1ee65248a9f00b8c183032522cfc0223068c8c1934300823068c8c1934563135f972383a516905c210617e1c44e1c2689a0c4d9ba16934346da569319a26a3693526101f0b583de8e365013035336ec349d8521e10793382112e95b2a919dc4effa7406fd588a03f107913e52acbdd8ad16915636c0d1b00188184f7bea689b1e38d00a84878ef2b61c6841354a9f7be12664c38a1eb8142aaf3be12664c380105ae763c58dd8abb1f2e61c6841350005929d44fe7572b1c7ac227ef8c1164a510801b342ae0d871b01f5bef66b04113729c29e1068d0a386a04b0c2833352fe4cf61bd8f564d6da40a1ebb152aeecc5297012df96f5689a5301478d0028b742e50660399bae0750be915b6e28c82aa7c0e105008d0a383899950d015a373805c891d3f5805981c60c95ca86ab99187304e8529d4fcdf5bb05c891a36300165ad8f907738d0dd7a572e4e81880851676b2dc705e06000393a36300165ad8c93c0558e101c890a16300165ad8c93c2ee4e855930ae770e05f0ebc91e7ccca6d260d1a1c0b2dec641e1774f4f45310671b9f57dec24ee6714147cf8eaec7cac15f11c1e6e06acdb5f2d4ea42ad3a6aeda93531d70f520179436b24ab3ef57024abbead61ed84f94dccabfe2c6282e42d9aebcf256fc5274251e18ce24a4439e4e851088e72b8aa4f42f0aaf5672cd7c994ebdb76a55a25925c83c821978372d85272adf5697857b27c3edd5cd82970822dce6c3149b85fdbbe225227e4d865996a2686096cbfbefc50f5f76be44deaefedfede7b19305ff5b16380ab3c234770db77deccb4abcd948f1d0733957cef6fd98720e837d993cbe522c273bf46c75b31df7cffc7c72936f932416df9462db33193791e9f84a017895e27d435c208941e459afb02cac4647dd0ea83561b0476ddbcb35a4e2510a7c6efca25cb5afc12438a686316b8061becbbfcfe7790d5f2c88b28fd317aa1063838384a70d846b8febd18af5646f407e91287e21835134e1ba586805db66c295527982a11c41c92625155441cf6e492ac36e24c41aa10390d09654be91b7925188131c55be0b08d421c8af32f3bc80d9cecba4532314a69adb5f6c82821a304be55b26c0993308f792c2aaa8dda48ca2841bda04260336c49e214248817386c1893924dbc11c7d594ae53d629535f28496962c147e6c442e7d03a651019659451468ff4c96d9db64e23d8a313ed25cf766030793d67628a526030180c06c284c061c372146f5179d365300db6c138d885759feca2e44e741152127620d3973904c23f6855119992e2ee430137c8fea1a6b4b3730587f71e799efbee3537d583b9236fb0adb70a0ee353ee1c7963ab92f4bc71ecd4ec4425f04e400ae1303e09e172cc25a3c81667c2de0109d92edb65bbadd4565a042c7fbaf5ead4a7ac74563a8980a5bbfbe4e24e7dba743a259d320abb539f2ebd167947c96ca5b3d2a93a02e7de28c447fd91ac14979863755b49ca6692dd94d94a6da55dc427d927e6b861a3a5aac011808dceb624e9bd88c3dd0ba45f9d233f07c41c8ce0e7febc70d8af259b2852963ad5109d232b9d2929553cd02955510ce91c99e1d4a4806da5b6522d3f6da5b3e6a5d259e9b402fbfbc79f582c16fbf97921fdc89b864d29fb255953e030c6ba2d6ae14b67f573315eafd77dbd24f8ba5f74c21e6315f020b7b93a3118a716807039f3d7561cf4dfee132c3fc6228d1638ecd7cbbfa3aceda248e30f448826c28a7ed97e7181c3d8d4312052058218933f13c7411d1c1aca1fa70687823764a073860ee496ad1a0158a55cad1cf4d496a758ec865cd954973b654fd93de029bf106bdf6f3dec986fdfebcb5239c061c7c27e65fb524a296bba630c8cd1693f20170a0e6fac632edb6d3677514f01ca742121e58911548c8b299804d19d40cb0f497881e58814120541b2891e38ecc0c411d712633164718287a418f06004125fd3951ce450650543d812d45de4e0209974122190a83cc1854814228824b19404145d24418310b80089105a1cc112693044144060b1031055b6a822b422821f3c7043135eecc061b0ea7205890dba48a2031530ac60892cb080558195e0c824c10e8e4ce9224b6e88d2c4143b5c9164c8135b3081b2820840182208490898ca91a82c4ea220b1c4674a174d42c4bc783909922fa8a248d441cc620827945080450aa11994a024892e7430428b2b46ddd442907321948194589185090e401082272bc0f1031f7cf18414441011c20d540039090289278cc0c189245370b0a1e3a7c791a8d1da766633055fc8c0265322f260ace1ce4999072b08820a23a81d68d0e50b27393a71840a9ab04192144de015fea249a64852583a25849843b23c75855cc5d4b8f7ba70189d98700f3ece17e2d8cfe7cc81aa82644d1264512014b45dae5fde544082f2a683e4e3184475030ebf47f7de654834950f584a2965cdc67991a7bd705593e38702c8311c404da4f10fd239f21340c4e1ef9f0ffce69da7bdeb93caaf031c46a7201cc62e3dcd4bf0c0d62f70c2044d8d1f36d00f07154bb878ea03f1d807fa41fffefd6ca00fc4133f4bc29ff200fe67a900a90926576c4941149e7e20fd3b3f3f842458b878a8177994763a67e6992319a8e4a62e26a010421731c1044433018595d6ba99300153b7bb8fe71d0793c0e16faa9744a2644a2080e8926790d2df6f5e5c3f3fa27277751d119f1c5122c7217a4a5fe1f477ee27723f092acafde44811f7430e6d7247c9a18e677f1fcf5e65972e59cee81cf9713ee0cffffd7ddb7bfad6054f3ae79c73ce39e79c73ce39e79c73ce39e79c73ae82204a42a59c934a2aa59c53ce29e794734a29e54c72c3d63f30fdeaf23dcbdc7dfaf4e97efdfa74bf7e7dba5fbf3eddaf5f9f73fafcee9def35e44c4a6f7372507e96dd2cbb52be0c4cd665445a654b9f34da68a38d36da58239ddd8f76ddce5aeb8e95da660b220abadb8acdc69ae5b86dae5c7f8b91c9844d462907a1794427132de2049ad588c49c266a1c95a80851e7b4527db9dd23aa44e40acbe4011d8830c5a84f15aa72a91ca8c3c15a942bcc4123264647b91239586b51ae443526094fa35c796062a4a4c9c11a66514c411aacc8c1aa19f1967f31fbbf28aca7a98d69ae69198b49cb9cc95bd4041ec5fa8ca22175e5fa3d7da957e307fdd5af3e1b70b703f6afb123cbb8bea234cab2a82cbb9f45f90d0a2ecda50969304d890e411a932c4a7be96094438d2857ad4833d28e72fdecdadbe4ad701a39792bacaf5cdd8d5092432e5f2a2185d130548301333840677ce4a15e0d06d0e080128d8f3c9910a64792551fc9a514946bfd507be5fa3220c0e1846d3a7e36e9a29f7449a01a3f56abcfa324ab4a56a59e84d558b75c72b0324929b2c95b313739f8e2ee6572c9a378cb934b32e66094226ff9d7bc6d30130623a3dfc05491e1de7c3958dfa35c1e255d33e893403cd5ca2a0ed68fb2519443523b11d3256f62befeac296f59aee8c6722fa308db24ab94f2b497f1224f8c1779ac974515c9fab87e9125a2a722525edcf6935beb4b4c3e30e9e6a96fdb9fbc1525d79fd9ad59d40e30674d6006da782c6977d9f15882e42da4d6523f8f25491e8f254b5b70528e9d0f8f254a1fe7d19166278304d3a29a452d45240fdb08971cbb17ac3f8f25473c961c29cddc97c792240763e6b164c9c198ddcb5ee6080e2f9357e347ccd39807e291f16afc90f929f3403cb1fbb1fad5f79255e96703fdd5b792acfa32bac0e165c2008e4f6e20cab3681a49968f0993acfa991156fdbd4e92557f93dd2fa95003396651de02ca3c9628f94d7424bf993067d58f99c7922699c792236f01e598b778011c9fdc209463d7c363c9518c2f3c963099301e4b9a743058ff782c397230e6ec29e6e85ceddbd8658a39da2e756c474dee2f26b5f5c06c95bc751d69c95bd15f6ebd51e9e5c3500f50b9cec9535fcb6e16e5ad1a3bb2dc4c0ed68ff131d7cfb8788b0ee1f2160c23419a90b7b497b786bce541f53b8f8696e1d130338c17ba925c5fe5d130ebcb2c8a86593f861762185ee851b9fe56ffc50ba551ae2f8de44de4914c523a49d8e5a991a7767f918c922bfe421994ab1258abac2ed9a42499a06245ab5056a8e4ea536410ac71d40fe50c642a3553299a4ad554caa65237954aa56c2a55bfd6243efc870724d2546dca2b2ff6afc60c9d85449a9825d7df48d001d78f393291e3130f44c93487add40aed2bcc9e2ed3d1d0844da227448ef29c4c66935cbb1d32790616728c34b66807b7b5cbd9ded6bb6edddaa6b9c1c9b6f70334d7ff58f9c09285232b71b0ce2a1608192637f970f9461afb753e30799b1ea54c75dd2e270d6f9e0eda1b6d96ea2294289e72e6dd486333d74df36416664251908bc887e6ecad97fdc89bd567ef43dec47ccca7b93e1f3b92152377da6f9917cea29c7dcfa248934d0993b04893f9cb5f9126cbb2eeeb2099af83dac7dfda372a69b64fb355628b1cd4aca665d7e903505a6661b688e889d092ec76d0b730cd165925de72a297b76afd0cca5bfe59c0bedfa6216fddcf0a3968996cccbbef629232a4a49824bc69d9c7a6d8592afb881cac7f0212bedf5acf092ec0a9565af2567fd5b2efee071273c85a9f4b823f7759e070c26e4c126e9883f5b5286c935cb72e06ccb6c384dc9ff5fad92007ebdb24dbc4226d73fbcdbb9146d33eeb72d05a2b43488975592342d615e42d6b9984baee075c634796336ee2608d97a9bd2bc5419613922af7dbb3f28603355e8603ab8ff91a9fe692acfa32df95acd5c77c9e5d79599464755d478b5cb0ce3689b231f7b25f79f46d93cb74a564519acbc6ea93b7b4af5fa1a2bc5545de3c552edeca549d4ae5b9124049c95112f701a45c7fc6e2930f28e5fa93c96f224f7d55aaebbaaedb687775b752c6d9d3e79c12a742a02d6eced1186b773e9dfd79bcf319972d23970a379063f77037d268cdcad97b39fb3026671fe34dcc872f39cb3ec6eb1859e14b55ca9a32a76ec78dbe358a83d5abb1bae4a08d231c9a48265713f4c9e5831cce231752376755aa4bb97ecd9a72fd5aa3e42044510e7b4aae358a53ad5f635993bc99f1f533277943e3eb5bef67c7c1fa42628e9be8a8942d78453164686800000000e3150000201008064462b15810a5719ae93e14000e6e864c6e4c3415c8824992a2280cc2181862861000080086808111182a2b0069b489b2b05f35bcd653cf9b13d7d75e07d2645250931e000e577cedcbc50ba76f9335faffaa80e10319fbb9d42d17f8682027cb9d02aec49c0bccade7ccdb0fde0c02e299f6f55fcaea0f66efd1b536a46ef12590a1f00ccf4786ee44bca43d096dfa081c03a8af4afc3074cb0691077779a0e115e0b481f7ddc99d03e2f72279073a8c9528a6c0f0d202f28de6a640b27640b7fa5880addd2da834e8927308863eab4356c136c37c09257ad374871b46e4ae5e17fe211bc02c92328747f4980ae09caf354655a98e9318b3366d43d6615ddf2d9d1f6fc31b9a928e00e80709a1bc6f90096272efe4f11d58e7eb078bb7737fe54aa26b43425cde6c52cffa18b25f49bcbe8202d2f06967447f4f801207cd3d1b10cadc4fa5d59ec69f70d0648893b65af235598e320fcd29a23e121fef84aa1bb59475c9e48cfaeb48d4cab55e63019509f62feb72d4377a7084c4706ab15d60e0a0bd1e698bb7e550cc3b085a67f68fbba9779eec83191c3fc786f284c3180aa96388df431260c3b62793934bc37819be80b870647b3b4bf724aacf04fca71e09e1e2389a13ccf0592999432b61cfdb102dbcd7790eb93b804b7332dcca20192f9ba3bf18a83ad04e2bc9ad6489827fb2fc2091e82428a5d659bae0d5962d00efa6b67f43ab7f992177ade95ac0e6ffa2c490c8098230fabe425d912c20b65292dc06f04b5e718227f6d91f8713357926a532918854551c716df9aca939dd654dc8ed699cc69cfa1cf54ca5b0b0602c0ef6bca43e64845817f9227b6d3104e7b55191aab27df0bb37c9b3fa4dfe4d24e0cf8660e4657f709dec7ee250621694f1b0e4bd08ee091cd7ed9c1ce29c582bb43f53dc2d6c3fead2bb47ece0b5aeeb21810ad1ff0acdf1c3365fbb0ef97fa5a713351c684a1b5044fddd90e5bd13a5a8bbf7fe0edc2dceaf14f1787a60cd8e51c93f0ef3cbe81e01888e8d5cc1b7118ddd5d5af28eb322b88d5286e6da4a69f8eb525f4149463ada06dc1c647a76794c2728b7a597061492c310e7112e63560b5df76d7693d54e3ed79f1df55b8a8554e440bc6d87d3b6443593d10e872dbeb9bd990c99e398abc1b3d7cc194605317ac907cef383900a47fb55f45f7071028bf9cd9945f6373bc289f2d2fad1ffde4c5ebd05b40434d34d54984dbb040b50d183c3f62460b7caa200dc701e2022f5b19c14403cded0de19b2d8cbf70fe8ab26bace05669168b6a69328eb9df4d2ad0cc181e4e3ea2fe857052971f61a1789bfa3aea007353d5a11b6eae8e50a0617fae3fe736c9593b757984ec92a7a281cdb91d7737fa6e9c4cffbec5385d33555e8868018f509f63db48a91eb4b7f88b4b18f38c1b993aa75f4b3be8364d5c456f7beee96d64d642aeadd0f421af4279db501cf611bd9df389dfd7bfe2df3e4aef5a7be1748b7773c803474440a6ee2a3a5286cff500ecd8d508a45419b103af1eaaa9ab8e85de7e2282b73b767c837a9a159d14eff81d289115d702c1265f407e8ecaf72a18d5a28d3602aa4ecff8a60dc8802d4e70db552d30639303144ce8f70fe9c66af96d3a6866ea3870b7451b9c8ef004008c91669381dfda188d5486e2e029ee74bc679bf278ea00c8ed5762d8937345f54f05b3a0463c3a2e8dcd4ce024c5452c5b57e757d68a9a28453ab10217aacb009be226ead43355e30c6226e078220e63c2fdd0a7550bd113bdbc68b82f58cfa5de5b2705e8ad22d53ba2d090c0232c77fac02fc749a966b3da3560797ec101454e9a0cf8d60f7a1ee5b6bc12780365e9fc34b0a616a6291b681e471b1744feb5b9f2769d98723bd48c36c20185b7070c2b54bed7f803d97ed53896bc449d209acf8e56a61c1065a742d6fa53c302a7abea67f98c3fcc76185ad2d6229bc07ad60238e950d0ad75cbbe31057c70c5dc372c1b2b178814bf46f43f3092759594c1c665cdbc535b343276df343f112476840012c43b4d9f18e68a87d957e2c470e67db67186400b900e48ad993b36c09ce7b2856d6af9d7455fc9872f95e00ee3d632fa58fc415c351eb3d71347d82fd6597f18b71b5a65fba32e16227344d59197f9137d1c2d5730dbfbfeb43dfb8301fa29f5271a407c3cf5efac1a03d1d452ad1ff5b42e6d8beac2cfca98fe1aa6c638b4d1e1a63be3434aa36637cc48beb2bc7913c40a629770b36d258d287b8e2765ae7589f1a6109a58c4697e163561eb8269fa54c303fb092f0e118feb2790cbcd0b2c4d6f2aa9ac2456e63af13f3e344eca65cbe7123f266fc9dc81e09aa763d5f510494478f7551a3ba30faec546f6a4c4aabedd253f724a07b6fd50feb9697fc52d5d5a948851b2813f8392e479c99e81bedd66bc3637fc83679b6cd8113dac7a49b4e7faaa154caaffaf1ee5e95c0722895d110a3674655b6d282c6190c4997346f3ff388f17b6e480f8761acbc6fbc5f7328072554e47aa30a265dd5dcdd1c0c3eaaaee53ce24faa21840ba5237bd894d75eae50a48fc5bafbd0fe879ead58dd4073b496bdfa7cf7fe429d39b98ce5e6736b41d5212557e5ebd9ebfd54acd16b750d80c4c420bd4645fecf56470af0cc070727197146528f39ec324536d0bd36a66ad49c0b63e367171e91cb1a042a9ba30c7f49fc35eb11eb29a6d3829d957bf563e992d4d6bcbf7bc1b30994707a9e3c3baf39d4b54ad86a019602a980ed2bd3d63efa536c5f4d172e0a9c8657ad2b8a22d9bb84652b2f5fa9f3c890b6d7fdda7f153f5ec94013493aedcdd4c5805fed7fa4a47edef1852d16f79f542fccd0dabd845801803fe550923a2e7d2d669cbb83b3759c73db66e343b7bac2020d3453949bcc45eb80e36c6462f125a6f6fd2a033bc25f20ac95c4122119e832379cf11ec143bdeb6c93b14ecbc9bd4b85465175440e056ef2e93c0bae10c0fa07ecebcce386a9df9478c2a722b1aae0842d7e695012d56d5dafa257ec7ffd5a3474caf52cce753da36f9dccc26278e2c88b78d9998e7e03dc71d03f175fd1887b531feca30fcf9669169a32282bc2453e286a0dd6deacc155b7c8bd300bbb637978e274b70c25cb63af7aa7306900dfdfa45e7ddebd436baf3ee9d2fa904b2417bd676f7f6855c3c7e5fd80c6fed33baf472db14f936da8406023e111771424a2d3637ed0a84b0c9c4792828c245f905eae2d7da9a231db7a2469eb55a1c76a6875982604b843f5dcc904fde352a4bbfff504638cf3b3d0848e1f2cb89f396dfc4c97ceac4e033f2944300014295b2ed09277859e688c5687a24534f322370e907888873cd095e544f160fe17a0850854047c076256e1b3f92f973c6de0c5197e543f226e2ace78ef451084f3fb55ab803462415f9a7ef806d2b9c1d5a20e46430784aba3885947338ed509828ceeaae14402b6247b1cf418ae233d6c5884e90b34077032f9c857d45954edf99cb525fbf6c8c7a61cc43c565b92d556c245ae1bee4a357416a1a75a15111a09c0700af18310fa06c1081b9a7c5a2d25aa6c987973a06248957b2cf35ead8d83d16baee66eefb6821006ae5d453e89f6e5bbcedc9656c7d9c4513b561c2fc79be34e275c5564b28c26cb90586b30a496fd9edba1b9aa3f2b4bcac851ddd54c5a6db0dff396522668c874ed941c345b10fdfd9b782394f8008a6c2673f770f3fcc59ea9f8cffd52bc1219c30079137cf7b624c3c4db7ad4ffee57fd761b763b007030584b54daa8da7eae3a7664e06b149fe16db01fac910032efbfa5a6c7421798fb8e4533329418d5e66aef639ffbefc113dc49e9ac9bc1c3e941470fcc5818cb45438607ce75f19188af23eabf46076d6549449068eb890e7b241c250190d417a2fa2385bed3fb136c9e84ca7db7b502a9ee198339ad347de0eb2298211fd8b50272fe622f70fcf0537d9a8ebbc818993c6a1830ea649461647acc659bda021ad50853a4199631f883fe683df9210c0d89e2afeac95483e68edefcdb8cae6b37661d012f30529cc69d44cba152dc093e7f1ddfcc4188b10630045697469dabcb9ec3a1ab6c1b9bcb363a63ce0c7435a011b7d14dde64491dd713d7fc55ff02b0f1e70da77ed1748e5af8516a8255fcdc531c0736056fa656d25fec05972a5f204460d2f432b90bf899e0de5285a6f7b73f75e0bb06106995b63323764673783207919c0debc2370bf76a8541580d4377373f75f9a77b44352b8fb301c72dba7ac5d617c69d852d4d4b6dbe8c903f2dfcb145a17d0282d002462fbb4a1d89132af60dd9c6c7af17a7a8ba3e39a48a890abf6953026dcc0eaebc545dcfb4f4f3f8d9444db769d22f4cb10f6847325c0e3a355e5dc008e7555160fa5674da1c6ed760eb738083a4f649c7883f50b7902e9b8e3fa5556a76b5193c441edd2e22b174ef44dca7aaa435227840e5218ed99160bc074456e79ff26469d0b340e60780c3858aa863426511b6ffb3cedadac7b2721aca0c9e3622ed9ece8f54cc752921ed539663e220b16a81cf6a758d19c4f807e2a4817d3dc06d7346c80515d659f0098ffb80481a0977a1d907c5c1ce03892f388bfb48668735a3429e07c7ad015d11cd3cf8b4917d0202ab262743775f7edc58eb6fcc896cd30c987322ab14ca9a01fd20ca7938d4542122f8c2225ac90581c038c2205f465622404dbaf03e3597e944663b0566d8f04510be903450e0410898e1a6d0c60c39a8ddfce730ce689fd023a9135ce801fdc96fdd3394ded29a87b834c724afa55a41e2b965695b3a52f453c7d0db88687864b0fc7abaf688e37fefdeab4dc1d3983462abf6d5e3155865108d590c9eda533077aba252905619d4e9c7324a4d6ff2e7fc17dde39c71ed1f04531d7349fbefdc1ca7d994ba78f92432a250c2963732f94705f2140dc53c81bcf52e2aaa0a571c0845c647bbdf8943080a794c06dfc27d6068c82fd090c6836486e6a3cb2661371ffbc1694a8e310dd753866b9e68c646665f0c9d402ac4376dd4810cfc29dba08142ba769660b477926c1c536d8fddbf2f25dd0c80cabb0f27189b09e5fe230b7d4d08f8787aa9ebc0989e1144b15086b07c71bc49111e72a046cee89f974dc3420063385a684aa0c008446f678cbece3170b4bb6b1d70953aa90297daf3a0a172cf5c5da915fa2608dfe64cbb3cca9db2575b27d37052a45f7de6ed739e928a744a98d450d6b207fb900e4ac0fc3f725bc6d50d7298bed17ba7601c247f9e6492f13210a795ecf21dac822feef80f4bf07c7e0a6099e7248691090a0c3dd034939338f26baecd27ffe3a9205d211d40df41b99c8ccd60d0efd80addc8055e22253b6151f8f942d4d803dc16c905ab2fe45d6184a0d6616dc5d67ef66d4b0a725046c57baf55b81526c9d18ad7744057a9e01fa93d8cc4ecca650cfa853ec383e801ce07cdd0b07cdebec81f7ac838c30838d9223f03c6170e4dc2e6e754ee09ec37ad1d1cb9ead205b1d41f496aed425d2d136e74e4f0463ef90387b68f9e291f6cc31672e4d38c1b5949e47184ae252d3fd36c97ca35422cb16b8d092a25bc11952d6db73066706be9b78835d668f1ff2d8be29d892db5a95e7c3cc356f1109d6814369214c30874609ed148e3236a9fdc1a6a2a2b2525962de9a5d82f32404125158adf58d852e3ea3e74a3da276e28130b3e208ed1aec70b3379877f04528c3891d6f6eb28e86bfc19574172d7552e6fb019c3c0ef91e06f75056c03bedb037adc232e9b24e83a083c13f74c483f8b6d7256d01e88cdafba930cabf7d25b5df996a045b70282ed921821f96d61df94580ecb30483e27853d4797b7e343f4e33832b782b023adac382a3d1eef61959a5fc393cd04c5f7a303f37a1644850df503ba1953f4ef4927067777eeaad8f19d617e12805685f52709b07874525d022ba9bd3137ac9b2d1823870992737fa7275f36af62f3a043618d6cccba4831cc1391c95fac53c43fdeacdf708a2b28a3c4d6c225137dd2d78829d5b1c77f4fcb619eb1361b99a0278fbd00eac322cf1b2293784ffc7a50bd536b4e024399e8561f806c1475b975d7335b9533d0f18b22b7ed2c69a1ca0bac6e59cead19535a4cf0385545e82f7fd034727a7d6da70e0380278495165dffea2e0716dd95912d1b2bba4408c13e2ad2f552b769c99fb91fa075138a7648bebf586e8c10ade795619e546ccd5a48035b9bdba345fdf162fa7e0a1611439f8d60f8b8c55ae5d87b135c7e046d3b80e789611df554cdf21f2c3912450525c7d31d5dfdff0f4451a5a9ea95440c9ba6f653e84d1a7b3a8617a12c0334eda026332fbe4d9c9dd864d55e5f28e98a566e49812bbbcb124703c87d0bbf117b7966f230972d8f947c6fead6d836a5229d375ff1fff59db95b4562f2929c10bd63092889ac53d24d03a6c4ff7e740f04b555f4d32a89fa0dd81421aec1574987fcf53dd4f180cee78bace985865868525c106e47a43208a43f58dcccfd0fa80736b3e325a42edf9bd817587e4a98ac008ccc5306328fdee421a379803fa75e5223db6ab159d77022c628ebf53213767c19fb9bf4b251f36e3c98552d1d5c1f9de6f915e3e51a475b58ebeb7b85de7bf1085ed34b20514e4e6cfd0b52ea55c34611554ce09c684b2017646ebf5987d710aabd7a401506838dc51b02d3e887611af908d31cb3bba7bba4d3f79dd6b9a84d3b88c603d81698eff3c1f45b2594159d09aab4cd50d8557e1a831a55f466c94056426a5fdfe4c88e22b49a957600be5f0909c017a11388c79968c45682bc15f6d985355fc44443067bfe380504271fc24f407e961a420ca4305796f21f5facc68072cb170282bddd0d648461259bd33ec60eff070204f447a488ca34271d46751f8625d3a4e5dd8d0ced800e86582341168b9a571ab6440e9ae1eaa9ccf9e1c75d462e7000587485e9171fc5e0d58ddb582273fc02c11f8952dcbf6fa209e23f1101f6fa6b24f5a9065d34d54a78fec09d54c0f44340188017625348508c59e809a73e92ff516ebd77731254d9af9a9dfd6ee5ad2fd0e9fa01d04b06d8827e6080dbf0d0844967d480ec9d1c920a4cdeb5b28f8a9d6d9ca5d5989317ca2f66aef8bb3100b8df57fcf0d104b768f1efa484f5b92c7ef9f2910e256b1d7a14f667f416533809f7c1dc84a9aae53559b4990d486cf80ceb890376115dada0ef929ade1b64dfeaea919059c01ffb5416f2d3fb46dcfd6fa795f9699f0610e44fb31a994544bba23b9e815ec1ed454d4ee1f1e5069e77a2664d8a8d9de28452289d3af7476c43909b461ff2ec63c17791dbb1e926fda0a5221c8623be1c09ec07ced808cc5b4d23c1eee2f04c777464814d742ba2cd022b00ddeca24a6e454a1e578ffb58a56cf0dad3a783686e7db24f1baffd0307eff538abd0588ab4007517cb411dee4244c67fb9acf48152c51f36b1f8b82ad8c4d6875b4030f2234e0852aaf0369af967fe3dca05526f5a94e6b06269fecbbc2e3dff3f112700ff84403c067f62e162968474f25e72e866e0fd6511694a0242be4b8a9b95a9ef8e10423c3ae438e681ca153890f98e1018010955c69894083faff18ad4f21fd3693e99da150df13ca53d33be3e489190e91ec5a458bc5a41c1a6ffc7b2537bd02ede4b0a769f1ad9490f6e08a80d670de4fd0ed43bb1d0fe88644fd7e986d96188b38b88f4d6d07ccbd3d5ae539e67aa77874ecf6f7f04743ddc36019bb01900501dfa5329d42cb84fe209e228b78fdb3dcaeb9f947f547b0614f7137e3d901164a6e64a5bd6ac84fcb18a40b2b669b276e961b943f156619fb2ec970295fe189f5ff3659ad111a7c12291b39ff5c0b6609faa8473f57bf6ad7c1d239184c7ce258d4abdb807269465f438ff69cab2d2e16409e6d4d9065895e7c7a3b043e2ffeb981777a9afd8e1ca5251f46f7b7ba58ae8f7fe29a28b18b188e484b2bc194d1ac438a1c5f9e72be7b580d92a256631f99886a422aa8d5048db469ea64bd52fa58d68b6f7f8f4c4d9afe3999e27c0e811e0c2a929f2f8135e18e3fa9202b263493a4c25c58292ea66ff560ab2dbf224356abc25a9beba1cf15609069162966afc24ad14836590a89220868f23300857ad7f4c060ac8a5447ed75986a156ae88f1045ec0310df316f3e8396bf6629c819428c1da506dedc584bfb94b3fc2aa1648ae56eadc8dc7a8e0e3d11329c4a69e49adcc458726c023e5141fe8a236771a3df3ac3038fda64abbb90216982a1f4a1ca081b5506ac36b84b75fc5a41fe0280deb3cbb4d8bf3944de04fae3553d92ac910e69ee4e3fa19b11f04cf3344d24fff1794c1ebb3e8d14fc22dbb20f4ae239212e35198a3059d3687c9c73de8fc8a978b456bd8f4019fc12474f6f2117218a782a4b9b2417027616ced63b6e6f0e9d9f4830db8699f01f54205923a6fa9472a54882cbb1cd84e5b68a9f4b004d03d7e05dc0050376f86e0166d74de3d0d919aa20bf1102385b4c8bf7acd6106383f0b006f8b5caa1cf7ca4f74369dd807e1de114239b3fd2f06dd137ed1861e8cf2dde3d247ccf968b45b859ae41ca6845007d7642dde3da7a4ee01fee8c4e939452f744d6a4ca1e0032e37efe9f61ac0523a094287f319cc8ad9181c0c5d90ee51550f0f610a2a051e0717593fda0df112c7867d56efc7e0e104c06c4c05eed7f87a3999e27922d0af9c49931f12db13bf328d4015cdafb8b2c830ed600d4739284da0f3071ec0444d8ffd6a159dbc239539d019c5d01108d9b68a517abdc247d3f370ef2043c7b81bdf9377cf36d0c6dd70550f99fa49b48807d67cb46c0725ec5620fc9cf3c1dd5ba35d2222bdf1e0165d219d7175a5bccd8c72c1a32cf64d2c1ce8e8ac2873cca65fa673671f2a3692393b9fcc7f52bce821382aa7757812192ed2a4da95cf5fc6c2ade860ef6d91c71b0d8f65e1457f90cb73338250b41321f37e76b2cddf5258743b88cb6ae4ba63f3b7e93b29aef4111c11571e5501ce482e559effadc5a2e7200e0b8b72f786d12e1189de81715315e688106fe017ec8ad7bff561e810d244bf4b9d4ea03aba66039da8f25889a3f7e09f71a3dd95bbffa478d347c04855223a83d8e34fe6c357203cbd9e98edd2c9c24c06e85b3cb6078ed5518fc689fe8d038c368ed38f220147f8b53d4aeb2773e1581902e9ee4027a773a48751dd457b11d25089f5bfedd7c68b825e8aa3d1d3880f5c09c507c80829d791f0a66e0664afcda5f3778dcfa565e01fbd2567da5e3ca1402d9a2c2f31e16a78c5139b87e239dfa093465073d6d5e8c88a82a17160c7437b45b3c960f148a2acf11abfa5bf1601a3973b5c2928048d76b1ada1997d5f0359546e81673955e48115f72bd8e2761cc66aa7cc1ec09df8954df16e88a745e195d654a552e821b473ee09c453cb426c0f7e63684ff8f7b4336d92ce0929d351f4722820298eabdaeab7a957080eb9c1af6148efa5698bdc3287d39f4da80100cc54f7171b0a2db9cb6509aa22f65fe705a98eca45d32084479269ad653d696fb54e4165d4b85bf358a7834a835f0795dbe8567acdaaae4bce2c82962ec2f736a892a9cd01c19e9a528cc169b349dff236c2c4141add351125fb8e8af9d0bb1d38d3162129c6194e60e68142dac14ee1c0adf43fac8658f35ce3f83b3208ef8018e59130491c564aa27f89eec90edaea60bd1d40ef94565e4a6257c4e95542ac464de63bdc7c9e028384fd18d23c2870a1952acab767e20edb3a568ad12ff388b8a72a1a0ff10440b99c49076a497b0579853e6fcdcc12bff8ea11275501672097030b88a7003816bcad73a4ddc530d10a1ca2a24dfc22e384bcc82b695ec420d64e5c196b27e512253cc0a1a3b2c39f9da4f0d78f1747f3ad7a490bffa605983a297e5bbf57804405976e186cc0ec458fb73ae990b68bc2d430cae704b14f35df1f08f6d5602bdcdf89486774089589ab83d5028d699582b309d4e35f91c3ab7d9f2617e2af3c62ad765faa58299a2badf97d25ee3df91be278fdd6e18486beb7ddd28fd96d317cdc9bd7a223844de9c7386c8042018d264804187d41d3286fa5393b3547a0f217d2ecc37fd8da02bc4f1fc0c2ea312de33b4489e647b1d3aa70a7e2aec313514e7a0f9582b04c6dc91bfc3d0e227de83c3491250e992303352064a167dbd2536078ecf5f82fa0b25a21bbff464202f2cfcd5b2e62f52241c0896506829c55403506ddd2fc1803490f1e9c678c0f67e2d8dad7e20b209552a55bb72d766439b1a81015167a471fc439681c18ac226cf69daa871aeaa401718bded1fb8f05903f99aa706420a1e57d1017c6251840cdfa8417f71f6a1c8363b3aead9cb7f9ff671bc16f52edb7ca9c5cede0ed61b2bbd89b38ac2c8425bc50d16b54b97c0dbd4686b4b241fb95b9c95ee352281d7228d14afb355b20ebf5b4a7e8350d6098419223ea4bcd26c926da11847516957cee6c01b30f251032c7ad1c612e0700b95a0e4215ed63d375eeca3658b2f291a9dd0a9db7d9d9aef4c95ad32dbcf8f4e3c6fadc4e2bfd275440533dc806f8ff77cdfa250d61386a573f76d961aaf5f9c2d97dd428bed692b026be52eef695e1227add2b4dd742a91421c9737ac83940811ea4610f9d39df86c5da771a494ffcb1d6a125f4aed4814cf25c3d0c4f2429ad03b443e5da43091741607419847cfafcffd3e67e54000c17c8f69de09da900fb42e402c1206b87e2a117c8bb0be8ee30d1aeaad14a3c6152e94d8a6e4554b53ed87971e7e3214138e413018ef64398607d7720b05d3838d6f1d50dbd283416969c95749ca2c62acc67ea3eb178402175351250873afcfcb3b958356027733eaa6d5ace2087250538c46b80706ff1e59374c983485030e1a168345b0201b17ccceb2095781c9a084fbd224ea2d22e6d48504b8403ad66946c489896d47697e730a979ff80c424fbfb7d125881e11acb6b593148cac656d6432299a5abcdbf8bf3a2683a3f435df9097e319e6c7f72d6be20f307a00f1a3233a4ea0842a4e3ce14f3cb06684717c8aeb9506f0f37fd927691143a6998fa9480c466e1f1508ac6ee2492d826e429a291045205424e80658214fef0cea34d33c0e50ac387f55f1724d1409ec8f402d05909b1b56a2694106bc984b7fcbbfc389fcf0eacd3709e71ed00122d082de6c2651774f04667ce5f8b9f8190dcd211c8d2c8ce8806efd01fbf296677e8cc195e8c11bd20a568b2700927815bf6754e0588e3a4ab0b4d3525f47da6c3a900e0f99f1dc4c6e25c5682588abd0f16835451615980f49beb3fd1b84d1e122e6a8b3f6cef372b03532f81ff59d9f79aa08ba38f0d0281fe4079806b7728559ddde5dcd29fe661acb96486f7853c042c55b38a926ba01310bd1706ea46d645ad3840c7c081c912af152af167efb63d425dfd7bb8668a9e1fccf5038d0155e8e7547f3fac131a11fefbb3c4bbcb3a037fe329499cb5217080a38f9059d8542562872a0b86946d7ef31f8d852c77cc2a1c3ea2d5dd8b626e9bf53d4ec627295003b9e6be0cb09c6037e6f0c61f6dac7efed1484730c9e3b0469b7521f4755a4ea4df946ceaf6ded1243b921d3d1d65b913846ede9a56094aa8f5ea40958e74fda531668fe16597bcefcf65beeafe8639af771e2dbe40da67bee08ad7939b8d11d97474b57fdcbbf053e63ef5f8d90d3d3b43c1bdcf64ba4ff1f0ab83bc5d44a4ce344a4b574a21efca9a7211d922333511ac622ff5c4ee7b925c72c215da58bc11073eb0e4a265530036450128322761f1aec49bd3d864116000473c63c7a063c96bdb2960f6d07890454ea202d4befe101c3f2703d07d8c0a580455060084580690ef7340f0905636342c10a902fa48680805a51570524f30d0d4216c1e92196fe6b4df934c445d72ded16a0b8cbbfc40d9b8e6fe5dd5a9cab408ae9c6cd8fda86a089fc2db0ecaaf223f718cd80b5bd9c93120028ab72ca4f7378c291faeb2359bcafad76dc4d895cc16c3605539cdd0e7d7436c7631edef085490b13dfa002f11df3fe713353cfb98e330fc18273db849d6243bca0864b127838fb02361e0ca48bda599b6ff8067c26689310beb2b9cf6a311c1a12aa1b562c291be3d978f46b0f89e25b8a829323c5e218ff5cdf1900773bd893160fa6edb9caee94e0b360cdf484c4f61e6aea6ffa4587c8876961391afaa9e619763849ae52cce88c3fb8daaea91e6a4f95504ec2e0e23b1b66371b1bd692d182f6258ceaa1fbbab5f1d7e8abd4efcf9c89d4fabf74784887079849ee9f0d00af14be20f3870fa8e5bf899563a83f6ac5219c4ae293cc740a45176278a4c9ecdd2dd0fc72c59e6b5c27eb430b0f2bb594fca8c5f9429b2d653ea24d2bfbbea90d9a5a23d1b9d4dc35099193b0863da910fa0d19400c62a46679d2ae9fbf100ce898db248feb41cda5fa3c4e9fe043e6efc184cfee0d81d627f90cef527432f9cf9213dd50f3c976951dcac92bd335f91366336cb2eb24f19b2ad42fe13948815e695b092464fe2bb39a858f7ab364f4f29f5833ce3650f431ecdf366fe2030ac3c5e817969b2fcdfd27393a50530ebfe13dabd4de86fb93d61d3ac1cefe37c09834155f3e985e62264cf9ee123c64afb85df8c3169600d93c608c9b3efdf5a93218a5480287606f29c90b0cb9add26ecb2be81a4eac976e3fac12836ae49ee7c67897a5db7ab25874af7d39dd17bfadf20931429acdbe15df3340477d5c3f801112054d1c676547a74db918317c4f83634a6db855228330504116acfef47b00376d3b1db78b87870049ce7848b90c2048abd6ca790115aac4f947b93dd29a18df9db0c29dcbfa4ae1b2a6b8623c32fb0b45845b40c26868c1facc2c95fb5b8cc623697041cfff66fa1f1f6250e76b2905387c4106d3041cff48b43d5fafd0dc7b8d3f55abf30820772ea75d903e51adefc129f6db6b34e876b2e5593a9cf5b32a993b2cd1fbc29567e020b65fad62d2fd0e7d660f948d79d92b69f7d23065c744c31f5f2b76d2c24cc92a973c7aac841033d3aa0cae2508ed4607276aa6c811a74229e7d83dc61898990c6310da62e8e7dc8c30ae38048bd0d40351e4793dc53a26a972b77ba33ad154cc58034122c72f7e29ea0e11660e1879e8b634a8f953ce17bc6c43e9a386f3afcbc43f0cdbc4ade9febe4e64bb08655de2d75f64d9566a8461f928dfb9e56373326377443c040f20d1cc6ed2c68308accc379460ff66ff10de568ce4ead08b67681584820742c136809c4fb0784bd1f34f2fb689fbd9d1ec643ee72131f1007f61fb1bcd74c976be84c8cba617a500f23797fdeec1f0d20facc9e739344b612ef7ca24aa35c74a9a74e6708337388b88cdaf74d3c5633aaffb59e1cb3147bbe2f046de3bfbffe46e3f0b44f493f0a2506e13c964e7b4e25623d9e30b22e5219c960579e800b1ffde715e68054afca5edd5f68cd48b629c8d6b81672f80f1e4b70b8daa07537b0e57927ed8e9b541f6d6b6b5cb8bc9a1fcd5897878b303631aa2b81ad344de31f1605013b3d7e4c074bc0a708e214d9d6e8ccdf62331ad87f08ca97edf713d8fe6cebfabc7e8a32a7621d1649e3fc30eb1d939b3aa4c5329df22add6ae5e4f48573b518658bf3afb2df3614d4aef1a5a0eb9fbc2e3023c5db7ea304c8cc6b130584d9b75ee66cdc58ffa69435871adf072f71b09a6290ca430358ac05abae54a1cf857e7a10f5583c8ae9989652e5174408aee9ff54ed5c41c8e6df7c0386dfbf9f528c5a1b7e43914003d7b319ff29716ab8c3afeb3b136fedde7a3af587301bbf4c7c0751aec048352713289f3b2434be21ac50641cdc7751b2965f204184d87196412091689f0300809f2b052cdf02497cc5f20aa27d6517e19b58ee70a7594258a7f37299848db19bc11782c7fa870452f4642e614b7d7488b8654ac92ae835d6c9ecb4464cf81e775fb6a11520500b618cffc068987adb75885b7fa5d08c1bcbb24ca6304ff1fa818748d7b5d18e1009b2c16f2710b96a89643a2bd665600c88c8859cacd4081053af5b0bcce05d0889f28250168c222cc7335c32f5f134d4dc19164208f56babb5f240deeb9d29354c0ab1b60a211037e9137ffc5939e0aab3c6fea13aef8830f09b59b0a128c4f1a49833eab6debceea2b1095636b9fb60863faf9be8d884ae048fb8060ba170d962e7e9a1b8ad3fc76f684da54dd213354229d9d17c6e8bede2c53377ab5a194e5ab70f9d0ea3fa788b0882f4f4f1ecccce27543f21e6001d164d572070346804d587143b71c0783dc17fd5145ff4ed29ce07f8d429edb88cc8554d899d017e39bb151747a5d564be7db80ee37902ab2219d55cc9b2262f160c2dfca4d9729df3e180bbd9bddac9803309975201cc04c478a719b2d830c8a406a7a4f12a76ece1b88a227f08d92dc578303c61b91f07ff1c8adbada2f210bbb1607cb7adc516b278043c9cb76096f3dd5d669afc6ba0cd4bc0af312e7fc8b7476b2fa608df4e2d0b3e809bb01b18457f7bb3ce9cdce20f8a4f4e2831a9b00bd1fc59d428dfd53aff99e34927dea257396f28df11789cb5c6fff3636b1ac3e86bf42ff7e97e3b743a6192ea735b3868cc030506cce8012c7b5a2abdd5cc5ec3c34c3f00cec380cc309465d520d6d7d562d5a73454ae2b0caeb1775e2b9d526aaa6a805b95236d8105c691d6704049f9485d23951408863b40dff01da47a2f8d3939ca5fc292bcc98446cddd5ce4bc60499d3d41decb2849c60c8e19fe621012d9eac1e186879e3f4a19a1a7e21f9c5723ad5d62bf9be43dd2be305fe0d64a0a222c4b4db99f4056cf660c767d0d86fdc35f645e95f1a0ee64c07b670762bce2c611bd82f29fb0c344e81594d10dc917b9ae19eb2f42afaaa6050116613ec95c20271b90790594c9f33fde439ca25ea05c17c55f592528c8de077970f72a26c1ad9ab30b272979ea57cb74050e7c8d18738934ae581757440096ea0f71a3b83feb0998a0e7b7e41fc56ca26e23f9be3075b2b1ddc2361236105e084b4ee9907ad0b1697691d072335462c353accb16f13cd6af9ac61c97ef4daa2838a57be8a598b927e7e6e05c76b9b16052e1062d3dada0c3e037b24566e18b3990293fe6b6fdd4219e677c270d5e96af6516c7906963da9fcdeb239da77ad1d74c4962abf9b47eea4ce7a2dbccac47565d866058b547102d0fd3030b1faec649c38418b97615b4a78f839f38eadbade053e374be784ef8c7d39a54e561b063574cec0f0100c8ff477d54025b4368fb4b047261f1e7422162d94ace15a0c7b28fc3ffc317aa6fa1b3725d9a540fb622ebc7c6ec022aca2270d38e599a352e4d661bd14340e28f18c8c2d4c815cad4ebbdc3b419b88745c4d1887bccaeb06dad9959f47b911b1ae5a8c3ea74d185bdc6d7c20ab84b97883191acaca0e1c575316f6b712bff32fc687a4e09e80a70f3909fdb2e8be2c67d6e817cc2f6e2760e173b3514f81fb83c030c74a53e2c9795d1361186b06b8671ad0233783bb54cc5c9d0b7d25b9292e22f8c3feb53e60e4696d2769f88933a4de499efb30f11bc54dd5b4f9b1f0653fd62a48b779adccf7f015e72060c1775da3c87408c58c9413963fa325c9c8dc6031e06321f6212ec364479a14f2697c1c6af22c1a247b07784cacededc2f78708a0c283557907e3c0ef064d419326809bd3cc779bb5468d9cb27569f64a51dfc92479cf5f8bc691675827ac4c949d1f160e5ce77842e2d73cfc020854da830c28315703e5f6d5f93047a2d9300415db5482a562307bca699f1775d36afad1fbd40bd56445fa9599a57fcef074f2b5869223d7cf7abc222ae0d444216e1d1d79a2bd2f3ae97108930404f820885bfc75239a53f0f44bdd611b5fc44492dceb35baa953f4afb4a38a6e01077668128afe8aace974254083bfc6852980c84db8f4b3907f76b671d40fb664218c8ec8776105250a1bd920c8703d9ccf6a516f1c4d50470fc9026dbd572ae73185930b14a22386e24f3f64fa8bd192ae5c5eb5cd4d9af47bad6dc65381cc83e5382933d91740b0529ccbebfc1f4f0082305c32e09e6edf0f1081a19118694190e24d3775f5074e2609f4433d4cb501fcc0f41af890359d35b2dba2067aa800158ad956bd4aef39f6398e3332e9b82ed6becd578faff3005e0655a3985e08adb8d8de88239c1073ba30bfb25dcdcdb4b6bb03fc4fd1766718848c7e95cfa087b711f370c30bd1938248e950eeaf1bd005228673c1a88d1f4f76b459833bc26cc9b99eef94dd12fdd45ea2a209f4d79cb30f311cf92fd26f57d56f0c7955b7cd83b1896b280b313f144e7898f2849828cb9531fad8b4878bb8e2a939bf90d160fb4cc063779703ede1002c45a52b5957803d923f2b722037ec126c5658a2cacf7288aa176a40843d33c533d54cbe951c81f0a95a23fec7ae9ce063565744b02d0e5ab34dfb06b2654dccbc2e4549c2de9adf390901ff66a70879df0cb2885dfbd724e151404b27f75ae7f86bd59d57c70b0396115f54c3e30669bd44f537a1f39083628ff8acf63246b0538851a9bb174c10e89b7b24127b2b7dca3e9302881369111f8e12e3bb56c8a80a7f4729fdeca7492beb363219ccc1c05e9bbc288f84d414ec954ff42dd30f6f7bc87614fa781f4bba2f394c0e2438c395d29ed85dcc4924aa4e24bdc8f3502e0c31d48078fe0dcef7d8828a859d95e88ad069d01f149baddfdd0778e0257c6619bd8e268453a330be071909ad98fe5e539b7d971da4f020248f0971b9e0f85d85d13473e37b29e025f4561dd78654802710efe372ca1f9b5feba23a80ff6327c1ca426b6b12cccc8da0fad5183470530b1f1c7196f2896329d25f0f64273ee220c9c416cc4349c0da98fe85035a05472ebf524fcc5f2e6dc12587c474c43590338892d3a791c200e9d5033a2fef0b20d79bde58d5d2f1e87b4c41c6025eb313a21178c0c2ad7a9aacf3aeb2c3d06600bababcc4a63cd22c50e7f6810884b6aa3b8003b0ff1bced2f0bdb516de4c5bf061ef29a0e83cae93dc21b909ad9155b97ace819e733e12ba523ae6c31e48722af041f07a919d43d06b9fab1f9399b913f9d20925e3df98baa6db00f876092b4e9e610b8fa05ac2b023de54e246bb766cb0d80e01782726081338257388d9b8ecd1c66a4b4efbe87704834c68dc908a033081ac4d190ff8cac7403bc20e12de1aab0b549c8b31314243a173721439c02aede1498bc63984aeb5b502187cd09ee0c52604d29be6f5b02251816ac13073cac4a6d5775a34e8781cf1a19429b27cb2ec5f12b90ecb87c37dfab05da0faad252e89d664cf917b89162d63307a8c8ef320abaf004bae7460090be3c3a86eef761ab08ac6b1c611c9069161ac210c7c074d401ee5d4f02b3e2e19affa7e9dbe688477f74dda6aa0d4b8697999d7dc8e4eed202c6c983246d411c060013581f79106c55ac3b50fbf0c1865a306214f8d31fdbbb76caf5e88a78c733fd9e10deb02dd983ea188bdfcd32462ba0f984761c6bc29bfe6469f579d008677d9334ed064321f16ad70d42f509eae246c4cffe8a5bf5dc2d171fa25091922eda5de00fbf5adc24bad18c26c094c5f4307a3c8560f8e6382214818523c28799e0298e081aebb629e1884071e7bed2aa22ec292e09d7b9e170c460419fcae2d27e20bce60f25860b479a359688464751460b5b4be548b7563aa3f9a6f1e3b67d2814a143cf0c52e35125598993d718319e9e56eec0a9b72354902a781f7e105fb68a47a9b2ea53976f933d297a463c2e2885370d6b004a46d6c5eaac3ee2c74b4215339a6f85fe41e424acfc4fb756b15fa85f76039b46cb45f5a660ba2a071909e86d33fd6bcd70ed61fa4b95cebd5bd29e3a4e75f31d41c2cbb60e9ad1a300b9c889ba2abca568ea60dd1941aa88ca23fe8005356e7f66dbe088d3dea09232bafd7570f17b1acbf0e358893b54cfc36f03509ac887ce5f4ac35e0c98dacb48640ed1a9023c7c2685ba8dea7892d2c133353b1ec7ad4e243a6cb9ca56d6dc06c16207b3aecc5240a18fbd0091d93511680451dde121db243c7128a2e8c1f204007c650dd20b46cb7ee2b8d04a4688664b299e3789e603d0ca70db33636353ee770f00c5c145fae0856b44084618d72fee51927bc4628c4d0e4a4cb50162be8ad7c19e6447b1a3ec30f93258112b20ccb2a76e2827c5077aea8ef0fd0f8311d80f0e5285af6d877b702f541bad9293e987ec23e472db62a80f4673f29899f6e701d36d1823944abe37ee30283dcf23c0720cb5d1795eb64df267f63dd9b3beb1be7074806850cad4315c43ad9900082de85be7c0fe8195c8596e0f2c3c589979492fcf9006f180db286f8912625782bf469d2da4bee4fdf48a02884bc4010ece7c939fbdd21255775a0b8f3c1bf4222c1fda1b46b49e1867ce2d6921f526a73e764daf8aaf388691f017d5710273666141fc7cc86aaf8f38281f629735a99755adec85652f1e3e068405a09030fa01d48f7b61b3ab45174d7f67188507b29d98cf383dd92ff0b468cd8ed9af0ed001c059616c47bbad96ab31057dc24767862fcec3b1363151b5539bd872e007f8730199172bab22aa7cb0b1a4245ea08c2b2252afde8b1878f5d04ed340ba8ad662548a90d7f99ecd6d7cf92d67b098c22389d8a580f6e092070cbf958b713ce7407e8e3ffc0999252a9ee6bb6cfd883c7d252d74519ef2344622e413284f8b6d271f8ffd63d56b38ab5a4f572c8f3afdf66cbf52ee937d7168e72e2478d606b96369779c5cd9e4835a8d94fd01f4cd87b35658eab31163ad7ceb9ce4920d8bdb24f836801c10d6104bd848098efd777975715c3280fc3c762e9e7e465b7e022140f4706a31f9fbf8b3c51f392700171ff3f498d340c23f62eca8a882a1425fc3fe8b5dd909a78ddf9c0bef279c16cbf3c269d34a7dcd716e82df9d62b284e1a4f5f0a6ed4bdb936737a74c9ad4d72f2c66da7541b8b2b41cbe13315502ce9277dc84430c7acd9e95880768489eb098ce80d0f5d6b84b33e482496d9b24cd00c89e19677fb95f4a8ab969a3da9bc1c8fb6903597bb0780d48ea1de0fc4ef06bf19fe653ae17c97a18709b88d3bb0ef92cec05cc9f5379c76b5e7e781160344e9151d96f4aa224e574f105a51c58a1b3f1cb2b06cc09cc5a60d5ce380e4e676875e2d25920efbea035e4a0c85224ca80b1c4fb1f1a0f2ee49dfafd8c0624fb586aa06fcb60e6dfc189801695498ff208b9ca884c16e1493e44e90a4d64c9175c5a6c671a7645e9275bf3fe5471e34fc3d51db1d7c036a154fb7db4416ef83a7e4df4013a6f6f4612a4484e091856bdf87b3e69d357f9c0494b57b7ed42ea847b02923fd87af429b53173c15361f1de05e037f7c709e2f2b69fc195d27203a41515f863037db6751f96eb7f9218b5ae2afa6cbb07d92405f3ca6acf04916da385322129bce3f0416862273124a8d9d1aa56d11b07e1aae0a0887090484bcbd1e5a21d914782885363004539b6f06e5cc9e3417177866e5a9a1e2f54378cbdd7de662029d025b5cbd2d0f66cc166d1e9d9c76e8a77ac9f7754a8535d83f3ccefea26f410360230f20a5c43b91afe5161cf54c841f10069297fb13014efd3cadae75b7345b1c26b65080c3de891a62c59bfc3fe52699cb711d5bda78db832034a7cd227b1df93d9a06c85c836c6f94c29648b72a4bc48b30620f17b2d896054940be2a0c1d2b0078c567f7f145c58f83b5a8e2d3feb1163efd57aa75f950f18fc3bddb6317f97c95ac1813073483cec192adb397b33dc9cbc546b1feabd02a17d824433887c8d44f5f1b2b5807e1b679fcf74ffbc75abb1bb0548253939c2cdc719f15156b41498d4e894091ab1729a61cea0f7485773e33010d8047990a0dc65b874f56b314807572258310ed02f4a7618816e2d847092f29e68c9376ff06fde7afffb90b7c3e6e8819c9e9fa28e51563148d155e331e416303447406e0c6e1b822b3d6bb4022b35422874f654f7a119813ccfe751a41e1066073710fa661681099940ff9c44058c8b88b3778f4652de8e989aff5a03f83ccbb18e906d25d020ce3c9f680d1503050ea7b1bdbc6dc2c92b9da609ec09038d38cfd708ea7209bf6163a7b7629a714f08fe199cddf999fa2986e39501d89712063681e14de1b969b70f7581fa7927671fad6bf11d766d8e4c849e44d9564f95d0cd743bf3190c82c80e437d73c85be4f073e72516ef38792e362a5680acef0d96f13d29e44751bebd43b9d4f1aea49882521cf98fbf61babafedf15c64ed5baf3ffcafcaf19ea4215cd59a0c2f353ec4a22c269b8bcba280cce2d073d9c3be4475865de28081c8d0b29e8dc79bd26e374bb4ef3311d5fd7789cae2b691a7bc7f51a8fefe28a66e374be46f37141efdd318e2ec97ab5f7f873e0b965962d784ce14a7e02d3642a62653f72c6b223161d60e10c0ba7b1728eb573ac3861c911d61db0ec106b075839c20a3ae5c2da910b7cdd70e1176563d1368eb2e1e81ba3d910ad4dd16f8afee6e81b44b559948da3d83ceae61725df54d6c7e194955409c5d807d88952d1a1782914498802eda4fe33cc84b35d6833e76cbce050df26b2a7cf7cfa620bb87dd16ef0f9e054bdbdcb83d0d757c2b1469ab52804e3da20994dd4c5867fc23135c7d77dc581f10ba1aaad1ac94668570e44ed08f56d28a16c8d297953a9b3222efbd604f4974fe409c720ae8b89421101839c551910c665228d10fb4cbe975840738ab2da185a38de47fd54ab2509be67bcba95f355559907dc158837acc8e986a33a0e9e45cfa476f924c8414de721cd8b6e192f5fec13260fdfac359dcee50bccf396fd870f0d2de49dc254c61a892a45b9db9a53e7798da8c65c556d738c41512c4434954fa1485a4a3dcd554f83034caeb78772c56afb750529c560a538ed08214bd59eaf01a4c33a29e5747b481c12954247eec6143cab93dc3e5e16024c9a12b846a9b597e6504989eeffcd943902f5335fd1976826429d95a42b9400d428316cf3f06ddc77592afdeff3fef999e96c95f0fd9f67b9ef42a37e1f56677775f37b9830f617d4879f2ca529b0dcb6ced693a8bc5a7cb721ad67469d56173c2f72b3a8e36e2ddc1cfd2f4a6b833c8b0fec8cb8d0766dfd453a699e2ec6fc697a7c6bc11a41d467cdbbf4a2742c53bebd2df487172b4af1a3c171e202817505ba4378788f8ff9adde7cf0b122f623a4dd87a4d7c4bf59aa727910d7898653ec47bfab8abe1ea8eae714474d0c5d2d8b1f35b1b0bdb193eacca88d109f72a12c7ce694f7e8657aa608e287d4486f5e044c4e1f10e990b182661407f31fa2eab3034a79e79d24c3b79442701afdede36f3d3f49508ca43ccc16b3a3845d244e40dd2f3a2daa1085b43b4468a3242ff197141dd1521335fcf63dc48f21a403f02bad34e7b0fa06824bb302abdca8e4487687e354e34332d6282653a5116efc9f536710bc190eb7d88c5a3c8122c9db53cdf20357fa653bcfccc00df292bc9326f2d106f91604a420326c40bf6035cdd6d20d26c10a85991a524f0c463a6d66330ac05cc92819a0141bd800be2cd9354be9c590357091228c9ec65714db88440380c8280d14e41d85ea788db586b2508ad9a9a1ba08534eb6928d20a34c21d4c0862a6c46699da2deae7d60409c97a1d0e18fb276366d4f670203dbc7713ac39e9d244e5b08a8f728d22465a61a19f654d2c46f8edad36b41354ba72d167fabda18866f790c92f051cfa422562f24b24d98f1c5020232178227e960be05d4774d128040d3133a8edc4573be05d9f9c3bc2db03f4f55fd52716a01add5eedf37ee18a1b750f6dd2b8c530d7a2336c12acea8ff3afc367b8a043d655f347fe9ce29894cc8c7f3895e405444287f14b42f2f4b3ca63b9f17a0a0c4192528cb8650cac1c99a3b800df83beb9d4ea8259496c384ca85b89c6fca3af09b123002c0ed0add575dabe44e11dc4d0926c1b54f41037438c9449ab6c9360d725b9b1812ead8a47fe9a659ad8265042f747712a48b15802769188852c55a27dea3fdbd6fc9134cd1bb4b4135b935fca434921f632718311412a375e5bf22d8d59b0c2e0e77221489ce758d82c47396d7516712b8f637f09684247d4854d439a9a44ae83f922c0d6b761dea731b391af05d6fd3b1d3ac5fd41f54453fdd48111492496080cb2a1b1ce414beae2447a2d9f22dd025fb1de61e66fa550ac378bdd2fea8a5c7e73651d44ff4621ed6cb75a84dd90e839447beda52f8216dc79885e3670c1a77518d69728fcf1c89df5c2d00235ff7b29874d5472e488c79a482e4327d23d72e4133c93ad1dbe30a922cd1d88e2baa593cff86a6a4a9589d846f3f742c66e50b226305ca146416d3982884a0c0137d856727728166fd462bace5840dbef1d42e6be6e3f3ceb68ff2b714603f53a343069d621235b08a20e84a202cfe7f10de9e3e0446d91ea00dc1faf4e0f5f46d9c578e28e2e1ed7091e4ca928db625fedc28cb2988886687982715d959693932e808e4600150952180974d4a394c83ecd3ecc3adc11b64336497d94299a55716437533fc687c23af2a2dd28a5c91e89ae0a244723247bface9bcf7fd712c9353555a09d4555c3923c940aac94d6aeb2b5e035e2666c9073a71bd6a923ed5caf7f2c923040989f9b0bc7aea331c76fafd863e85e5b5c6d33ed568573dbd89d0211a0475d50b291eeac48c6801ca47ecfa1d7743bb552b5c99041296352fb329fe8b03bfcc1c005d0e3a66502db81b5de17f0728264ea24429204334147a613a7cc1a72ebc9673157c6f180f7fd1f12526d4402d1f6d3059a40516a233c5592eb417b9e64ac350103ab9341d7e9a7c0d17b029b88d6ee15b21860380fce52a400308a30118eac95c6f20115893c006581e49653476f800953137cfbc86ca9408f91fbdd3a716f77ce528709b67c6212f8bcd807ed6aa7df3c61ed7bdfaeb728b6f10cd604d45a7b2f078ce3633c6b97b80982898a5b610183a4f9ee4de5a6d007f8f48213885eac89ea113eec9a857c32d8188241a86803ef06a53574bc9cd200c4fa3b06b5ac6ea77bff1a15076584d4940a953c0bbd49fc47b337f63c0ae1532c7b67a0a6f572aefb55d54ec97800885a5a8d8e466e86aa014df943f0c44888c83c04203a11c91ed63308c528428d7dddaabbcf90887fc3349ee5be2e5d4729f6d8feca14f056516de1ac8821fd2887049311515797c92728b6b25ee8d0c49f3e50192a3e30ab28330b1a4bbec2e707964e7657562de74b080bd8db74d431f835604c3a092cec162ec3b4c2816d532b6db0513a00a2bc792baaaa072963dfa4cb1b6e6676db6177b8e28b229847a4714855cf6988dad01ab2af4353d6453c94fea8cd06558a4d4e1859eb485c48e724836b26f8e7ca09357dcbfd88fa1d33fb3be22041a56047c7a0b1793e527cde1ef404e43fe405bec5b0b925cea77a033de48bc4e92a865a1e773751c9c14ac2f17b3bb0203bbc866270e9c474d47ddfb9ba4806ec269a70fddd4bcb100f28603aeb33647bb9acb19849f0b879d612369190accee0367f9f0ad82cf600367f44b3b5470aea83ac076f4ea2bec116618a0d06ff8368ff32402be2f80f8cb56291f367b2711449eda2023dfa49245d36877ed75a64a5599a057b767d38a1b380690c2fab9f02d80492336c7b0597bb06d19ed213eea192df6182b8986a31df6fc494f4611ae8001a8990fa33421b05204638d63b2b2ddeecb279fdf8127eef2c91b2a8f9b102f5b401505f7016316bb99f8b9828a40679d767fca7f7458e858273846e89a73f41c38d83d5c2e94110abe8e1264b4711a02e4f07a2612ebbd1e54ac3451569ca6bdae37039217cf3e4eb7cd813f87fb4ea880e3e65a14098d8c5f0fb4672005a4fac1149b59c9cb162a131c94164781a00318967443d10caae6f15da81630b185828a592c0b1e2e879d4eb8fb8c4de639cee4970a9eae3f6fe01988b1ae7ee4b184a274f448f4dbd6639a94f73bfa26e238517dea4c4661b6b1838478f8fe7fc088ce5168068a73308e04a8cf83406603fa2e4c97a09699cc50877888af06aecb582b884ce8f959c8d138b888be037539363a6de2861b0df3016c1903fafb11645a30d819a7036dab9a05a134ab75779d9d6bc0f2e01c666843da549bb60a1c25bc219eb06530849899cd658b01dc07ac1f1c8007b461d3b229550700df9c26c40108241c732c4635c05e3e46b40c01324c1adabbaded2d654a29c9300626064006222ffc904848eb1c79a2e7edcf8ac88a7d9ec4e2602955361459dcd53872cc56c221aa73341b16a9de9538ce4dc51e3b620d69cca8fc7153b147b852e547734415c158e54773c426e28835f438bf8717236a32610442e802257f7fb920368c5a779668b1852de4d4e844091f2d9c30e29e1a9d2cc1137d50a3931c25eaee6c92e58931c68d9a6218f5228a1b0f83dda4dde1d3caf7b7f2bd701f336170038211289a8c000a272478252981e66dd96ed1021b9fa01d5439c20c57ec40063c5802089240827dc44a66b4c0092960408508aa1fb4e85021b410c58f155bf8e009438800162fa862486708415358ae8655a92af6bdf878f558b71530b161684a08a080418523ab0d08f4bb7f77fa312b68b83bd20a345c9c2abdeeeeeebe727d79766777f887636e8a1f50fef803e9dded3dd4b269b54bef6e6f3090880206182d67777777777777bf9cdedfddedee3e3528507e39a79cee3ea79473ba7bb7ecf6f6c69470051a193c81d6ed2e99999999999965337377b7fcd6a240b33ef5c73ef547e37a95d25b4a203c39527637b333377716822f5abe90deddde434c44e9a20b1b1b274b0cd5e86489292fd4e86409a04a3bc53aace3e2adbbeb8ec1b8d170aa914129a548a1289c0f252af372d7042ae50906d99330c821eb24a9a858519175560085bec80f59a7081a199c330cd26e63d65906bb2ea8ecd84974f8c421eb546769464c21c414d2bbdb7b280a215148ef6eef21160c61c1faf6902aca1552908214335248ef6eefa19eb3863f6d788b0c17dc5d87d6b44629d0893284273c410911aee0828b1a687145139a105f8bc3466a01a5cef3c3021ce077c7fc60228efefebe96daed1b49090dbf76ff37779605e154378f1c3c6af80f82dfbbbb1b5f74099f2b96b004200061c8979bb9fba5866e66ee76ef6666de961abf5b4ad9bcbb91e376408b47285132a46468552ab659a262940a88922125434a866c6e6c8edca89454515245c9909221255e28a9a264488b2b9290842af6a60a2abdbbbdc190bf19a9bd39a3dedb5dce193d36478ed9c718a7fcb09b13fb4e6686a1eadf55cf89512726a9bf9f6055fd379e1a6e40788e54b9b9948e40fde3131e55cd7ac4ffb13d269ffbccdb9efb19e65edbccdb3e8e5e7aa2cf3c8cf3e66fd3ebaa795dbce13ff2449f890bdad8273fecfa09743f880da3ced00826a57c203c47788e2ce703fbe559a1b00f08cf11066305c263132b101e1b46491e1b9e150f109e1583b102e1c9613056203c387cc4796e24109e1b1e1b1e1b1e550784e786c15881f0d830186b90280b7777cf8c40639fa477b7f76481075c2e2b6aac7097deddde43be052db4d0c28b9a6a9120a0eea489a02a438d4e9a50d5f91f10aa5a6f467aca7fa161c3a8f30b6fcc9f46aa1130d6190259a253e70764894e58fb812cc161547f38393b497a4e204b76188c15c8121e0663ed6d4912d6c969204b7270705614c8921c06fb03b2840873082ea5c43029a59459480cc330296973320b29a5942ea5c4b028a77477e95f90e6a41d83bb31b83d737641e7d7b91361d088f7507e77ad059b925b36db67e9a1999f9dc23ffce1fef06f94b1081aeecf142a0a883a3a32fff00ffff04fec41dd9fe9a4dc9fd58124c2e839cc3ad187e0e8c3a6fcf9f9913f3f3f3f3f3f510a0de3cff2ecc79f1f2643abf209fa614afc51ddb494ea242460edb2654d9f3cd05d7786ad2c01630c5b1d81915cd0588c0c75515e5e5e0b3818b4bbbba5b7f4c602edee6ee92dbd494e707777671836a54fe9d817b472f76cd9de185683d5484123f38a57bce24c646a914cb02b5ca480865ff35aaf644169d7692ee57f9284936594f2076a45949c949c04d1b0eb4c5f632716a32736b19f69cccb320fc3b0bf91e9217aadddb3ad04d4e2a2fbcbed9227f69e6dee85b37e42f41005d1f5e6337b0dcc2ccb1ee35ee22764d2b8d2b22bdd911449911449911457e2fcf9220e0757a9324ca64ff1b508449725c6e83c621224a78c9fedf9400497a56290bd9a19168bc51a8d3ea0da9fed89a2062e8c3e214b454b8827e401a39177b700d1b2713e6ad5c8081936bd3bc69f9806bd124b77ab3a25a4a56aa95aaa96aab53388277e295c39e0205c41abfe2dab09d522a3fa1b49194b947850e2c136d65ce98a4cc452f24e31990c106200809258b29d235096158eb422acd46026131353323295574a5790368dfb354c3c647b31d83f84f3f16218aab61b69041d72ab59b4dbd70e5bc987eec0fd10550e87c60f51cbb4fd85608bc17e1cfd34bc8e1593759b762880017a38e1d01312fdb27b10c0f9a3389a1cbca1d88613c0fd002b7fab8282fac72e06fb77703f44955bcdae99da630133f8f841534303901a8230a0010e783109b1e18607e000811c7418b2030c0f312b2a4e4527acbdc1914f1c61d32c26471ca9e12659d694de2cb9a436a62af14ae941eb588c0bc23fc322fe58da6613c3be1aa0920fe5c9bc32ecc42a5182a8c4571c52d9605428430299ade9940ccbd8a83d83000101ad7657946b74c2c48d4c7b6ce1b6c1a7fdce24aa2d7508d5e60bf3104ec9b0a352a93a60b3eaf8cae955ede79d42182595cc39a7a8ce8dae20505c41e6cc0c9cf261b39e73ce193dba660e5af8b453f2a911f4288360a770005540c308640314400d287ff82d7db3e69c406950cf408b354a3e1486253434c508838620682326c747132bf6827d0cc2a8f9ad28eb41bed809096d9ff2310cc32287cd30c83981beba09ce065b43acae6fbc41cb1872ff42d9043a0514bebb3ae822fe01ec08118a29a8a03fbc2a028a3752e032a73625f54ae9f202b2022173ba03cf75b76d3f73f92f7f86f77351b62267296d8afa73fd2af6b76da3ddb2c56b99a1acfb655f8862aaeb934b1dd12d62742550e60fb7e82267ea3daa51d6d85e4f214ce0a2e07ae87e8c379a68a14243dffafd13d5e839cf6021d5282327048a96312a63c99e7fbecedb84cc25894d46f5f79731d5c5d3a264f845887c5415800ddc437e4acc24bcfce06aaa5bfb7be9df2aab7b35529b2084eec5627fdb9af64ae9eeefdbfc4259e507444bf52e341a986ab846aa6f4705bb6f9be47cc8f67e1eb5bf22b2723e649868cd4d7a0a1afa14b4bd37db3c0322b6aa7c8f359a936831ee8819af3717c78e65f9fb2d8ee8dda2786366f1062ba188cb09b6752af001561bd44bb3ebebde3958460c6d937df5f6098788cf24c228223f0045a0171c583ec6c7f886f8b6d5a77d233de68644ba5ab677d00f44b62706f56873df350a442f6a1cf275ef00046d1313a82a7f6c56931328e962a036c90e136c77776f9c1821822a43e5173541b38ff12c8ffdeef470cfc9091a6ecf3ef128197b88a8705beb4a7281f945b23a5adf1d5b77e34ec6e2458e0145b6b20e83bb3b0cae885656381c48868292b8a9e14aa151c30dda292b1c8a752a0c6ab8d59d50f4331c744a8286dbb33d51c7acec2bb453469826b78d0bb2b27d33f106edba461cfca21d54fe9a9608b2280534fbe8931eb3a14ff2e29cdbc3a998171446c5155a272b2fea4aefa9fbfc2dcc0b54f7b34ed1ef2507fa2ccff211d1aafbf4fb5e72203d7dfa11b140dff6f4e0f8c5c66504743f96b6a0f1c99227aacb13f46f18ac6154e5bb7b60756f7b60a8cc3160e446a45cd2d3c362396fd4485ef28929f9d428f9947c5c7a4c2e52947c9ce8ec11d4eb0d39bcf1b67119d75be4dc4b3ecff291c503fbd44fbdef53fb683ca06157f7a6de6053f0295040b2a2b513f485748a7e9d62f9ec232780fe3e919b2fe3de1b2ad0300e099165438cdaba7da110755bd27afc33d3dbc6d558af673b99edd7dbfe860a95d9ed3a5066c5f4cf7dbf793518eccdc4a08d182e09345691fc0c08991839ce08da3b1c6f5f68fe9a7b91dbeb2ccb9e26625ea8533df3b0e9c919e69f9e911abd22b2ca8fc7125e480450e5b542eb263cfa0a7c317880bed0207a7ed1bf911abd300b85086dd416456cfab737d35fb62bc434d8589041e7ae0a943f069bfee6ce324abb2e26823518ecac8a160b4079e868abf5b65faa1dd16cb0a0a321cda6ca9fd9d1500ee9536737524cd10fbbba6d1cc463091e3a5264104e0da5942a5ff3666030ab9285d6e0d44a34712acb21c328466d2fa58d2a57a45701ed318c870ec6711cc7ad17fe0684aaf6dbf69c68c43d75c463898969abc9c42e95eab55597f311356ff4dab7ec91b57127346c09b2c9324aa3d63dfa4299da210bd5d1c711953a8aa36ef4244f3b711e0b716a063ef58f9e831a0abb639b8aec82c86fd468e5fb49dfdc87319917e3893f5a82a05a01d1cbd7b4dda4ec6ddb9e26d6eddb34ede548ab44f0d4aca1b038e447e9882714ae9acd4c7a504c6fdb0a7a145ad1d07582b6aef510a39ad5a40fd1ee68e3e639fe5a8f46a3a78975f4466a4fd01c98fd940907c673203e7192a3f0b6821ef1b31b4ead8aa79cde9327b6c25148d56b49a12d3feafeb55eecd0c89bf16660100a83fddc375745d0d081221684c17e01dc80869249ed17c26064c2a9215107f60ec4a919dafd0c1736ae5081faac16046362783b06ec32193f4d8064cc70a12bb9bc98666cb36adde6b232b2b75c59e1510d89a8fc41c9322fab9476dd7f4c0c14d992515c5e4c30345e78efd53f322e74d3803145983731ccd75fffac931850a945f82404daaca86329e5a44bba523ff4697af19a3287e28dfe928fc92601a687015d71ca027deaa863e30c7ff499be96affbc28ec6cfaf593972d4b0592f64198d8fc27c5d277bbe1ca84f1135b988600721a2a61083fd3cc4280cc693417dead74cde14298b10412e224a4a8960bf1cf2ee93ae78a385f08660d0a6f64f1583dd3795b724a58b942f529aa484919286942f48f952ee7c75ea25a7b8c01ad36dbcc383e883ba1fe3f2a8a10c7579d47d31cd181d0684212941638bafbdb0bdc0f9e028eedcf2381fd39ddd63178353d8b0eb483793152ada69739182c6b0b9f4d018381f5a90e6d2f32e516c0fcc9bde647ad3bff0a68f3d60beedf11ee661be215e7280791a4fe323e2853f27683cccffc37cece17dfcfd0b1e23e006349cab8d8627d97ba161552f35acaaf2278318ac5d0f68d82d8d8a0c582cfa0f82425348a84e61948c014e4eed9752a45011560b48bc381a7775dc0ddc83bb2a68285d73c5a7fe2ceb56bf2f988f5c0ea607ea148c8c25d0705f41800289265520b1e3535751078b53fc3222a64ca6cfe72a9c2b919cfc71cb0634f41eee0a747ee8402d1726d49f5f28228a1a9d1c2154654b3ed1b31d6f5107b7fbda1df3c3f6f5d3a75e2ac49474451dd8378b88579de21707219efa5dae560b08885bfbe2d60bfc5abe504ea9dd7da19c82dc825d928b2953a8cdad78a306346c160090a086a20e30e20e6e2d8efe1642166a83b5fbd8a7b53bba8a88a3bf49d58506346c56017ac4d70cb045d3875788291a5ff77c6198be8e457080b94242b9b5dc6a3593948c8eb9d2c1f9983873357356253d985a42426d5ce3b19590d070ab728a0c8a326caeee8e3fc09654c056f2a1fe33dcfd2524d4e3beb21212ca7d8909caa0cb0c8ea44cdf5f6c569f3cc2a0ead188fbc23d52973fd3f7cf9cce9a5b3231335bc987d6920f057d94a8a06bf337f146bfb600d3e75bdd22a366b8b0a8bfa4012bff20f20374c2f4abfa6ee6016a7c52444e754abbee1f04b5066c251faa49579ffab31212ba7d89099afd68a471949cca429ab632fce316b704f3d9c92e5c38f9fa65959050d29798a039b5e7cabf8484baf0252668b8af9e954fedef86b25357c560f7dc13b4e5c366b54b0aa382a20e197570a760beb38860bf8c1fb55ee32c5a3aec3664071e7af02102129872bbf43d64071e7a989b0f1190c0045c5cbe38a79cbfb3eb580637f2c2fe2975cee2b33fdb64a30c68b8aad9e26acfd7abf64f57d4e1df3f5b5118f5001c2090830e4376f09e9f78a39f3d6769cd97267aad7f7a315ffd43a28e51e50fc4d414e2e6920fe52954fb6384e1b17e34b1b28c15fbee9ec7599d7bfbc426fdd3ac66c220121a39f57d42a27dba097f2524a80b5f3a8286fba25dab8b57f7da72bb9c18e62c030b744612d590971d5e5e6685f1dce4c5fe6c8036345c958b17762d7d17d8ce0bb3ea82176e95da3fc393e185a0d7e26d1246b17cefcef7423fd6966d523b46f647132bedadc2603fc90ba5505d9b9b9c6fbd70c2f433ecdebe3a65fa9ac55f483f9742d225a9d4fea46bdb98f3c159e63d9daa61c3811cc85d2bdc8a17dc31b6f55c6a09092d31419dc573a0226818353a4982a7ca1a2e50ed47f5cb20209c19a559eda71cbacd629bdaee5262add5ece914ed9940325ab89543c355cd559f7a59dd56e1d64b34e216bb7ceb548df66952fb1b4a6d186ac7d19ceaf2f4770ea3244be1d4aafad4a5d5ca57b5377bc4086a53f79de37cc4b9ea54f773d5a21202c3352bdee8778901ed5f15b7f6a764af964b4a615450d421644e1be6bc61ce07cc89c39c10983387397570f5aa539ed3d1efdc86cd6bc987368b4f0dd6511028bfbf108551fcddb0317fc401025b649400361c3218360860e096c33c6d3ac818b6213e806d87ae988d8d072e0857ac87ed0607c448c530021040b4c22f3d00dc8fd9ea54d62757cf6f5fd3618bcd6a563391691683fd24269a95a2ab54d0907f5eafddd99d7d09f8cce7d31aa9919bddecb97d577bb4813b441dfdf4c2ddd9d5de0c55794d71c5568c52e7fc9199609765d3230a1b79410c51b316794174cd58fbe19c17d0ee0cc9c7bc2084a8d8871c0802abdc2bce321a807e9c318bd26dd4a5a7e4b3eebe1ec456de27d195c36d85fa84054295df07a12c04df6384838030ab33de989fe3bc38eb615f58e3ebaa7ec4199fd6f81e0251f708a388cc9df366880b472b00218c8cba1f93e346dc3124e2e0dfe7624a668b3a80f0321bba8fed973188ed7e473c9a45bdfcf0c90531f2c3f7880cf2e2cff9d82c8b01b9621983ccccdfeee63c72d4f93db7e5caf7fec4055d2a756aeeeb91fddb2d478ef871c1babf9e7bdb1734fe4b7c890fd6f92ff1ab01acf38bf18a788dbf358e3e7236c41e339c83513b84332655a1e10bf6f1c16f6745071a58058803283c1ce12ff2f0440f3df2a544e56aecc1ebc8039ee05079822354e3139c2835bea8c6273840353ec1e9a921189fe0cca072353ec149d2e97c3c68228f3a768504a3da1935451d71052b2458255ad448181657d440ddd45748449ae40de993ff8a27a44fcebdfc9020998f6514983e8389213f60de10061dc33e210c3a0e0c7a10067d981e919a2450d16f2f7fa66bdc9d52703fef655ff4fc435435b9c1b084fa9f8068188184e030845118110afcc0a817eda3639fa9d638f234f3ba1606fdd7471fefc41b9b42c04b880f3f147955f79c55ad53622efae84f5bfc41c9022a67205935b451fdfbb5de1d0901c39e33828a3e7e48aba8d6302a460da358aa63b58651b4fa8719a8f36b8474f619a55df721887d613634d2b60de38078b2d6306aa586515c0da3b6ea5fc328ad8651a31a46895e681855ec67c5be3003bb8ac1d2b4920fd89e93e2fe68fc5a4c4ceff3f449b24146c65bb6d45086f3e12244f98756391aadfe582136200041b8345ebcb1efc51b9968fbb1394dfcc8d1ac918a713494926c10713458f51f71378c389aad2491bfb6f1b66ddbb6315bf19f619a4f1bc51d71bb9c58365a914f333ff94d24748cdeffbddb525c7cb01b83a4e7838dee2ef3f2ee322fef2ef3f2ae0b16d4054ae782056db942d35c7a688c16e37fbb351834c2bb2368ffd0c42677f79ceaeeee9fb7732f37096a1235dc6a7b96755dec22700bcbb2162f66669c5665666666de8da168b1866f5bdd976952b7c6113f35fe787f60cf2ff6696b7c13d419dc9fcfd9cb6f7b7112cd861b6c60928f183c40399af6f675778fa6953003b599c6f90b33c0136450f6e8a7676f90318b58ce16f91c828dad906aa4236da3116f981687c46e40fbe5afcdde708a7b89935941b38cd2aefb9716e814113e4907c453cb2deae6d51069161041f934a20e1d9876c7cc731ec827f9222f870c8f98ce32950d83b29be5aa3adb1d2c5c6440f7e38333a6ba4777d7de9f396ff3b23ef96bded45e14e0d3fccc23b243314cbeb6fe8519a8312bf9bc0d4e51804f44189c60a7d6065c500573ce1762084ed9e089bd058a4c51c6222f9b73ce2712312fece6ace2a58607ab4b8d92a5741c2fe924091ad2796b69186c59c841415aac116231f644c15481132e55489728cb5e31b4cb8965a2d16c49d7cb67b8aaee3921099bc553c6c49c96a0a103f9d6fd0b7cf18d4b09e8ba7405f18d8dcd11579094bb0298d22998dfffc83875dd9773ba67a7f4699f469f68748f9066f589270a7952f4a2f2d21cd5eedec4304df462caa5e0c550d01dca64703b94dff4304f63a4b99751e75440f979e34a54ec10ab3624bcda59a9d3bcaa193b5efbfc30d864bf66c2e06cd6b6d57e6ddb5a1c855bec9a51635c271450e638ce879c3edc3fd03f92b6432c5d306f82f1765f432d2dcec75c195459a90de45554ee05d8345e807129c453d757f4a04e99be210ff217501725746fd86c2e3e35c49fca1bbb309ed2a79ea6e629eb05d560bc66c2271f065f58ec0b67c5ce9d6a6dba8c83bea591481add28e7a37b8d6340101b46c500add9cf28fd08a2c308a366eff2a5e7bef42ee3472fe35b1874a146ca20ff864074957fdaacaa0aead494751ee695bc66c91b02a038c6109a294b0c00b4c898e182bc912b7973d331b6920fe5958b12ba1f722bee8b999999bb65aa0680ade443e57497da8ce8c24b72141934656954aeae546dca9d7ba8a46e3625b1746a4600002800a3140000200c0a864462a168389e47b25e7d14800c7a92407c589a4a84498cc3280c520629030800000002048080d08cd04d0250a31098e106c0adf602ac265b1a4c07b3f479c66cf619dcd49303f938dfafca7d9a4e0cf4458684414caaa78842a000bb7b71f4485742a956e6232530884323317100af3d90f347ab5ed6d25d3dd9b87adecea6c5e87594b6d1838f50ec874c23f185f32c8f872fddd7c1654e8915b834d788361149ee09724d8614a5ff669be3042b66303ef4241834401c167f48ae9ca6c74a67c7a8920d7eab4ea1e00a5bd3bbe7c772a8027b441a109f57a60ff1a979461a860726939df0e9333d73a45a5bf28cc7ce17aa266109d2a74347080f5707de57c3cac26ba78d676a7c150fdb995f97406a1e622268d2133554e4eaf08f432751138e98b392661ba26b196e08e49610d0876038ea05b3b66d36c38abdbffec4bea6cd7e52abff6be78db7980bc6246b4c159fda3f4039054fbaa13da87a16497e251ae97be1b9dc79904db5e990561781ac95db096d503bf25521db59541935242e5c3012a7ca2c391f13081d14180ef9781e4ab847a67490b0b7db9f1e3105ea494c43ec97b35a9152d0d157488de65037108267c916b9c076b5615855ba622dd3188935545abb926864cbb1bdf28a6a588b5d217f58bb75bc445c9d277c2b62a270d1d8730401e87994c239774f37ada719e849b0aa7ca45f1114293072043a13516bd70242e019b4c17bde90fa6e0df44c6de4dd2fe7d9ad819e462c41d0bcb5a38945365161d4255e59b5829001cb58a832e8ef0a536e5657f85b812d4c0b017d0014afbd067a7ea0219355cd1339de245efda69020163d7c5baa5afbaf4761c11ae8fb71eeb7eac0dddd584fca5a5771bb35d56824b000728c5e02f7d4215ec91cd998b0ae469b15ae99dc845cef93009b4941a97dcd5b0c2d23b18bdf888cdb51f9476448f970e83ee57ddc0c34e92fef13a524505eb9039cff4f5d51b41ff44cfe887fa58750faf5ae01e3596f26ee1db70e9a42791322397f5dbccda2a713836379cb14ddff665716ea77292d8b4e9e554f7e04f97c225c8894c880ce91752ce26ba63492707200360de5b2e644d09401c96c73046a160b2f12da07568cb361e94ae7094eac14e33ff1f1327f0d5043b6f6dc06ff499daf706b2e7be1a7dfbdb7ef24bf34b6c43ad8fa8c7c6a346f9607759b9890bfcca2c8fa8103a5acbf917a132b5cad115edee21abc417d2b0cf9abd706cc424ef688b10128135efd9e9708117ebd80e2be805cd0952d6b02ba75653f4f08595b6e01612b12ed86181db35e6277e8e4d04543c8b691bb218553c5b94a8517c7560c5aed408ecc8aebb2d852c25a434ff78d789eca491b87eda1fe51a7fea70cfd69e4087e6197133af390ba55e0e30bd20111119d22872f15945bc8dcd49c840921c5ae356ee97a4008853bb6703d306c877cced7a48cbae4fb1006b861cfd1745771555f00c5f208d6bea81d56342c5a559a87202e6442e6cb1f1f4d44d6c4ef5b220fb6c8b98b9df34c632acc8bdb69cd5a2e1c49013d20ca2d1989b5f8221caa83896741f74f1a57d143042bed7338fed1662e548cc8eaa9276725b51ac66ff9fb3ca6a9a74e53f0b1cc7b6b00ebc5dc001775792338d703167ca57ff04637afcc1efc511585bb09a206ced03d7badccb5121e3532ecc5bac651175ba4342f705c3e2602ca293f484bccf7606b366d53d43f72f2962c836fbb56225084fba33bb0df158fe54a7a8d5d0a05ac289116689a7117c25b25dead2065322f80a8b988f6f286ed14c537184d68bdccd41573abc1e2a888004bbbd8374a21a40c6bcc0a63bdb64f0c6ecd9fb91bbbf2f53da936902efd9fbc244a339e715fa21245e68cb98b7179c97e17e31a79631f4645b634a1e6269319c36786732fdda54f3c0d38bb3e634279982086bb29b6505fa91241e57f6f741a178f956962e2b0a737ffa1ceb55610d7869c88074c401fb7d6ddc7ce81669b081ac3ff82e80010dbb671edfa8dc2c0ac9f42568468b0a8e34a124de91f38975dd31d8b902e37d87f9251968e6b03e0c09037feb751b2477053900674a24addac25e0040b8242527e8a8d7b88939264f257cb6073f84ad5e21edf43981ce1e4739ed71609fddd67df25a2798fa2ae973f1fdd7d66a183b257231a6e3af28792ec2b4a7f67b3e71506b263d31fe2f81b8a225916a9004b37f5e529fbeaf8d37fd7f243ed661d22288136fa8aab32b084d8f6602ebd85abf4f17859a1a6d876f418d4cd94726e6cfd306b9b239af46441788d0d7c49f2555abd07e669968b85fa94cfe6f957aed0dc6f166e4669b44c629a649a888653b4fc127fe51c7856c90b4d7daffc78049d12b915145f4a489f99e84632be896bc10964dfb5160c4a2743cdddd0e66257fc0899d3cdd80582aa90546a2953a637c204d658b4995fd34b4fc8c341e447ef9ee97946ef5b2c0ab7675ad68a4982ec44f87696d48f7d702583eb491b3013eb4907f4126c4e9e6ef83109f696c2b902e75d3ae37fc00bb372fe480d5aeffeb319fc9fee1f73751a634ee57d3c0673ba9ad6de7031a0e80da7448694154d539d53a678732fd46d9d4d1b783e9e283f56af79ccf7f7c66aede5e7bb8b72a826bdd32a800c855d2323e89636ba18adf62477b37ca7afe44289e369cfe15d3393b3e0ffc030060a9717248231416e4a5dd54ea7e7a2c68e815091faf56fd679ed1e16e159c16f4a077d8736d6dd81928844e3490819aed4616d2c50d588218be4422bd771aaaf8620722326f066357b98ad011137373d4b14f56aa911ebb194475a6907e0520e2b603e6815bd528056a45bfaa66fd4a58b5c09e2ee19321baea98b1575a0b3dc37ad952c8a98f6d221bff721f86ca799d08c81dcf0216930e2f94ec0738acb6670a5f1b2c656a51ad8e0abf8c30a2b3852e1dec24ec13673856974cb01b66b3b39fb4baa210ded68738352a2b051ae7708f7a958b93c010862820e4af55758525aacc8637c052982d5323c15b8481d5e20feb4addbfb5d4433b8df9024a3eea4cd2815ded4f69be5998e7e4f3c0d4e2f701805c53f55046893345514100f39279415b6dfc737add6c5dccd85ea91104f65c50bf2080fca0f4132a1aa3a4b227f7bed66a18d425866786864881bd33ecd84c87daa3f88410bfb717b7742fb94aeada8f9d2f45440683fa45cbcd1bc3def25ce0115b1a50731bec5fbc772535ede8e32bb6f7af85c85e417d05bf8510882cb90efce510ea52f9c7aba755d31ec73832a6a69b248df425b9238f738a06a7ad6058609ace5763ff3e83bc1d25ed73ab768b1d033152dfdbf581bd36c13b6be7aa90cd42c15580ece14242495366abd68c7fe92ec18d73d918ceb7dff1dc748abb7667d470a03126c272854b3abf1a109cc7b5d6b40662a34c7859389192645f652ea28e9a29907891338cb7be39e64e633d5b67fdc1b4b0b7f1ceb18311aef251d0ecb4b5808d5c21fc6c2630e3e94fc5a02ae3344912b2cbea76d693aa21e9060ee63d8825e59dc6184f12ad4f2ed48ddcd0846ef800b537fa705cc06eb542ab6feb9cdf45888d6d7b1c94e6b3ab84110af23d971f92148d1c281736754d5be1952ad08232a9b11a3f733718023fd5b3f4da237eec54a741e4233e04d0e2109f27b0fc2854016d0e450840855b99132c8f35c9a5acf934a5d984b3dc1c2d99695e585cfca3053bc1d8a71af7b9e78e0a7580c50301ef5988d3486c21aa5242188cc924d0cd4eaffaa3fd3e0f55ff3a52a1854a3eda58e8b12ea22cc85ab694741f0f382b69f516ac3fe59af23c6f948245c5add1e65d317fff90cc86767e82b42d2c2a899052f0583880cf9ca3cde22722ca20ddaca9d3a42706c3a22df5ce90cd7c1cac4ebe237e2b31a5ee1fa20b556f76a526946bbe851368177fa57beb4e77dafa4dcfe2905ee84d0ee8ffafba28244d527dc32efce6a69758a7f5a858f28293f3a8ab849b45cecc1daa0838e2b19f695ec3abe07079b673498d9207209ae34e8aec2688d1fb5554ce9bbe98b38d5f93593e67bec184c7fb71f663bf7794a3825cc78ef758fa0b0bd47c2fb540af30e076a4704b3f69e5fc9335536654594ddbf1bc6b9c72720c0dc9c85ae869b36e8450097dd5aa699a929f5a203da0cffe97cbaf6f266101ed62bb2f1eb11750c18e9ca4300f3c59e84ecccc10dd78b150d076b82ae26b8e015621b7d7f3413d2cc792fa74e75864387593e8c333d6e7097e3f4134ce689cf268cdd92cedc57b30679d41b431a2270ba4ebf5663a23de134712980ff0742a1cde92e5c29d57ff5a46fa2b94a8e38aacf19e19795a0234b58538cc332fe2e7b315a79568498f32926accfa1da0483eff9b576f45266b799d207e3a34b53eff581b207936ced7b0d60f541dbabd520c2a297a1048c3efc35bd0d681a4e3618483fe4165d0a43654045d6204c4514b74759bbaf259605f6a0cbb099b74421f57b374591c140f74442adbc2839de69380b644eb011eb7392e3244ecb94b846eca83cd26119f4f9c2a00c309a8ebcdb1b1b8652c19293be95e43199e237a03c8fa32cf85e508e8c698fcda6a6e3a36e5fa25cd46c9a5cefb35ac5474cc4752ba77024ed842e853e913680aad284d3c0c801c9c986f4e22db1d82f641827af62de415aaec0681a5b37b546ca5859917eb6af22375dc573e1dccfbe2397dc77fc62f16a1d39c3a234c573ebb963c118b96a8b49ebdeb9286e1a132233522a6e99fa5321811fb7a9e1577214f39596a3d5decf0f7addb4272f3a0f2af01669d71f48e020c07ac3d8e2dc652ed0fcc1e4d54d964d138ec40b78fccacb955802ebf43f07353191c0ef7908fb4194dbbc5d57fd6bff8ecb170c3b459fdc7fcf3416bf2b993bffee963d61a82b3911784c458c615cd5e2db83a4ffaebdc231b70f3fe2a874657acba9c1e06bfa986919edc899e9382e4c1e7131ce44e8f178bb01841b74b71b730b7d1f14922835643c8ff6696ea265b928ba35d4fda027c8be16e2d928134cd52715ae12ed3360d3674e54bc0e6bcff719eabf179f91ebf8bc1c8b30f1821ac23e7eb2887afd8b67df4bb34c12c0891249b2048ef41c44bb07b7d158aaf9704f5deec51f11c4cc5e6395b39cd32e90350f217817f902087612f461baa94d33d954d39a50cff613c591137e930f8ad318f1b51c7dc523a3563f8c78562eb54f656f0ac2489fdc900c46da01cf0decd88be42606d4a976fe8bcfd8416a588032a0fcc1cdf3721fd872059777d05f2a4856b1f9a2d9bac03fc663ce382173848aef38995272b366c37abdbe2737a27a73bc308964b16cc22b2477ac73ac62ea598e1ef0e06e3bf67ace489f0c6a81bf951a85980f59fefefc41ae02aff09a917a0797c03ddeb838def4c1dfeab7d2962fdf358eae858a549f5d80564ff2f3a4432d8ec68a8d85eecc3e5a61e1cec34d66d92bd8adc2c5338a6ef86b28510a19174437c390d1925d9185ec14e93598786055f0dc2a627610cfdb27254754449a8c418f443572bb17430a438f4d4a428e6c1a249cb8430c2608f94e07147ac4f7831f47a516799115798d26c2d78cfe10bf677213c4f701d5dabbbee62d2e3ac155cde6658bd830e7455221ccddb249555e6e13ef82849e2a0f01ff9b10efcf97e267dfa47caa730c804e92659caed3b01c6d57b4bf4eab69ae755ec2e33a152f58cbae54d3cd99dd1758d7fbadb6ca91a769511caddbf145e8d6f38ed038675e3e7ed94cde5d786ade0ca5c55d77a7233c4f09dfed7cdb620bf5a07b0ce35155abe717e267b62e6d1885d43cf2390a0a42f9ed96c18281f5b649aab99b056e11dd439d01c21d3d1e9a885653997c006151576b705f54135ee59bcc78a62f11a19cfe37e3749ae79a4914bf41fd6e7a59aee27c79c3ad2d66787b659b79c64686614ccc6067f92377852047e496cc9b7102b0a1238114610f8f937ae256005853c8fa63c90ccb8a8f3f4027cc3b0d50b32bf296d1d74b459f30d186932567871fb56cabd128eb63bfc17b24dbb0db2ca980d96da66a19569fe867a76464be7a7bfd80b01fa25ac3e57ddb0edbc59de2859c001188e83c5793e37398a34f7ebbf64712b1d1e11490f70598d55f10fd66de4e11d6c33bf6ef8270d38e41f98959163ed6cbd1c0058e77c41066e87a21a5509521881fba44514d20b9aa630a17f6e606ac9ce54ed1697242c0386021469435933f5f3a5494955c2a66553acfc2911ff89f927031e77da7093c52e85397aa1f20dc1ecc71bb2f7ac0f8181c5107cb78c2ef90e798e2be4c6a0a66b19d370647bc239ea03f4bbd3a2b00740e2e5a58c1d367f2183a5b199dadd7e2911a7a240e66f9fecbb8095174dbcce011ff2a582b64fc6638919d251662fb00b24fd28f5de0adbe5fb0202fd62d63af4eec42a470581f09608d942a24f3b991581ac90020014aba57ed2cf6be335ca53e8eabdcd43dd03bd772a0fcc3b143d5d35878f103bf97641dca858a218b7774bb0149a06a598db924383310f75c74864c729cd19a52c2ecdc44cb197f0f0492c1e70dd748300fbfd04654fb881b922c4a0e417941c4ca047864ed0068577b527dc069227f44871238a4a402d8d4fc3545362e69d7eb308c3a2092a804667f3f120add195b31c72ebbb7cd0d49a14372ebb1c0f26063745c8b8660a3faa051dbd0454f67bde298db790490317e94450c7bb9def8ca7db188267e60a904696144c3641a1f4be6f95721837b493cccb92fd69c5647ed6fd7a4c3b7baed6932791ca645f0b5ad890d7ad63e876c9b331f7ee94cc51c2da4eccd7de49c11b1fd891b40444c00e25ce81555faadc6f842dfc4c014d22a954d4a5746ce5ff9a90c5e204a7fd278e3802a013e383d74af85d2814208a15b752aa09b8adab524b496b6a46cddd8322e55852ba0f585311dca821375a2da23b6f5473ac660957596b2cc2291384667a861dd284752a96757b2bd3c85210fc2185822e4150b2b136c248a4728f3ac66db9ae60535f58d154e761ea58eb64ccbc5f8c43fae007c94e80ffa1c8d0aaffe20c9943ceda6a539afa3a3d80ffdd3930b3dc192d3ed79f4b8de81ba779be6c12b0350a0a55a4438cbc5fee74f0b568c4806c9ee6221488591ed0a9a852ac301bfb9ec332fbed4183971fca3425084bc1f255080ba9ef37e5e1d40b4a586b7200f6883104e109043e99b61f833a77747cf9f7f675f6b0a198705877d945790a0fdef8589a2a47d060b89a0212de8822bafd2c3f63c148114ea4b070aace8494d16870f7b02d5eca144014d7fd09f0673e67f2db6e1ca2a6ae4346af5275e939990822ee4db0752cec045028c9bb95d48a582ab5d4ba07b8658160611f6efa6fc7811ec81fd2a317d81baba9632bfa36cf7b0f3f4cd6035bd0586d85a450a7a2a658b1d5edac5c513fe042a5d276b4e55808fa5518ce414611cad65fd993195564e64ad1ef27e3895eea8f7667ae03f89f4338a29c2ef30d1053ff0735862fe0b9d8a85a0704643560c3f47eaae96cdda4420982a780f875ee635399213b81126b4308578dcabf55cba1d5768443b170e69050cebecba3fda815673d27acf13a607c24a41e509d88265851b7c5d3816533181adc715955f5193bf0fe90e8c03fa82dfd531954905ba05e169e7126c2442ccd5928c13cdf366d50dcf8200daabb9ee451002f20a17eb1a082f810a9e05d4cacac5bebecf3a8bac649965ac83155330652c6a98c206547970ceed5e84a98a49adb0c1d376742bab6b5aa097179a874610e1e53fe9cde4f150a86dd1015a77e0e1d4abd826707beb26c785899c14810fdef1df30821debeb7907b4dfb48d2a7db0a8a626366d52534d6f122629e5db820b57d52ed8e76ae5fdad67708c8ce7151b4515b51fe023e482bb1f182d7fbd07d404a0edbbbcffc34030966528820d04a2ba225189c225626764e0dfae2e5494856bf6caf8b5160295f62c81f37cb1b89ecde3484bda9545277f9c6ccc3482040894f14e364283fc5b269ec9a2416f682bc76265c29680d985ca098af26efa11e5bf8cc9bdb34d14f1b264b23b9dd2d0847205c29fdc05f48aeeaadc72bb0568f118379b6014bbb3b2bebf71a4e9541a299b25421ee9645df975cc2009a4cd367ed5f729e407231061cb7e98bd6bf66339c30ae4b04eb9b084657796c2d0c94b01b0d97eb9b9c47a022dd9de138313b586d04aa4cf2477d972f849c7122c84834227ca441c85d5a44629771bf2ce8e5bebb8985f8b2b62b203d3b4212a7ca51b3f25537da264003b08970c243c851ebbc6905f8258bfdbf1d537c5ff6fb4facf68524c1a2a749bd84e49ce091ae3606206959024677706955b7b6e6a2a8eb07fc1f78ec99458a422516fdbed2481e7223fb6286fc46201d56b0c953e9b45a09b70ac83b4464c00923bd24ddb43275c49ea5caa43fdbf857bc2e2912853225b0ddebcd94d52444ef6723d773155b006acf05a082bf38b1c27b0bb78b2c2c293cec075e62549c043b6b48df0d33e789f2b2f70cf3772e1dbc5e9e51f57810f9d7d3b4cd4e49bf1f671c7a88dc12ee11f31dfe74494735b766257bbf1f69ed8f53ee2b2376adb11bbddc55c76d5f881e741386b627936c45a22462d9eb330d726774d72e469bdb7d36824fb77afd7221021832772764915a89e8c54f52e50b96e38d354e27b162e944d92d77498346e253c1d83bbfcecf69429349ebe4b7e208c6d6e6bf12f76a82f577dc318e294a180bc1c5b62f13dc5e0d9a4589e4c1d667545f9976e418a26d0ec15ac755a3feacd82a4268ab290596ff84cf773b5daaf659622cad78bba496ebb74a4da67673c745d76c908dd69a3a1e70941c1445159f9dfe6361967de0fbca1ae5333740bc7c08221c07280a76c83fa44e91793f164c2a4334ddfc42b791cd8405f9a12b6792ecef386eda56bcea2596832acf121b2391963a7959f9854538fd5ee23809568b0a938bebc236950a0ebd9ef9c2a5e8491fb0eb39a262ca29ddf37bf533c23d1477a4b4b73c63296481ac171a8cb1e550019b6280e1e1fdcd7184b5d0ca06d9846bc73032405069eb51033254437452dbd0512f4b284de6292db75a0e8267291606a3c7883ac719f9e9b1b4ad224b2f84b9c88ab57b5c40b5e0695783295fd692296f5f11fb2b1ebf9ee25e502bcffbf37621144a71c2e6dfec06097e22bf06f17768134f8856f24bc809043fa3b338cf462accf232754e8f699bbf97cca4223cca9612a2542057d8789573fd7c3895e1970310b05ba9c5efb244dba14536d95edbc4c9f647bca058437a7d85098ca5472c71e7f7b63764d82cf48e914d25b5d4f281964f2b96ef69bc0704f0295abdc2c3526518a650fbee176a1d5817a73d144b2d7be984dca57b9f912f13570431eed019c22f43f316662547b78e72f816571197c75d5bd45af31db8fbf6b04ab26155f518eb63d9b3edc4d13c12f8e300881521f20c21ee72a9e2b706fd4fee78d775eea7c0e2b5321278c913f770b32d8fdbf3f368b7cfc2f3110f55202c8373db9338d48b3bd9398b9429498830aa187b59add40e16ea3de6503a8c4fb38e7b4cf75faf897fd7e14978f4698faacdbb431001181a0476c962a0406945c6067e1454427a2ad81c7fa5ee4db5661063dce5d06ce5da00967b22e654a6fc752820baa068f0f1ed7c4c72afb277e4ff6a490ecffa688a35a70cbe183a0929ec8cd4f348e48d145f45c8e3214e4864a4edf92e0c4ffdc06f1bf99d35b5c889499aecaa5117066be00e62a6cf86abc554616bef177e5b05dac1dc12594a8fa6c7f14f4df55b75f575542b8542735fef01eed7a65d2921f8584222f3cf2a1ecd668ca87115e6a9bc055067343fa61a87f0e525c918996bc067b17c34be94cb10cb6ae4a4770f71c55ee54719a1d5ff89d8bdddfce26b12cdf56aa86ebacaae8c7900a3528251ef8a50aaa7ddae0e427bde2ad738a04291b69ef8869e2d6863e0e9aa08c807f667625e01f0d5adb4e0958fd3b22d03143e6abd51f5341430605b14ed840e84a3a1b798226e816df536f122e3b36c6bd116c70fc6338cafc00681e4e1905a56d2a39039e02c8314504f6588cbce1da9a40ce8eb2223ce8ac2a2e09c7df98509e4dbf5ec27f136b06b62a95049d69d9383bf8c0762fe051bc79ad63677c85ec5e05540191b2f5aaae3e8418dc0cf938cbd00682e87d7c4b3395293e4c87f91826752fd8ef6625ca7615c2f107c48111499068bdd166e382f99ff8f0c9b42e6364774a11f7396306903c3323c3d7c850faffa4db5d1bfa2f7f12444d70d1990641f1c037e559f24bfabda62699cb12b9f8f72eb9d6ab5396704b567ec3e85d89bc5b207605c1fb2928989f966b3c1f331a3426d0a1857dce3b02577c0c8b667b2bb3d7ba04cc8f27cc17e80f08f9bb0882929530a0dba491bab9eb7520ead756064b1a53578b82d52e355ce33cf7c51bbd492f268a5086e8dbf9c81401889a2b611239528c3e90874d61c5af30df46e057929a6832f0bd344e46c62a09f0ded84cfd9898e94baac2e14c9cbd1cd3ce6d785926d347a3a6434ae94126032748000eb82488d95bd4abce2a3667c9b89850cf156d35a4487a3f6520951e6f0a3bd9d0fad17feaad6dd903a05e533883cc586f3db778a742c4d7105cf075e917c46e8d6c1d5039655b9ccdcbc4b5da2ccd3ca55e9bc3f95f0ce833bc1d819bc3a95ffd1f986d7333ab865362c9ceeb9a4dd91d5689a2bf60b85f8a7a49a5ff6198319dbb59140a8d47370026475b3baa45d78c5f0061cc188c4c1f6d8b59dc2871829c6776647ccfbc55c3c80e2e79f7e8b0dd3fd1c05a8e7ab0fcf8aff3a1ed265a5085715ad594f508c5ebda885e4ad07662643832b7ff6a906e09f52374ba5983279e8289c8fb47f191d195c8434b974de943adcddffc9c8c32c52a9ac11fc73593a016daeb5554c6567ff4c64a031b5c69cad2dc2d5d6b298bed28d922ff4ccd4d78d861c6e10c9e7c39426c7e8b3ec980478f865975ebe808a9865a954675e67c0ab3a7bb5697844d015f7fd00740dd7d5330cf664d23480186ef6003a70f507d8bb1b8d57bbb59b08c5780d7a92f7c7cac59f8ac6bcbdaf10fcbb3bcb38c71379a1b268da17e72881573f0c3b0e22cf2405cdd2a28147f3771ef6777d3c9b0337737ae155e441ad508269719521fb639cedd353981fa9105736fc670caa65285cc1314745579f7568fd36d24cc1beaddd81abe3d2e2434753b9fd95b1feec7041efe7342f7fad1130fbad4c4fe4d2b00fe1ba71f23038613d5a966e317c7f438bcb24894387ba703c2047b15374f589f42cb027034ea95f30b763355d11a4739bff1aecbf9391b95f3c38e8b9c5fcb982b1433e089472310282a13ed2880048d3b0a9abcc3ae651e603e4a8fa04218d7ee492527732a75765d1523e8be2ba666ef609c54472ccf2e6d9cc3fd2ae90765b3ed20d2b45034aaae698740c91972d7812370272607af0d87b9d96a112bc67472d5f7a084d03d2c7d36b2718401d4932813db389fa16b0ec7db10032e1aea7a4d8941e8624536b7ddfbe2523c9a8a318e4d283f26adb70c13e387753e43357f11655d42032bb43f46a213c5ccfc804e627806836c0975d700d928c8d62c55833bdcda9a5c01a9431e315e7cff696838b82ec547b85fd3dac08b198b08aad26c66b8a78fc3c149140d0d8ccf72d65867f1ac7d2c8a150ef96f8f1f163e331460c4881179418d164e514e3259de8920c2a4eb58a084abc60f1ab8b61726bfac1a0dab3abf6ba56a4e075ad5d9bcce516ba80b080081eb04a135e9fa1550b11bcb0650cb9d818f3d251ea0df0139bba7ba2991c530c17d27a203d4453d0524620ed2f6c51f7617cdfa1f91fa79cc56942189abd86e0b19653a4a97ae281cbd810a527e061e225b5372e60b7086cb78abfb48d9523d10196b7be226f4f7896451f69987ebe1474f055d4698b2c09aeb2b7ec692cd339fdfe885706e9cc4091224ffb218fdf9adb9a4907ad24a9cf67c98663045384f92726278c9a26c9897934bf74e80401475c58d6a095eaa1fb773cfc3f002b877fea65728476261a005bc3327bf9c90e5ce79ea5d343bc216a8832c2bf7eb4845a099ec1eaa50f67d85ca092cc791f63539426839340f3908d05b7d6ed312c03da087316a381feb09ca781a73bb5b131194246bd19eae28a708f05921fd53f88f847bfa40ffb7d828970025e88e2d8e86825e60b8ab8a8653c85f4450830cd81566302fb035bc2c46f745607ba1ecea939a957538991b832a6309e4f46a62733338a749a48265970669ddaf3af512b0d235f31a4deb71d383c9de50df7ce2fa1e09abf46f80c7c275a63fb56fe34f207f24a4b8e8f78db4511f911f12370b655b0f6615ee350cc17881b63f706238a79da088545a66e93a3795beb607efabc846485980aaafdd472abbecacddb3edeacfddb757c8835d313d50855b3ea2d9697e13853d11159ef265f150828fb1fb661683bdeae99723b4792320f454c813498ee3f02f83892b40cc735848541eaf41c6deab4683986bdf951dee94698b7853af4c76c5c411e4ec4a53a7459a7cca498838cbdf60597469266be562a6d3e698c868e2fadcc3810c3e306af47b67c4365c0dc5b24884f78db7cb4c3b5a47af392ab895157d3744af6edf632136d3d38195d7505dd28a640211313ff4f3d41e787f10dc379d06204da2470c5266d324f79c867d0861045b634630e149811b4b139596a559e49f204cb288081b3304d693b8f31b5eb4c555ae21674bbf83d4ab693efebf95a9d35c4885f3de983a9a56c8bee407040437a0e99776008e45dae1e8b3b03401ce8f7661676267dd0874c921548f4fe3c12d4307191f4ca80a24e7cf6246ffef38f1542cb98a6e1b1423a8f1e21ac22a6d8ffda37a0997d5889341f9bca877efba97eb4a038e6176ea558d325e3b4fa2dcdd5d2cf07f9e766ad0040d62b350c9c61099f01110ae18a9c4a1d8ded5471f757c720788bac022c852478cd7989f00e202c33ed65ff58aa07f3cc45dc432891f9d482ecde48c52b24a18c70c9936fab54700bc6208a1435416885cd1161fa9adca5d778be695e6164ccc7924e124f0959d987787684dab6540228e72660e8de1113983048fa6b2d33b8747bd4ccf9c4bddfdf1f262f83a275d53f7332bc4f6d8e37c67a0cd653b960502da974071224c401286cf89d8cac66cc23beee66c8855ccd1d5aea479c3706a53ad90d4914462df24d51850a0f0db86c594007efe4b1080fbf7dd1a72acb60ad2203974763fc08761c6af2098125f0f0835ceef65a19172a5418d20fb4d4064ca4c1286386ea595d3a656ddb59db73279b22ea84fb57fb1fa8567e57337c17fd5bd42027910b16469b715e7c974dbcbd6ba06aaa1525991e4686f40c019d5bf0fbca3f905906fac8f26c5738a799928b739c2ae44ace20852ea414c9d387e16a79518b0b921d76bab4a52cc9ad8fc9474997ef607c57cd2df50c673a43597be3994938828439c8974362d08344dc17a9ad0fc1e75c4d86f5a27dc75cac2f50681ff10811dd2c01c48d0bb70deed3e9eab681d5b556a607331d49f4f4b635836cd572eb20550b0d42f59b223c5dfd2e6c010f972cf83088de2ec96818c9fd3dc12b01e0a5aae68df9d984c877fa841d115a23509148c9e51692ea3e20eb45921a9a09d320637578b34ff7d40d3dc981f7685b12d5ba2e4a78cbe2d3a97513a434ad3aaacf6d39fd6a3f262964e840a9032c7fc44d1a38911af69b50909b22632cda1291d7c8a5b73cce2d434215d4a25b8f6ecf69502b9101a56f587234393cb7d584c27a8a5c50cfe6c6a10e6029c4bf54257c5916860336974cec13953ddceca1fb8c6641f59f4afebbcee2c2e94e697f19a7144da2fc903a5c6a87661043e758157451add1d5d2b686b747b490ea134a35b651351cfa1eb07ea57cf7bb6483a6bc07931993a29ffafb4ed8ce525fd2e7e00cf4222b740075451ddd122b330d48a8be16d37f395b44f29ed3d4a204fa6c08a9c491d7e90aba2537cdfd09370788730ac5dc808b2f2b158078c6c036424996942091b653782c2f17a3ae9e6d84ce490248f64b9478bdcd95fc1625d8b4705542465017afbe238e202760dc7d32280edb7b2e28672b010720981a1602c93da8aba489de24a58c014bb84412c8f22338c4cbdd01894562e6dd96019737b4aad195f5e27af6441e7e58835275763384c826431f78f62a66378598174461ba8496560877ba01b8e8992a0e85bb5e0cfbe0b8a1c489adcb1a8a5df113c00669d91e9f61db4ba043a1f63a3a424e40d728685f607eab893080581825e82f051c05b47cb8302ebdb6132eb8b8f40679dd67cd37694ef96b47acd33e10aecd126651560049e5e40c755d868674eb4b822676c942c4200ab41d4237a2bf05372c2ebc29a342fe58a08a10066549f768951f8f7b080e25acdbc76ef519b147f901227410c1e498e26e7ba769f3850770a601880b086e2365ed4e5e92ec5b8ef4690d8285eb734afe485b366a8baf20cc6d240d41301d4b64af3a657131a6005c0700e13929d1327338e74c50cc3644b5f047c400a94b46607fb3421de629a06c41ea6a7354a69fda3c5e3f8b89178267b1c24c7b032e72352c5ffd7a904697f718fee513c927b175d1bd12203fc3de98f2bc088e9459d026110297b0030e339dc912136be4a59846d9cebbb66e2be2b119f7165175e3d5c052fbff0c111581ec7c6184e8a4ca4d4e1caf11f7441b7303735e6fa5199a235afa9ae7cdc335d867a97380388e0e3a6970c959f17a2c47fd84b709cb83c12749c265531759319d00db3cffd276c565b6193b7a2c261f3ac211ca5d45c037f94476d43f52a7359a35597f3fb60f807f4c243f083b79261ce1c5c158185c107d3088c04c57019d89eda9a0ee3c137974a10996999198e24b6875902f68882a50e3e1c32af553a32034e4281de0cfb292b8f709974206c70d303181847b8d78e4fbc942c2d356e770af8b3357b388912e6bca6a466556a930ad32adf825ab29eb2da03d248be84478df2fb505562a61b34b6a9fe404f771ed9a398207063fb96dea60175fb97c74a98470c8982f6529203b58474595ddce4f048bec6c495c927e501e9eb8e82c78a160957b8da7a9dd55efd0868db50efba466b39143dad72083c08ad6c21c3a3a29f693f7b8a85d86ce7d6144d308a18c3cf7c723ec26c28949cfeaf562df8b3bd515010a1fbe4c8dcb52bf78b2f0ae2dd026d37c4fc6c81992b706cd772012372002f72b42cb4f0917f7661efbc05104f3a10d39283a6fa02d93c8c51616e2138ac45b35db396aad448a0d9ac6e0be3f5d4a822c84247ff43da2ab4d39891a55d5446818549e264d3fe79cd39b01e1a8045cbb704a51d322d462dc04e9268b4e9c5706d9b14dfe3361c89b6555a57e8ffae1f39bb91529b79152d7694814f691957d0896e068043514e784c09823c812c005cb984169c9098b30ea24ca1e2b0927e5712c50b6b6bf40cf431c8dc752b666e124d95bdcad2a15ea74da391b00bcf319e41da77e5200bbfe362356e61292cf8ee6bd071a792e0397a6e864be0518acbb91fac586c1d7191920d37f1dd08b542e985e5b65204cd7091e8b14c4465d3310076b3a82a064c65baf3090c0021151206ebaf2dfbe9840583bd456a22479eb1d90db5c09669d94ef6d4d6fe624d3b13f31384e1d567237407636eece1c91883609a173d3cfbbb76566699073b4a583a400acfdcbde24e422fefecc33abbf2382905d05270db12a3837c0b34b5d65912d5280605ac11693fc8425c3731c6f63b7641eb465d9a19b1a21a0aa233931d9212c32d37018db4226fbf283dddb0615ef3d28c53e02a42893b58a23c8acec793e2b8a577e4dffb68cb9a9146599261515263b38917d6233d9b4995324c8f18c8c04462a91ef35c4599ee6c4c748a38f6aec9fb165ba60f505ea52d1d10d28f512242aae747858e01e3a22af55542437c84c641db0d90e4cde3489ad66292979cc312c931bbdda7e4cfbcb2d0f27481382d221585019eefbe6facac8bb31381614814e263cb89fc2ddc89241bf54b02575613e339ee16981358eef77777c6327103518274ddb64935c5310e431af2383bfce610bb7874a1c33da5f4e438dd78978623c64355283e46adc2404a9009ba95c68b69be5e40ad173bf6ff2ee6ad4c8b4cde772abb67a794dc4cc8f4ca54a401b44cfd150e4295790aa37db011b2787afc59c9dd6046530e514076b08293838bb44be524793050f641d427a8c4d79805863ad1aa9ad1e460dd172c47289a4ea52d1e94d39204085a7b7b90fbac9fb75e6508fa60de60025647b28b5132afdce63cfeb27e504352adc59d5554a8ab27b7aa3caf3e349e8eb335e07de14e020f99e1f870ff9081cad80860148fe80a103d4ba76c1ca28e9585a3746118046fab6a84aad53d9d9596ea9032cb3120a8bb921e06e1532dd62940c0e1d0b2d03dd7eb06d124379bf24c199b4a63e7aafab8535addc4b233f1ca88fedf068d6cbb614a9de5d261412090931e73038e26710430c6418fd33f402ceb1f0924699aa075439caba8d164fc728d36cea309844871b388cae7c533878ab02c95b8e955411df645c074cbc1771e865ec4fda77de18c3f1972e7d4ec51c98dc29ab4e98a170db29dacfd0f5314baee9c48e1447973a2a24f0fddff45e40bfdcefbe545c11818c06993736a18d1832c80306a88950376114fd277d3d521be2e7d876f1511c6ee1e74ef372a5a90a23c26a73b0ea4114e59d5b6a68543295def9ef632f4cbf4a7bedaa5dfcea4777bee6b4a0b7b9bc70c15f4e5cf5d7163391bcfcbbc70205c1b37300c46014d91b7ccb5545476c29619aa29eda7490ed266ef17d547a1f51e4a8d692714ab22cfe94e1be4000caa0fa5ac24a943bf39c71cdd4955be433dff66ff90a55f934994b696c7948043df6d05b601aead1f268262b26fba135175cb57eef9e02526523e667d26c859e1fca66af7d68faddfb995b4e574d833f8c9496f5192679df8aa221e851ab4f84a7137f188de97168954c549ba3842dce976be3b1cba946e1d4d122cb8fada50ea87b47a8f2fa74489bb3bb351dece1f1d3d436ef5b53cec942fc0117f2da3c209e450b3229d5c560de03b6fa125a8ac76ce5508e308a234603b10aca3dc08dc58b9070775363834336aafab6512b28d636b290733085455290afab3508d83fe606b12e6eb94dc642c0d6bd30ceac6ced2fb3c01783e2671adf8e0a8e8a6e607259f9ad99a95c45c6a51f52faac7cf5e4af40e9d00bfd9513fc54d0ca992bbdab3ffefa5311c69f0d323d9651744a9a65de7908db92264ad0804a4962f9dbc8b99a734141260a60da93933566101feb6c6410d3ecb620b13d1cc474c091f1855d3efe853625a62ea717ffb0ce4e878895f13a68f193a24caa54f7611021356818a47bc2d7430fc59bc1cafa5a198b71f2468d38ed78c19d0e5ab0229b7826a0becfab9809b39b59c1b27be1497a6b85add4bf00377aad1a118d7dc88a4374ce488f0308d48a07e423745ceadfadcdd4c8b3f7040dbb5fabb846e1f4461fc738743ca50f5ed2d4f80ce0cdd8c4cf83dd48429437b2bffca89af53e7e9c84f9c9b39f534c4612a8ce4bc0921ba036534ca9b76b8e85a19cb655b503edd664353a2415fc482dabd89e149609fe30e6dd26dec5e91a56f5904f08d6c265c62011fa03a1cf10ba3918962deb650745c52892eee15597210d909c5668973234c97f4930c379255088138a38bba57236015ea8e74297e9fd0000f4abaab905772cf5594d0cddfcd902d709a1c7459690a9a890e722bc75aa7bd7add491b0711e589c569c223f35dfb23f4792c9ce1caccde5f0265cbf40dab8e16354e579480d2be71590f4e9867bd2650518614c898985bc24167cb986ba0071334dc62eb2e007bed66262564f5a007f276be620012cc80b13b91cba85eeb2d67f3e1e30f857ec78fe3ccb6c113923b154a8b31ca84bcada010e99a9863bd831a57ab5efa0073423af80f3aa40ddf8bc393aca0a8f37eabb03e1b7e343cd05199c5ca6dfa7a667c3d7b6fd41389cbb2d0a03b1d5a192116b14e43f8b26b4c92a3ba51de3b07288668286191049cbebb710a66bc14d311d748a92c2bbceea929ada6ea57392b2e876d40aab33cb8284f0c9def1a812769a0ed33b23a336b237c3a623e2975c54903a415b6ba1d6672ee6594ac509fdbfe7428b147df5457496de05ef60bd65331e00f11b6b80c21d31c7d873d33ba0861d16f08a4f560d1a0665dfd540a783b8b0c3816cfbb1dc6c33539619b7adbe669eae4621027d3f39d3a57cb3639e2a8c03a3a728673832a851b08901874066eb047ba81ede82b38f94f483ad699ee1e9a80c70aca291f8f6d328d3d517fc9702d611221aee9880d8943b0d12327d204f9ce0ecd826cc9d46012f05bc76f6b0032c1ff4bd484e1fd3e6d1d2192d527115dff6f88a7862bf90c81af61864e21abc9198ef3dee241e93a50538f2d4c939e230eff8be4a2c465942511855e4398bdb40b096ad6bd6914175bb2f5d5a0e1fd1a73a994a569d31945b83f3c87a388f3bc0de6286805847f314a69a11be5092b747220b2bbcbe2aa22ab90f2ed72e8b88e2abb3f664582b3ae6bc38407136d25196d4ffddcc12f537f0da7247e7f303ff272c403412d5a04a3a9453093eb349bc7532b09058c608e2461d6ba6112fd4c2f3a09739f810a28ea367f9a173481ca52ac5e087906e26587b9edb1e2c6cefe05160bf62559a120f93700d5aead0296678da3df47b34a4e87dd18810c733acc6f06c96638d54ee1fb5e54e897b01ce406692abf23316c0bc0d8bb56ac750e0e0694e681223248bf461a94f8385853213768db1d2a4c1a1c7c17bdd500ee32ea563185d549da86657fd1794635144020952ae88f8d35412c1c4054a42df44829d26e6ba256a5613ee822c2334e1b2398a7319373f1449c98909d13890169a24788aaf884aa128816638331493af2250aedfc635bf14c69362e85875f0298888983cf19a593ccf39682bbbed82388f035b66b9e11c7ada176fd5c851bb8cccd36fe6a1f11b604b540919816d9d358b5278092fed1b15922b5e192085d46f5e6ae8a153ddb994dc69ebd21ba28b32c9175d26e0bdb5628868b26efc43a3c3de369ed4ced70a918a2208d6c982416624a1fbd3980c851e223b092e07901e84dbdf43841b7861c01ff9ec2620c6bed6f8eb16132feadff9c674c2d839546e9ae4aa7a18a540bf43d4058db08b5787c08bdf2de4871048013c60933db4095c30b1d390d02f0400024e872794a26a47adcb9808d43ae6a949cffb786fdfb8ee9d67346adc9d773e65c301c3377b2d21190199b87b1e1e9f173b68ae98f65344bf75071d39f086075868e486c946ef94555ca795399278e425d321f929ad15b07e50d8dd13f68f52528d4bc18a8003898b21f14b63ca157381c65939d22ed597c5b2937ace086e547e9c72d017dfc33f2bd30ffcf542026950d04a2b58908b9da775891ff3d540e6f66d69c2a779cf79ac2658a0f71d85982d9557a6769ab1d0f51aac784c4c9cf486562b6a064a5c79e0f5e2b0a8963da996a350f24e54358301d3b9b1cf6b29bbf70883c11939afdaac6b0ae8a8afb305b1f5169b956625eb24ed92ae2f28bddc76c6b16192b93fdfff58614527549d8dcb9a8cdf4df08451f26300f8cf3d49c6e25b4da90c90bd8c73532fe6ce44cb0546bb4356e7eac60aae701d61f1dabb138c31a4135519fe177c992557844382a3c93a8133449b4cf6289e4ef883195f74bf1a819a1379e2790cb13b5880cc66a9c8a8dd8ce08a9144710b9304f1ce2f07620c3bac8538708052ce8d162b81511552a891a77e46f1901d39749dca23c24686d0f6a87a207cbc52d6e26c0b91db31e9c082aa48c18a360b101b55c7f12ed5bafd9bd55b6c9ac793660de5604d3cc52620a2ead5a4a30b4545e385aa3a4afc36466eda7310ab748b8a26fe89a8c3b91e3ed1130f53c7d6075998f2a7308e7642e370b37d8fa6f62329b916424e532ef88ceff5f828615b6d5618417b714b8950ddefd9a7174e4813a829bc91038b60e896d69ec5e17f2c95392690711088acc194ac0693be5acefe7ea5c1516be9ae361a35b0617d19119a82e5e370213ccde3cf6430dce430dba8f59a5033cb23f1581673bf9c2eec4731a0dd6586ad74dff6610b5bcdb4e3ea89d3de59dd4bba84d0235b0a563aa5180f7628bd54c299937c78308ea388cf5fec5d95d39064e352ba6f1edaa460ac6a342d95fc0c621c33fa8fe3d21b00e59c73d8d7b729cc2cf6b25ade85205d4130b50f924326a5d5af505fcd1cb0c8f49d7af0163473ba4e20e96bdeeaf4b361ea5cd6395789f7306f82916ee0435e83ef658e4dcdaac306eaad4824a653405d42c9ba0407adfc69bb277f225dbded4c88431411dd88f1bcf60e89fd83737eef2349e1cdba5714e8d2e3da9336d563868c88287f6f2ecd54020957abf1ebd08f6495403847089a24b64cd906623290d058519465e04af52575c962ee95792ed796d437d7c9dd2fb70d3c8e15459395fe23d0c8a8a611c8c7dc25b202e51d73e8c795f99c0fec6ee20da92545f0406c00d480813d01f6c2de04c8a81213b793c6e1fc654ddb761847fc7064e06f942fc2d0083bd3a4c1fa0813041190843b0f80f46a9ae27559df89a70b0516ec35066f5e5adfdb9f8d25ebd5a9bd89f56a1c259a1f8397503dc0351f167c5da3965344476e176e96a4d62fe6c871689c9671ff6184d94827ced1d49c95ed53cc5330d2da26540a19edf06c7aa2fbb2ad2bf05c75c8fdb0da0298f645da46e103e85c98ed8ab5ed27214d3364e07e3c61cb9f31e6c6d509c2aef8d2b326a93ab2f6762a016e1d7ea1c5ca64bd6b2764db4374b87a5bf6f4074ce33609f04c395e30be4b3c18d217a7d77648dec5888a1edec2deb487ddf28d07a3b218d4888913f090d17576e91abb5f237b067d9abb4bf99c658e7b91be362b5606d859de81e7cabaf6f7c04ce8cdb05e6693abf9fbb0fa8eca6c73900f3b27f7c4b6d6f6943e1fd1646352621aa499d66a1389f1c869ae03a33438f495daec9257a4898ab495d1f8420cee8f1ccef6281a2ce15c6831c1c358993bab8077fd89dbc4c117b4b9e44700f80b372a7a4ee38f527561516724dbc62f8d7df7ee505fd1ab537b17ead42958d06780f3b864695d672a1edcd30a99888cd680d37b27121c6d58560c92ffa83770a58e2f776cc7e003f1b4f9010d283b173eb1582709d00a2c995455535a0c667cb60832d9159d706aafc440ac9a4cd1d91ae23a98e8e780ec77827d86de9c02247889c85f0ab5208ad575083765bc8593ffcaa47e8c72ca9f2d4c399cd47e06b3cdbe31f5e3db72d673983642bd5844a292a4f83a07a10152da6032540d86194292889568e174b07a64b3730f74048d1e06d34a98de172b1e88da3c23303e59caf3c3cecd16ac8388d6acbbd2a26875b63ee827128c840034aed69675b62984f5dce1e737170d0b0f027fb9d0d635a013ae0252624e5a64cce11b64c0993d321b93ba1de6c84ce5b1e76b81e69bc65112206f8ee7e8fd0a7a626ecbd31ee6dbd073b4dda0d1c1e851ba417bcd930c2054230f8568ac0cf54f6c9bc88207e0022f27697c9706aa940c6de90f16ec9aa9a269dbd9dcfa48e179a9034922c12b444cc72a8cfc4e670a6b3782615e333a101e5d1f7513a33496f4ff2eeff426313951ced75bf946001ec53780f29107580303d213a335bdbf098ce74ebd4de44fbb50815ba32b12be186f1641daf5e742b6ecc81e1026a776a4619a251c25c3457aae2b5845d1141af43c3b3b5403dd609dd9ab517d17f2d0a5155c877752191aa4a633a26df8e2a1702b724802c3913f4f77a6918f85d5de4c0f996d26b775fe1ee89964257dd2713813215fc7130f769ffd5156dc9db4d9d5c0255ab20c9a691aebcb6d59a68a34ab495267366f25914a5effa372fb2fed89526948d98134f4447248d835f128aaeb954e2bc919d9075361551b844a3624352f5f5a40ec0cecbf437174158896662f94ad6455f33d6c188c8d4d2cf6321ca64026fdb0341f1b274f17a056aa0f339917b46034875a09704357421a5f03c82d2b8b60c36f98723de825bc35eb3167db37d73b3d9e3d3adf426ff3d279b0de8507ba5182e4d2b2138f0448043e1574aeaaa190291c2460e895130a04fe89791b1a9ab1c9638817a8b177224845b67011fa6ae380eb0e09338a17d4085d158aea2d4f3fcac7dcdb323a98ab2daf13b3917d5f06614135720340aadbdcc01c24b04e15176619aa1478584ad35eaf3b1d3316d5d84e7d19923838b9777d56131e1279e185ed722815876bfab57ee85a3d3586a95eb55243c6298e5b1fb6451bc3403f046913f0df0a23de83f6d727283ad79c4f70337e8e95b811afb13eaaf507935feb0b951fa484a331848a098e7bfd33765945e34c03a9c7c34b3bd585020341a44a3f8d45685181645c62beed75663101a257de1f4eb86d65817f9ffbb8ef19e28e92f341abb9d57c02ffa815332411934b457bcfb1a711f1bb8a116f0e7e542d5870d5f9db3bd8346401be71068aca83297450d588a568752d8784dad081f0604e8b9cf9c57b9278d92fd89749ab9694b233f8a2d0a45d3508a2ad327d134648cad24e6ea358ddc998e1ada96d85fb5a480078a0f12109cdbef2b9e38ab2b1f8ba72cb2a9463ceaabc5091bada842b3a1971cc97bb1dfbc4bfde17b0133186c742f56d51a9662a0148ca19eb78fa65601098397526083dd3f3882a6edb1ce7e162c383e55d15857cbba1b501084ac66bbab68ef6b1a9ceedc4354a19cf23c59381cd1d42532abcec4d596e71b3efe575845d6dafe3a90dd95d8e8463f3d5b0fbac1b1d41409afedce46cc4454e4f645dc9983738ec40fd1caa545d3c5d25c281ba00101f14e1fdfe710bef22fea76ad144a3cc3ccde53756da1d3b3eafa80fe47bf15a134274b47ff912adeb514d2604201c818b7d7a84fb795c052ade8068e8e83dba08df6ba746e932f125014878c4d979e4ca41321038d7badf1ee01419c6aa21a81d7171d2aef10dbe1e35d787bfa49908db001c5b5efa9c7b20d58ba9fb31981775406167c5df3e34740ba52b265c85ebc0a13887b4f43909bf145448a2952a6d11b89b09063beedddf0aedd1c0056bcb3a750c8a1399cac42f19586c471c929a7828860f28ec1a63eb5d6e7c70b77c3ba0f6b12b3e2dcf1bab3c572a6deb2bc77c8de46f405782e4a771fab74de495b5e2b84a06b5f5a2e602228943829425676ea9ea384de84a0964a1f49d9d621ecf2a99d7bc57290911fa28cd4dc1ae90a479315bf3eb2775199f6811cfc9a6862d52a7ec1f0e29711d66d08c5caf6605ed7ee9c65d6c9c90ee9afa1c7fae6b33fa1bfdc7488e6361f23181c8edc9729a8e62fb7a6668ae52901f72d3c0f2c8cee55b211eef76b8aadca9e84317a5ed97cf1f51f72bbfcf19b8c073cf09e85f6c142110620371a607dec981a5f5a524a383265533ab12a996d440faf49e815ed041d95b96c5a11cac3d8c2584383bf94e5efe17f0c4c7be0db428b98c28123296ec31dedcbe5faf5c5d89fb59ab6893336e9c8b9b7f883513c1e5d0c7a4233ea3ce9b9c3105469fd641af70ab8d6d22065bec37da13b6446608f2b629bd5ed03ca2cfd052aa9c055e43dd5cc1eaff8942a91d77936b8d86bce82a1608b56b117c1a9829b3ad4bd70574528ad0e10520487ddf5611751ea92e24e1132aba8a85ea5faa258ca5bf69ab5ebd5ded45383d442477f4c852e1766aedd16e545303e5604cf27daa50ca68e9b5b15b44c9bd42c7dac709a88ad7ac3d9315e5f828caccc680c9cccce0efb4bb2f4ed3a9a2ecf596af549bc30b93d4956de52960c8adf452e7b5578f390c06088d83e118d7c55f2c7c13bf4f60b1eb28031622fa67dab9c330e5ef1925e4508ba53e13da440a3fa59fc98da45327f495d0bcd27d118984878a6f87dbd438ac67dbdf11bf0dac1b0685cdf65e54cd3b66d907a338adbf69f8325837b4315bc634b699e2825990db573a1ca561324d1045918d5434caae081601e3d59b806c2aebda1cc92c9102b40ad6be25b627787784fc09172af43b8f6647a8819605f4373baf54cd37c854e4d9251d55f6bec947a61ac3dfe248a0e8ef72903b5cf4ec91526ada2b04a25795b921f9a30bbbe0e2346ffa9476fc7b8cf75c947a2726625d83deff03a96948d2c8fe9d8bc1c2558bdd6cc07e7444a8bb9e3c4389a67574628d5dea3e459043629b71664726a52555fbaffbe53e9735a0900f8a25c6a3d68e638819b15f9a2a7077378370e9182a1ef1dfd1be1206402527bc8508c4389a6f12d3b21199e287cfcd13ed02736773db13445dedf10524dcea0780eec1de97c502cd1a8c96c98731a242447624f53c638b13d0535552c15487ecf888945f4b983181aa21c3552953ab3b0c5c6fdaa7fc889ee510eb9f0ad22e55e36931b11a59ed0849e0449bbb2de9904ffc75129010d1bd867639d7d2c755130ab6c541218eec05e143a09f124198a03dffbad2f8682679f01d32ba66ba6534306c54f651f3970a34d1a0ec20b0f3fc82a5ca0476e4fcc861ae9e8ac9464397ae9e0b8b6a3b3f05ee17f3c7810c1836a4147a93ac61473bfbfa38e1006aa72cd9d8e79f0c0d36aa858e59a06a990e252c3e6bc62f6e1527573c67bd5518c910154bf87dfbaf0329873fe6452c101bfc9d9f7ddb1a5d2a9483cc9c0f5d69db63a96189d1d4553dc21837c001acfb5a1054ebaca6b897c984ac15b548a495d4aadd67224838b1f9502214ef663afdd64a949d865bb80e4724b58f2eabc4f29e36dddf50ac74417433bd01efef8715bba7190ead92a358a363422f5962ca58b1bfb996e3657ac449fd9b01b825a86acf79488758afaf2bf59d9d9136cad61d20100bd81f4d9d724644072ced881fb1fb30fe6b100a86ee9211ad669b4453c3a3a3c4a93fd28eb64019b164ec9dc9c31a2ef3a19b45e5f915ca3880bf79758a3a0dcd6093a2be1c7cbc56348cb2122554cb07acbc072db1567bcabcc8dbe09dd5e9ab66bb2240291def3f38ef906cfc726ef8187b15d5203eee8c919370ad1131da67ac3271eb340db67a26217a5c78cc413b99f3aee381d662ab17e1f24c024f8269c7986cce8546edbcb05d9d42597d992b05360a3be8a705a4d4281eea13ee8f1fa4d249fcc6534e31ce4a5c4f1bdf1972bbff6c9ee8a362b2911a1a5182637ccbc8f878cdd4661ad53765d15703c6116f000f5626ed919ff04e727a2a1deddac0005322804cf57cb087aa3503b11519add5eb8da6cf4d003df3461512f4e88c78522f984107c8f17e8349a5d14b2e0f1ed7121688929799d0d71c360db180635a476d2b7b9a14f1b97a80f6eebc0c805e262c0efbf18f4b51e3ddec0932a732fe93f27e708dedafacdb3b3e6e35e55437ea51a8628c81558be9a7c9059a3622d7d83ba25a293fd08cd502d75a1bbb115f4893caf8763d442152acd11836fe311ab03cbae40a4478e0ad4f6e00766430bc986da5859675ef5504321ab982831e2882c99f1ee5ac3b5b22f53e3e7bca408dc5c190d76982cfcc8360d0d8c6a932bf900b7f124e9a32c0284599c61243637a26de01e05ec173f3795e268fa6d9a73628a2704aecc3ea365f6928a1291f9fbafe01a276760401889c544f39d689b5068f9534e242e98a3d86119ab74114e6447a1e0eed8108811b748a6d373a201f283c6231faa5b5f9eabff9f761de5705307311557f2aac8ac9d56e92f2787d234a758a696ec3c069bd012f44ecde96585aad35aef1b1da2605b3e45e1047af4cfdd4a2718ac8429edef44e3c4b3353c3db39d09e42047f8f13acb8caf5c0b83b63477ecb4e9746862cebe7e9ef3c42fb2c1006917ca6a9e1af9dd01f8587afc5d57f74840126a47134591a91a6cc6c4168c85288acec1a91b8f4e82780d66c8d54c714c19ca6ffa7038758981b5435164861898a5aba8ab2e3414054b93eea4468918d23e2ee7a0b0860d5e17fd6d35b61a1faf3311a6ff3513a16dbf05440c45d1f049543783653c14c5a489a447260d5114649e5fb6cc752ca2b20b2d5edb6bd150701f3a9dbd5e07a285a77e2a17fedff77b37de870f9fe38710e79f8e02f4b06f86c2bd73b5d325f27412a9b6a235d3c822877e1eb8c6740282062464bb450dc509e3021732b7d7c5bbe5ad0c7749f45766ee8d628ebca65dd4afc3bb25abb81e5cc30a36a12cf6b36cde9ca752d53925b7f3b45b07a8c33487de9995ec5e2672209b12c9190f2861172d34df14d535e6651b618a1a705f4ffbed508c748de034ed42bf3d91966182224c7447262a567242b60a51812a36bfa138ebd5cf8a61da7ab5831fd080f4cbacb020922b17901206d96e01bf9f0574ee29200258617e031386d4c4e37bef426967da18ad7ea3a3ae579f5213a920ea2a2dc62d2bd5926b05cbf95b4f7c72749ad7ed94a83e3c04675ea15d1399a1e69cfb15d99ba883a350f4f0d2e389b3cd1adc7b3751dae635700f17fca191cc2e09f092cb84363745c855c7c57386a849d045718e18011f4b59df2f0d429be28981f65a74029e502bdf7c2fc680f58133daabf31d1dfe8ce66322f9f7af8e54e5d94e40937ca403ca754e7b5930d81da5037d9bc9c866b3d92c7a6b3c50e1cb1a77bc4f1c1c48add941c8490381c2a8ceead5edce5cc02fdf38d4220f1ef07a42cec68d385871df0e7a06735c257c6657132f17b9420c8be6ac9584ecbd37915bca2453920196072207cc07140b94f5fd3b5037d062fb552b7f2cd644a1546f08edb56e3064fb552b1653e8287ab51fb20ec7d3938a9e501a455db2075e484cd02744a42659e4200e82817ce0cc3ce2237aa4653ef9b1fdda69fac4e168b20fcfd1d1d17322f721fb89ff56a8f7858d48d7e301834023f37b26d7936324c535c580f9f16d98313ea91a89d5c8ac3d3993ca2c28ac5da5ad5cb5893a0a9ce97f393244e8287008d47ee12c3487a679958ddebff7ecf619b1910c0ee8ed28515a8a562487d861a37282c4f67f371f4322b66f084f0b1d24b2b4cf7c359cd482a2e0bdb1ed7707ce0e9b20209f1a04f9fc0021b1e4081217b22aa04c502eddf59697a3280bea4141bc1cfd2fe7e538504e572102bd0de4400c7017b222fb892f241901bdd34a2c68fb04cda0eb2ab01d740417244a1a0073b808ce0c81f6b78f63d1bd2cb0ba23a4d87e1c3b1e11330e2ba0ddaced844bbea7271d662704bd13d6f880cf0a54dec3997e1f5de339efc5e7ef410066c14717e48f00b2fd2b1cccdb0f55773dc3fe8f5aa1545e500ad8812ab2fdaa15efe06f511b56774e604526f179fb715269ddf5bc2cdb94b5c389e0c50824ee03db4d73bd8993a1264750a4482fdaf1fefcdf77d75a5014e8f5264d604d9be0cdf407310403db2d44932ff6d297838de0cc0ff4cea939134bac375962fbb9c9500ff11227bc845ffb55c7da6133df0bfd324c509926d0db422dd44465fbfaa1f77442f5903bc1da71c3a48bedc7a91233cacc7ce3a35fe8373541617d37cc1dff0d059a06be0d5b9ca679416ca890b5047a59ac97e101bd28942a26795e984499efc3d7755dd705e1c52c351ece48bfd0be8141af431564f2dfea7a904d01dd9e406f0319d5cb947650031d6924b63f2b25815efa7290f2211b1979bd7c74f4eae52d9772974babd8db40b619007364854570c6a404bdfee33f0ee4415c544524f9e173e2fb0b2ff4471ed06e1f1f9f143c0c9a466503274c6b08d1abd98c7db658cda2382e94bf4b810f90cf0ff561a07ea1ffb1404fb5852428c7042a63047ad9a79d64454eef4ecbcf4b918b95c74817caf3f85d0b3d2d09b2fd456274875aacd7cb3e8f2b62842752815ed6e2e5ce9af7995a171b649cf356b7836cffe604aa3150ff3410031dd14840bb835a6812b1c4f66b27f83f7c8eb3709c7cc8f043efb40dc316433df3a2502254952fea8c7ae65918b638ea9967af4d375c821fd19d5109db8f4364fb7d54415948bf8d01fe71485b5214ca6ac0cbe9b4259b11e8fb37c335c5ffdeea50c4c22cab218e3ce1ffe17364f417fa4b3fd014fc6771d4553bcc16ac235e06e86b09a1c4b684aab0f4877de017a04b8ee52f6a9a0c260623a4f497a679569a1cb1cfc6c0248ca6795698788901892a9a261503125d7c54c7911551d2dfd596f9465234290eb9e98ed3f4717afb7e1caca97dc6a56963b2875f8a711b13838699e8e9f65898cfa026833bfddb6020840fe459585332bf035fcab8c77097da983f7132dcf31317e3df167b6c8c41a1e460e9e340182ac542d7782a43a1cf6524a13b22276d6e9ea5af695886b1233066c6321309c31e23c166228be19af4faf724fcc8e14067fb362e8aa88da63184ecdc90190a3333f638dd750de74052c1eb61c47cfb1dffb93de97b2cb3d46c6bd83327391c9861d3fd495d8cd0fd278c30bed7989f5d1cab830efde7c78ed2b2a2c3f983948a2aa25a6891fae3d8418ea1748dd633fef15b4af638ddeda0affad5e5736e3f703a3233cbe7975f8af1bb4b3176c72c931dabc3c162c5eb23ab696275d2cbcf481cabc3d1c97fd61f0ed60524d64922aaf1f8e2ab71057a57b7c3d13492fbd11d3f7bb14492c47a565957d0cbb2114298c3d3e2520913f31715c3b9807bb09532c9e334e46ec0000e449a866d7c0ae8d8e0a36be8c76771ac615a390866d788bd7e4e9e189e859af61bc735044376fe659f0bbf647ad2c31e4b32695fd2fee26e67d9aa16e78cf6396b6a72833fe1f603f3487a12370011507f9e1cb3c64e20b18c65c39e9947c933237d06996b2ac8b3fc409ee59aba7ee7afc7786c2471dbbf2d468e8866b79edccdc1668f03b5884241adc6384540e54792855afcc9b9bf5fc0c6273df7ecf4a9713656f7cb0410420899e85108af961d9bbb6195dc59b22bee3e85eed1dcac837540f820ac7fe3bdf75a0455dcddddbbb322dbac507eef6191cda669d83cda3df2cbd10c6b77f7912cc2c7b97577f8de7baf9344c7b81b06967de84010bdeb5116d9468e9f0e089f1bdab6469a7bb48e1b5f7208a0f59df8367bdc8dd3448837d893f0a12834fa0d7843c6e8f344f6604df49c8c357e33f02307b301db0f63fb5f0eece9e61410c54775343d5afaf38cc4ace4c139d29b91df037549f758b9f949cdbe55fba4608d74e76858949d21632f4ebe19f80fee603b9e6c0c1be3ce519b619a73269b69259b699a739bcd36cd39cd6625cc6630da45b2d366319a739795d1a1cd6668282d55a346c96b7c39e0c30fd8e6f7020481ed8f355210426f41d69f9918c040d8c2cbc2cffcb9bb7bf32c053d2c58b060c182050b962b57ae5cb972e5ca952b57ae5cb972c58a152b56ac58790f0b162c58b04ce13d2c56ac58b162c58a152bef09e1439e0416772c58b060c182050b7dffa290c17fabfb8ae0d1e3f1be7830889ed2e5092c84ff155728c7ba03b35b1a57a80ff9101278048f58043c3a198272a8113f6294d2fd69fede7bef69cfdf7befbd1791903db797edd7c25e7d4d22a02af649a975f5ea1e638c1e6574f7e841f0a24108d3dd10763b1872c2b894eed121840e55e0ec1c07d8fa334cb37b7bb787e1b1fb61628c578c1cd923757f24187e3142edc1d73fefbaae4b9baa192dacd65d37b7a6d149213333bf516bdd9b7c87793d2c25373bc312bc27b8d8cc94694f64b13f1c0687ee4e2794eaa58c7172befd8811a541a1e549f981c2cb27e189efe3e3c31321333f861042c8eda9212c849f1f1a11b98101a24204919b2d78dadb0a691f1f1f1f9ee6227bab223e3e3e3e58ad083a9b86888d851305a11512b3c40a0b6cec571eb6b0f216fdcc833bf41feecc27c8f865bcfd809ad74d869efe21f3cf649123575fdd1c5f728fc4acb342a9eea796d0f957ab3192838f8f8f4fce0f9fc3403002f8fd3523d5c6be3d4be8ebaedd23113e96b3afb54496a12674d3329d7a7d96b5c61bd7e7429c91b8ced410146ea91ad09deba3bd6fb35fc1c7c1da61223d89547586b8bdea497542a99a463e8f421e534250f96cefc34fd58032b4699a9210cd02c13ab0ba4415f6be85374b50b1f057382c7cdedef6b61fd14677cd310ef458f9977fd6ba823d1b93a0602b69902feb85f07f70bd3d52be8e14d7691a7f1d0804272b13982fc17ca65d9f9924174bc3be961351c895c0248d846dacaf3b1d20885ded638df3e15f3146d36f5cfa382b0fb4db5579da6e7fb17ac177bbd59d59b787d75fad5ed1ca9df9dbac3bf0a3dd6add6888165606828f4418b2292907f3120766509b1465678cfcc9c9a75c4cdd62e405c3d1dfb8ecb3c951ce7b667e36b5c7698d7b4eb9494f284e65a43ee8d03a1532f77e776ee7ca87df834bf7cf1a5ea5d893a4fd75912ed245ba48a4d7b81dec5d17e9358d94658fd31b86fd45b7d7fe9a7e23354cb63dcc46aa3c6fbb5bd30cfb2f715a46babe1fbbea6d534b2038416407605fcb09244efc580ec2be56135dacbf97b5d46304c39e8164929d4bfaf83d58c95d12bb952efd79da46d2750b335f63205887343d562a61a512f69c84dbd9fe6114c3e49bb037491d20e56e0f76877f7bd3df98a7f3ef29be7f8d1276e25ecfd02a612a4f5bd284a9314af572a0e7c6549e7ea63a25f6fe1bc74068bcd1a059ac5ed2aca7ee3893a7ee6a0fdc43fb42a289a3eef83cdddd4451134d9a48620f605f8b092dddfd5bff167f7b06da7a8bbdc5cfb2fbea7529b7f79bdc36b9bddfe4f67e939793ec38b4524a281f06411d60f677fbf72f6edb766575e3913b0da52cd58df426aef2409bd5db8391a63c695b5f4d98d0820927363365d9dbfc71fd3c6d3926945801d8d7ca81172d1601f6b5723064b397f9a5733d035d0f5ac8ede8605ffb33b4d77b7fb9f2b4bc34f9aefcbe590d7d3d736daf959ef2407b7d7c53dd2a37801f2a2b0fb4f26f1cc00fcd2a4f5b1981c8ba239b5479dac2dee19742f865d568d0acd7cb0f2b03c13aba9eba2b755cc2c45e2552d20c42a269f11fecdabc6d406a3c069f85dff006d8651b57e3513264626c3d46e03bf68e04831bb763fa1e2c3f1573b76aebcb2d3cd7dbd6f42dd4db5cdb74b9325677ae373d77576a7b8c3084fc38f0cae7e91dfe1e6cc34bfe067fbbaec45e1e0cc3eaf524b663c088c188c188c1187f7b4b8cbf7d04238c30b68cbf54c654dd05dc3565e3e563cf823537dc781aff33b82f9d626610c2df1fe448b8473fdc1e076e2db1afc1993620a6aff12513f637b4f077dad4bfd473f5bfdea877f60d0c6eb05ed6b08fcf828dd5fe1b2dd4d23f5833e31face18934aa58ae6d7018c0a1bbf834b4f9da80d4f8ed25d7afe2b4674e665f837b36c5c5a751ef44d53b67d44b65d44b4bac8bb2b1be8da9b7b330f576945e73a3cccc3bf3b1a7ee826bfb57e5e571ed1bd318c3b619aed7be32d3df385a53a497f5e3df70512efca5ab0740e5a0c75ebae52f5d3546bbbd7c6daba6f8f1dd05d89b5ebbb02fd5db4741db5fd4a576abf23dc85d700306ba8b7f495513bcd8b7af95441325b458967d2d25a0d856122d1c081959cdc29f2fa5950321fb827d2d285b6cf62c405f0b4a14dbd9d78212645b50aad8ccf2cb18ff623ed334989f756e9fd5cba56d03527a7f3ed234d9f3fc57fa2c9bf57a36dfe7ebccf76c07fb1eec7c3d27cf78c66920b8873f67d3f4d33467f57ee6e975a71ffbf9f72fd77bf518e9c1f26733bb2198cf49ecbd9e87db99df8365ee4221d06acce96441f6f234b49771da371a6edbf9d9054b7fb1f7af978ffc35025aaa7c4f32efd59ff972e5696b9a01ab332a4fd7d48ccf84cce7e7671d06e21efc59e58bfaabc4993620f0b9eef0635f72feeb7d7a85a5126f1b90f93b1b0d9a9df57220db7e68527846d350a0d2677f71fc3238ed6538983f6d25aea4d35d4c0c2e857df6cf9a3e26864d1508ee717dcda0d5fef2c0cb03b5baf357fb8cb37fdb0cf3d9ab36430c29a6a6e65f3cd0f25f1ea899b4a7309c7747eb9baadc80f0ef94eaf671a341b35c2f077aecfcedb5d7d1b4f7ed873669e8ec2ebb640c8e7922f3476941047c848184d8d2dac19716951ef81498738a0bbdefcb8a7b823d3386b1900e05b4f8922b4454f1f124458cc49f58048330b4bb6ce609d48f7417dd2709fa110ff2c0aba126c73857e21ec581403b4b77f1db28c90dfc664cff483f30474cf53df06a8a3c1e4a9aa649d3f44f1fd80436fe7b9783b011fbcb4beab5ed2084b358edba333df7197313ab303049c644329948da56c48817347bf9b78ddc54df0533ae1fc3d2111bebd7ca64f51cc7d68d8c8c6e1bb5112b9b9327a98522526e3ffe33267d12e84d6047bfa50ca8ff8c5afc1a1d124ecf9f5a13fa7cfbf1afc33db08f2f81c783f4f127f068eac7d5df590380bf9447cc9b991ff39583af7b3ae63bf75e0e0070f357dc7cd224658f9132794d1700e002ac159bd5492492d73b9f7f879f3e4e63dccf76d243278ef39fee805c0000d755f44cfcec0a22213972e4fa111bdf93344dfd9ad5c7f7218fe244bec4a1b8146fe24e6cac9ec493b8121702d236c583dbf93a98ecac33361a4c3f2b0f6cf9698c2345608dfb40eeefaca169b8ca909176ea633e5538ae72fe133fa505bd5ef5368a340dc9c6c7eb230d24ac24c53c6c4dbfe340ddc59feee2e368e31b61ec6bfc15d7443d1365feb611071d61201f1cd80277d9b680d522ddc5f937b21a9ffa22469ae6dd981bcacaf8f7647cfcc9b95077f17d63962d026b74e04cfc49aa37e2abfebe2f36d807d4cf97db10d417691a1a1fdf08fba07efedb66bdd9c37c46bb8bafe24eddc54f7135381a286e06c764f532b82ff732121bffc4a16c0cb7c305c3518e87ada9cad05d7c9d58aa7792b46a64e307c9e244cd1a6327b9ce8b6e7400beb4a8404901a14505d14f9cd2a2224a6b8a93d614252da2a11691921497860f1241a2088b580b248244cc85c8aa9864713e52c9806addf50f3ea2f66df1b73427c1d2401a427a3f72d8c5892cc50525c521b4888efcfcc2356849b1c576d132a8c019f6b5a478e22e448c08296770c5d8d792e204b3740592530183d91285512b8a1e584db3af15c514cea40667925c40ad50aa57fd0a27888fee7c10041314558a1044e18b143889c1cfd050163168c2959f2a6e2004143e40824251a5403d4a8582353850361394c8c2022438c1c9175d8ea4502288265daa60832fec25007bb57c00642f17ec15054e10a6a2f5a0c85e971050a81dac57354e10d47732ec6b45e1c45e949d42193fb3669cac806261ec6b554194da8164d974c73bba63a2c4ea8eafeb92f4fa560f98d8eb6a596185bdaeebba64ca08191d768a0736fc46d027650ad605fb5a52b4b0121dc87b45580218562001e5b13181d086767e42c5132c4fb278e2c59110d94f67eb39aa6780e66cbb60fb6dbc175053f80e91dd08bf8effa669701bdd6bdf1e7868c87fd334b25ef5f54c6cd5cd45d570e343061b6c6c90f0e2cdbc22bc9967635dc243028c37f3ec8b5b64f0676f3f6030a8c1edc789b583c2681066f6a1b8878dbfd2be8f525e0ef837a268bc1ed9bf4fa96cdebf1b01eca039ec03c05756db7835f486634e33de878f1b1f37383ee01719f48b8c61a13bed8c18311ea76fbae6f988f190c298ea756b7a99b7b9f1d7a6351a345e0dfba0de3f3e1e2dbcbfe41e36defdbfc67bbd446e3a8804dd6123e53d0d6d7ecfbf51ef0d7bd947c8cb01df061703eb80afe264e09eb36183a16d8d4a8197033e8a0bf272c04f5da1a8f77f3c9a896e66c8e8e2fefe685072ae1ed2a8da3f58c37d3ff74c4f575c102754bc99675de08238c1f2669ead5c102759bc9967358ee38238f1e2cd3cdbdc4571f7054188a7ed9c362926f4d9798a115359ca11bc99778237f3ec13163c91014be98ea83b58394a0a0b9d9e03f9c8a83b16cb491196f0669e05c30a382cdbacb5d6de2b2d71a921be637aaf279bc5c4f9d341fe1068a53471cf6a7cc4466c746f6c0d3a37dcdcdc2061052723707202272c706e4e0dc66b726c6d89f3d7f5bf8e7bb6eb6b0b7d84a038acfff53278338f8889040de2a47b34d37158a7f06995281c05d6bc1c7b33ee596814657e141d98857f368233ece587d3d9d4311fe3e0cb319ff466e6c37f818c9d3fa104dce76be51f1f37ddc1bf9105ca599a862fcb280623b25c160cf2848837f3907833cfcabac49b79419e40f1669ebd9ca47d6c745d92fd708f5785cb40d8c726783cfa0bdbd732da62ef73148842f9f4f10efa249d907b4a0bbd9af526c1e3d14960f60de5635c8f85d3a5c7da2923a87ca0d7c6067617d95ba5e093fe67049c859c27fcf3dfe3d1969482ef3d0337c884c4cdc9fd3073606c8c61e36fef85f8292ae8f517955d1c0f3bf04ccb1ef9751104969faf19380b57dde17a9e6999af8d06b7bd491eda5e281ac9f169f3dd4bf6568f47f3db81ca410160c3da938ca4217d3fd748fd7438d7d3cb21f3ba7be3146fd808b7c7e9e67898d6bf833535c099f853ab77c61ae09c57c642ce36d7137d1dfcc9696fbeee75af8344ac53cd76571d8c58ffb7d5221bbaaba1bb2804855ff4ba481b2069e940a8abc123a0af850589bd5a07524f6c6c524ee6b479c0162b8441851c1cf1450ca29430743044145e00c10a51b090f3fe65c0a15f7e0485d2885bbfae182b8fdbb7c9ca43432e9ef4bffeb8d3dfffac777bf7c3ea49e29c97bbaa5445f418af2b5e33cae8fe504b947c5d9c039a80c678c1b8bb9452ba747fd121840efd0b2dcac265f69a86fdb645ea3d1a4b2064cfa5cea579969d9a869df49ae6fe9926a5eca2e80db786301420352623fbc5457708358fd16177c330d81906652fc6a8d3ddfcebd44de3163bec7b2c151534ba9cae095d9d5551412f9d539b4d5454d03b55535453bc6a8a0b321b42d810a2a351c511a0745cc0c6011416c0212339c4424546c821a31546f4bea0a20418510fb688432cc5141dc4a176550e9e5176c513492cbf11d6e5c62971a889888338e5b211858f432cc51429e210d64568bafbf329b4bf4027ee108975f7ef30ba0612f5977a894029902876ac2ff6d22f32acac10e80af0081c6a1c4059c18443b28b6c6243894c10464405bbc2094c07d8152c95178ce87d41c50f18914c90c7e8f19aee369d143631b60a0a270f03baff73e527b42e72680a39050a394565630839c442454de490d6c54745e417b5a326c883365182c01afa59954982f06ddb8882f2bdb1b1f1099a441642d50ea87b6bedd55dc504f5245818abea4acf30bb63af1ea5ac44d83f254403cfc87a77100bc772485d3373baa8020846d60b1f2cd4e88988bfd6f222082b2dfc8714caaa3a1d415513d69c5a5d8461210972504d6465a1ca08faec3d39777a23e8ab615fab0b2258695fab8b2cf69e2ca99d087e5a1366ee6786b0a686380f4fa16ab25245798296550b08559cc8deef9f5102bd7ee45ba86a09bd7fb404aa2ac221f36342318e646fa5c4baf5990566b1fd503b350d95eea6f5aa620289b2d808e1c3f02fb2549265a61417ee66950d5436500d51c5c8c852c166a6b7520d41999999354f4d81c2ef77d58eea570cd96be3831ba15e55831f60c1022b508ab802143d4cf83829a205250062d2b2620a5b0f7e5cf8c11693155e6c9b7d2d2eb88091457b352b8a322ba890b870828b2655b4b848b2dda48e503ec8544df314c24cd360d5e1b25946e25259a60581eef09348cf05d01646566a9af640aed7bec742987197b10cab902349ad36370a72ab8a83d534f077bcbb177f90bd55962a028d97641581c6a979f187711aa5f253424e317ffa1ebb4515bbc3bed60f8a669089f132df63b38c3512c635cf392de460fe6d7c918a5892f61897fd3c7d0c2e262b71f14d5ca95b350d7dbf9e524ba90d467866409f7d5b70d8de297083897b16fe9c33aba921d7cb0772b911cbb454e2604a6d618a4cdea8a9042b6972279a0a02bd2c164e0dfefaf9cbef1972d5a7ba9ebf2745043a7fa5ba56ec836191bd08f46fad1f0cd9edb3f76867a040fb79eee09f690f7174cdcf93cdedc631313dc9f4441008b5b35ebe9a14206fb7f7ad3a94f2e6c77fe76615465abac030809008398942a528cb16f7d2346de1930d4af17aeb0c73ee00e45952dbde7e94ea7b3a6ef6fddbfb36240436db86b4cd74e8c1b721fea47adf661bdb21304a77a7aad11848699a6c88571825bb88f8fd40c8f6f726845afed93a309402a7bc1cd0ff2f48053a815e08e5a3cf92ecf5134c989afb8988de0bf2e371c9eb4a0d5dffd75fdaaa3ba154524a499b6675b50f9c4aa28bee8ff1eb9b42aff6eeac512dec50d0a6b97e7a8fd10a4b297fa4a2a01765a5ea579d464f8f1bc0cf75315f2cb998d76482fd25ff5e3525a4fffabede3fbed39c734e8d3952f7cf291d637c0a85deffa68953f6169990458ba126b410cac28bcdfa35c3d212f45d6922d423cb016dc11f130fb801ad804397825e6a639cf4348d4cc701bdd0f6e3c14e541d50fec7caa81645277b5b4a0af5b9802c6201c5aeec6b5d41c566ac71e4d8d618c04fdc892fa5046d22441989eca5b0d059543e592879a92cd03bedcf23ebff5a586cc9f109b7575f9b1d04ca1eea3b677966b3df3e631837c8f505d1805e0e2aea1f48b9e8c4443dd3cf2c63c65f8ae24771336470efcdcced073351d36854b2672bf34d03236b504fe365fc8c9f4f83e32d908f6a66d4d733d8779d321c0cccc7701db76ddbb6bf7c64e3c7f853a97482f918300f6360602aedae8f60cd0d6fa6bf69bd134365da63db0f0d7ee90720029a7dacf0f98837cea8bd8ce4d4502e4d0981e2f48d0c11c2222ed6eb4541786acd8825b685b0d87245d0153eaf087dad2cb4d87eada22ef64ead4796d24267b2d70e98afbb02644056a92f2b084396fa42fba78c41aec935d84e669c0999253f972a6c519654167a1f15ef87a79ba6995a8643b344db73ddc3483da7eef8150551857d5b9200e3c7094514f1c41204584289087650842f8e04fda46ff6a3fa9bc55a322cf67d0d51b6e822c50ba814f1454ea4c110472862a20589949c140fe89b3f64cc849b8832c50e04648fa67b5da42794eaed7b386c58753a25ee5714d838d5dfa07128150632ee84c2c1c23f11b150a33958f831ba8c1e7d634741aa33ee57bd61c0f2935202932c33bd6fe64e2581762a095d8a0bbd3b3cc585c61e2c5727b2b7ba1ce49fa594f0e37a1c767df37b3d70601dfca924505aaf2b5e2c6dee2b08ac9938708f15eb6053bda84dab5db770a615f7f0c12b865dba3d585325f272e4d01d7fe783b469b5eb9da46fab2a8236dbd73458bddae3c2e51259d9f8da101b6175a62ca7685d016a5931e29242429696952aad2a61b4aa0cb5aa0805b5aaf808b55a495248685989d2b272a4d5ea4222699ad6ddf6b65477b2e7d7ea3f12e5b13f9c886679602132961fd2680d8c17639431c618ecc088075a96944a313a9abf57a7a4e89581e8aea7184ef52719ecf5260e9636eebe2c9ad4d14911c163fce581a418dceb99cc76e57f5974246dc902eee61411fed16364e6d411f88232b6066eec0e7f1070a66c40af0ea73dc339ec8fc447660f56feb5c39c7f31c11e073af48149ced0626a07d7558022b44870050b22aef892002a80b8b0020a3f504151ac125b3d4c8ed4e08b2eb090c40c5c9093012a745142480a41f822899ccba25205152b6cf73fa216152a4f04a1cfa8944e35653dea1491040040008314000028100a06c442b148241e942545fa14000d8ca448724c96c7a3208651108410818c10020001003020033233228e02189a53f8c2ac593db9b1df8584890e7dadf10bc0c48a43dcf91e9f091ece8590e19bf3617be8cccba6527550910b5064248061ed999fe183c18d0e956a838d74bbded34de032ef0374209cb1b7749de674f703d6297c40ecc6cb0a37f1c88d4e815893d48db9a4817ea6582d975bab7001f968009cb115bde45679ea2ea637326d34eb6ca5127f81fb0bd859608e2b10e193208646dc3274a60f1b1279e5ae5b3fb80f0cbdb28abb42e20fbc17c3c7e082468fec89ae1c2293106e2821d6c6fe84a16a0b5da518d25585934bc69e3bb0da2fe6d0b436c4ee870b6263e620ddd042ecca4a452d377655dff990bc2aedc3c9c6124c8ccc19e7732bf0ba7af63682415f43e147701eb587b1d27da7e5aae565efeaed7b647fe700d242b771bdef87525aca94afc32c3d522a2e091aa7bf192df65b55d9e350a3cb75231e86655724abf8b587ccc9988c5931cc344e4c63746955bf30f98e15480521d958cd4a441171f77554a244301282f8aec3f21682e88ff13b73c501268391b4387d66d9b899ea2fe225aa83bec6b0ffadcdf081ca585ec49c40e2f3076928f198e63e0bb023dee306cec7c2a80074a714ed77d61cfc88a19e12c42077ac5af8fba258f713b4033c53eb99c4b4900dacd21ceed1e3abe29b43263a780cb23c9459c61fbf58a818927b7f1bf13bfe623f7b1d0e8f6863f80944d939fa452b6b8df5dd5cc33215bc10194909ab92974b08a8cb33cf535db635ebe0066b7d500bab3067eb7227e80d4fe558477f51a98211605785439e3d8f4f6453884567b15e44d65d00930831754e28ac272ab493c0eb80e045ea7d4e028c44a84323c9415a2e70193ad25b4b0a26f1ca0aa39a4f5375e4b7cdd0c63dfae11e04a84c2a1b99e8580e3dfdf6a65fbbbcafac183abeffb891bc931ce7c966d58cf1ae4904887d64629b87184ff1278bf8853f199bc9fc7c45f9acaec85dc18feea7d729eb69e01dd5ad34eebe09db3f5d029da4a5d8a5e4d44f850de89ec9ef51b2a4e021d8bab6c33e5d2e214224246d4b6f63a27d15292a6d2a4aadaa5c35c946668ff380b924aaf0336bca4a5ca241448f1925ed40623635031e8c82b98b2c817a58c48279042a1c09a7764481e9905520a6d4ab3a947614c06c7c6b577b5d936f5e09ca291a61cc9d66314ea389a46b754ba63814460da5c40294e500392274ed94df2c7e366cb81bc901ccce82ede76e50772de14655910f35224dba41372d047ea373b70fc17a83ec54d9d2c691304b3ae05206b7ecde8d8f2d4679c97ee27bb1ae247c7d78eddc34da3e680a66a03df338d51ea6d2c4d90f31f78193ec1c5eae308024171b47825208cc1255f9971db200a68be4c784d62dace502141d4d636a6a9d2c545cdcc67d82e335809a48c41a32f75e639cb285ad91d83d67af04e951dec940e39438b35c539e896232533fa03a24f04bcb5367f5f5e52fd074172a1eca784c8693726daa8b0d62f239ea51430a7ab6c732bd78229d57b781314b8bd38b1a9be3711533db3dbc8282a577189836824c5c6127bd9ac16b903c9320c0b7072a51b901dc1d679316c4f5d5b113f8938ece1dbd0e43485cb748fee5c5672ea6dc62a1ac193769efd5bdc58eef760651f1db2a6312154a20e325bda069dca8be5674e1945ca7244da46a40ec33b7783b9a1003a325e55c68c485a1cf6e7a0afba112e8eec74d46def717d81973d144edb30633c841703943a001319858fbb01158a9af89702fa0b0a8d5d06868738a438dddd0cbc0edcbd9fea885cc6bb75c790e5a907f4524643772d5a11ea99764e08636727e55260cead63be8c96e6291c90d93661255ee17a5f2dea0c5194b1672571cc08eb7aaf4e5033304ca6e0afd70522b2b6407d070ca8d09ec1fe3f6d6f69c19c52e4b0779ce85795bebaa84e45300172ddcf226d6ca1878ae19f3c660624486c9ea8b3918408c69663b17ddca18860422b54c65ae8ad4e4520c9cf8913d6dccfa10365458b93a4827873f2389026fc4c82fb42c51352d6317182fc373d1d48bcf052cc69608cba8c37d42760380654f3e86758c93924febb72feb47553f9f4b97f544e69cc0f95be3c2bd25b946f0328bdb8229f1f1a6fbfbeeef777c5f87775ddeeef076f7f7bbbfdbe99dae373a7fa3f3fbce6fa3aefc3eca470059ce8018c15c86f79ddfeefe6ea7773aded0fd9deeefbbbfdff17d1dde7579bbc3dbdddfeffe6e7764ef4f2e77505f8cbb865b01fc6719a9945a304569a2acc03844ba5e24ca85e323dce5a2a8281a03e16e16475df4f808e1ed6241949bef1a7bfc68bb283db0c069d89fcd8362de64240270ec23ecc731ff556016f7f310f46f67300cf132390bbd9db3e97c93af22d623500dceee937b8b490005d47e3937c2c3551fdb69fdc9f3028c5240a461ee8f2d2ca03b2db9e63afe1a696564ebf03ce387f60e105ee4a76567cc866c049996779df7f76cc3ccd1fe4191c62c2d7de801a51ef5e79cf9c894c5ea6d9393b07aca944b0f55c1e6a5b46fe706443341664ab3716dd279122367115f3a11746c4547891281286932a547b8378218373b59271a5954431e2976e73352f3cdeabaf6f6b9227d78a3d078261cc5f27872d29f52a27a9332b205e6c84aabcd55adf4b7acdcb38befae6b951e9d4e647fd6ad84c85e021ca99d7614bb20841963a3b21f2b7c6703149f0632fd78497f05ba91d7861e2fc699caa1044acbe090cad3ae0b0650adc469965470959bf2a5f4361b4897575a360de10cf7cce83ebb06e8579a7af2a135f67a929065e8c4d79896fd09d620d1f0c1c15f0245f11a7db1ac6c4d5046d54a3578310064616a9c678e7b6c92e553bd3fc6989e7a022343640e7b86c71170aac10047dce65558e26b6e158bb303a3b5240fcbc26d9a4c7306218956434f81dbd7d89e4151d3a60cfe40f3eb71e698671ada6b383033be1a5b61df7840ade6b0dc8da2ced8cff4bea0718ea93a9cb118e8c5a84635e18c843b53d14db17a4367f661f6600a7f44e03d41ab32cdff34239c7c6d9a811eacebc250ae16e8e25ed86c87095ed5c2081af34cbf1e72b047ef8105583e994d6214131b2abe3c07cac07d106f422f06443ab0e1a49622c537fcea79dcccfca64d4d9a446a3cb3348bb600facd544615b84dcba960215114c13e8108bde142dfcb1c8654b97e192a440bdf06b938ff39c84861c32d8c911ef0466050e5684d6a587330c998aac394e7b2d6ed866f5a3d7a8853bf88e8144df957f9f067a6ac65d6d22e94665b56b0e06cf3ec90c60d1989297667b2d2ff9aade8db3f4f5d3d479a74aec8f767486040f07d1cb0a74f70ebfbcd2ac68be79d7962a36fc6c4c0a54134b1d8d6a00692323dad575cc7b0d7e92ebe78921f9f8f49027544f3490d6bf30590c975700ee55bdd06a1810b0cea7d23d8381b731ef909dc34cfd1598f544f523b15b72438a4e67a9590cba86d97996a27ae2c6c4f90d35d66efbd08a26b001022177b5524967c32643ebb914c9dbc9bc074d9262f103259128210e73a73694827aa6182721fdd4f530c601438afbd9f27e9bb40112e887c24c4b8664f984c75bb7cadc8107c783f60699d8492d77940bc581e10274947e49b970b817cc6e68e8d9fc314fbe29bf54711ed677bf9565adfb221ba986f387c072ca0d6929e2a758c81deb547e9e8c1bb92df3fbafc87c32d873b632ea7a2583776dcf1a1e30e074500d9bb717dc6c064502c24f9f2475c1e74bdc0f32e6ce6bf0ff1612ec063798624c7fac36c93f4ebfcf76ba36e370652b9653e8c7b9ab780a5a761a031f0d550166d7c7b1eba0b14a2c3ac6907d82b2e548973e36ac955e50bd57875037e99262e9b7a1b7fa4c6597e06f020c42faba9b4ed552cdbc6dac19f9c5d43a774714c9f10f8b6af89e9b00c90f43861aa4d44f6e4a871f3ac0ead09f3f2c7e68329c85107250e6f30836423b5fdff67a2e022ba80e76acc35b1619c0bda9df7efd555bd212d6f13795b2d2f35e15a640578c68a9ca4729060609ee7cc232ebe8739255358ec4962029044a3ad92fa15e1d827f422d76168e4eff77af9a33440f891ce824c1f54a7f5ab7d52d7fd77492319ef5c6345310f5b32b712df21915687cda6c01dfddb21d81026ab2fa8ae0af073d771b64486565e98cc952aee4f3d466480f8ad504bf9d31a8d546586390a5f6dd9c1dac065f472c391bb292c165445b96afff34673f17ec5173e1006a59fd388b653d8ec67660bf2344f19a271e547f042c318284f03035ab72a99e6de1e8268e4b2c8a60f95ea5bc7960abc0d29f9c2d71b8d0445d7c96cb7051e9e388c690a438ab00eeebb2946881e7bb903caee7bc1ac68ede97489260fd82d92c99431d3a754c7a2602b45765a7e10cd1838b39d8d46a3d09fd7f26a81398851be9b75cb8b5c596bebc508e9c700b6d0117d098f18ffc3c7addc238f8212e0bc23511f972495def6e6b133c01511bb16b63798d326abfd3b21dae4ef25a087f294f9f2865034dfa8f10862d77eab2b75d6aa10e3db86fcc8f6a37c99d7b1bbee4dae3dd79f36b1bc7100050be4a4538c8618b1bdcc0891015c48631079afe58fd9bd60e7c2325ca48fa92f0fdc81d2419576ba6f5f4f96a3db5141cb18312be1555ab8dbcc0525fc04acbfa465073c897c5cc39c436f0deead219c9b26178aa814277f2ff4be011a5ea156a1418dfed5431587c4f1122e94b9fe7a129fdde2a185c54fb2abd3764e451256e23ff31d1755dddf0f01c1ee1b66bd7cc3b7209a3a61bba07e78ed6271c6d944d4cbdd887ae9ecf784cdf49122b832023ba785ec726b80170deed650e6c085c265b0826a274b006c3f2d06713559e114a571999d1efc05b5a140eb0db8ed2ffec34147154571a5084a815886aa709598bb5200aebaae55e213d61594e66fe1327274d3fdc6e09026c3d82b58b4f99685d10be7b89d6ceb00d9d28239548433c144e46653ad99a3dee5f181090d4066417fe1ce780458d85b62f2966220ec49ce013a7dadc372585d915ded03a6e8e42e24383f10677bdd3f736fb28d00a13e46c1d3f59a5a7092568e14bcbb66fe05ad0168470db0eba24d70d8774ea59e2d86651e8009c8fc32b5cedecf46a62254ba542c9d95498d4eeec5eb77268636e9df941eeba8c6c44d0e24c5bd69fa41510e7fb3461b50488a9fb1dc29b1852945331c7bbc20308328900a4ef926d25b95b34f8d659713dbcee53f82baa2364bd8def700b3e374b90a9268c1cab8485a2b048bbe7539007fb649b9f29f0a0e3b5d5a9e92aa72e77a27c34fceaa130bbc0d933441b44a21f776179737605ce13e27cca57138d4787c683530d1035679b5cd9a98e742d1379fe937b7f00a5f876297af00424950e1faf6751c33518a5c0c97e3efe938d108eeea4fe88f2a6a710c44a3eb6892231b1b410ed3eee38f7df284560137a0558556ff4111405f2100992106cfea644a12e0d243d37627e1d85d1cb30270c8c7ff3fd11726732973f2488af5b4cc61a7f6a4b31fe3107dd93ac0f644d5492fb4d27d381be12ee6b4f71b45ab3701a0bcc8b6d13fc8c295da734bc71ddedeab3210dc2a08c96480e7d54bcfd75c981e25cc01e2560db6a9e4d396bdba094925a6f72f3957d4b30c6e2a11f3641beb58936e95f670e3d223de52aaa3dc38a7743847a01116acc661eaac2607a3be94aa8beba7ed1269747ed5375b820c74285f88f3aa4404f29486e0e592859b85518a6def0c59da3725dc65403df604f4e1c07859de95503e032cb39ef9254715fd2fd6b4c63a287c24491402d8a55dda05f8f3bef64aea8f35e9016a3aadd0f03a967b0508533dbea7acc5dc12179009566de0a2581390734c66031b62f724925197a070578f0eac4cf998c710dc9721acc1e2bee96478d757f2e5afa03cf9d54c507b994a6237d3dcaee3c92298ffe594ee2f00819c335a8a49708e5dea6fe6dd43c164219f03e3e47f70eaf5211651427a8003c80ae14899c8dbdf7487ac2787604813a3c78167f60348d2305463f7b594594ea8eb5c142530058837ea17613a725256031fffafc9e87bad6668e1fcf4623d167a67929c15d1f8df27bfbe5527b622362f08b08f392b36d1684a027112ecd76154f382b569dfe7b99ac5fbc2e2c366d0069bf7a84bb3c39e650919a1ba3080f3d9346dff27948808c97505294fbc4e117792907ce38cae92efba4ae8910234c893b7484be83ad34f111736e82a2517834bc8ddf87694b6eb28d49e208430c9f3669b0868d5469a05e5d3d287d35ef0656ebe03c26be46ce04528a201247786cb0927a8817b514f3335fb089ed01c0106dd19a1c0c227d6c62c660fa777a0a4ac574de82f66fb04185d326529801908870fda1f78e33bcb4827bd54c8145799633eee7b8c4027996465a47d5d383e797551add939b7627b9e84edb4601d5b29d580e97bb18a7b655bfa205914084c22139b1317c7e43f2c6b9443423d92b65b23eef6653dd01974a4f9545bce89cccbe7c9727638af48499c7db34000a90a0bb591ebc69ef03796b1284d83f3362e9ca7c2b579b9b1cd13b2355d8c7ceccbce5b674753ca2e65e6a0e83a6e5978a5d9efd5a12f4d37fb12312e2176dc607e853418cd7be85a98c857c62013e3e1ad2175e2ea985b3cf47befa2efc57bfdf5139478c45de0b44f525f4d0eb2611aeb1d314b5a95c923726a00e0c4e36217b0c458701abd5e49b7840f7f5593873a91e6a4e02dc5c063d0c4371c7ca821fca3970235600694c10fc0e7010f1d361bd06cc8a14f0a0b441d64813bf31ee379aeaa5bde0c16dea9f6bdf74ef361e72004e0a7846d94be2d3bc5faf6644d1275b135ed0d75bb231d7c09aa922b0f01719d5e9f1635b4a76d4c34f02855a027f6fe5f59bd10891bb1d82d0c69e70a08ed75da062ca4da096392c1de8017187de1409605bd7c50e58120078a6c90c806291f6499a0c901420ee872409607bd6c042acb44f90a20ab65554d992cbed21a246398a7c434c5a629364b91090a66506e8262738acd53d21c05261498a6c434539098407d0e0e1929518a6b5d1b79361dfa0eb7d266e102287a329711f163d5299748d9102f95b5985a772517e382678696c1a590e9cd7de57a73319ec0059de1613da252d1b720abf24ccf0b52063f2e74969ab4d6d9f940d1437507082fcfffdbee424610a8c7d5f415cc5061b683c3b1285531a0105669acdb8b29870517a05ba801458162684caeb089fe222b478cc35b2ee6567f566e1fbee09a72ef4d3eb86315fe821bcb74924caf7f372c01d6a7675306799ff9b4761647b0f34a9354c68b14e32e619a0b1b2da496af8a1f80ad22b9bf3b84f8d6843601f0ebd909370040afc4abc6b12826d0ca1d0b6b416e7e2bd4fefd72a1685cf622baa15702a3f89248d5b1758eeb0ec4c5580ff6008b6996bd2a3bd7f62313f6edd930ef2c594fe8068672d4d94d8cdc21277420ec2f9d713a74137e72adb3ac345559752e39a495f4741e228008b7e8ae620b3b8c9d91b229a46b206a0075c7b45be246054fe0491123e9e1722ef0865ec144fc19b224511b101be96953024ad257a29d2f3cef771161180b86acc33f7021553e486ccd9169721a1cc82373abe2ac160241e8d006222b9fe4a64702f7cd2a6c26fe7615b204523f74a9e14921f62942ce6411f192311fcb0a19ec02ae074474757cbee4d9037ec246f8901afb8bea60b8b1fb28b10290225cc98c6fe931b988417f303e3ec0097d5ff6a13d97356ae556ddf12410d88a8ce885b1ee8a1c305735ba00fa7a4660548182262c54809c4e2f3a46f637a6176584494aafc668ad190f5c0c276f9574ca354803d85c7061a8ac1e97f9da3bf3f27636e7eef21e327f3de122450d059b57b63865b48b841b8769cfc6cf2e9e23abb744b40de8830b5c4b6465776892d0d7be43922158429fe7a23b013d00d9471759d5aa94aa25c8c5893bc755717e3be4c5aae5b97e067afc626c5474e82c7df721efe575046b6152908beb578dc3a0d847e8f35acc2a742c7bcae9596da0d12be5c4eb5b5cd314a50c34b2f6be69845255b3c95afd0e676f83f74d853d02688cfbe087b34fdc6557f7810daf0ca9e2b5bab7be04415dda1fceb64cecf0e275ad6b8ee043878a0792e0e9d39b06facda256b994bc953bc7724f65c1ee982b857b5ffef7105a9ae2b7f9fbcd1fba59ce38593eca32ab26a2a91d290b6d9799f72de198a7c8a9261970bfbb6c68e6b82b2da781626d5d09a5a43476211b3c9d99cc127c0721de8c500e865371efebfa30b566819f41fd1d0a5406254ca8b684b399663f9f70e419a4eb5218289887435221a65c204d8d324288d883becbe1f79fc0f4d763dd433909dbb4a4d6058deea0de853cbcd15af63ca4f9e2c80c960621d2c745250a42bcd3d509bb2ea030a84321292b1795ebdfb803549992cd3994cb05c7dcbf1172c3387373c202bf12241bf53bff4669fc1f091ae1fffe3f5ddec490f48fe642fa14d739d2e6b291d508702cfd7b4a322ffbf51cf2231cbbf5fe0e5ac86f7a9b47f2edc1fe84331fd872248891f55211387ffd473e9cf9c0549b13046f79329e9669f6800ccd40201337f4b20e13fb37fcd8df1102b014271e530752499e6c0a61594e3b07f2f3a173bff5f908eba3e30b811c31d27c6c700cc9f9579880266bb08bb844743b0f4893b72f170ef576c1868929b119fc864b69ca33f8862424382c76cde7a3662fce19a48ac6224f37fca6fc0451cc0018f7180b766e51105d5d3a9f22b34c22f71d0641c7684045f6c9e24941d07cce23f0141070b2a28ead42148f37d58a83e8d7e22070a984b557a30003e432d82e037cda8acb92aa51eaa0b32030890426e308c81459648839100df4b22101b5fcc827c44781e66cad539a5c0b0dd4ba58726235823e388bf5a2bc2d5126228abbecb06677c7cce52ebf737fc48a0726449b5d3f37fd8ea24ea591dee2efa2ae8f860766cfeacbc7704c7f6f02835eaaaf3ef93726f538fae7f511f50298554a9bf0ae3a93c9d76914a94d9e500d02b2c86b362720470b20038d733e7e8d21475b7de7add09829b86448f6e30d61a3e0c1e838b18c1756df8daa8b27a8a4f0e9e19e79aa74217545e0ebf7aad221da57b04e95f3f210cb45a6464002997d0c54681a3e8df2c3905956a3d69a067725b6f35cba3476aa7892bcdee8aec6e18fa9a61d1b9ece698bd9789aca3937abfb270ba89426f0ada8dd210e81028ca3066bc5f3da9a624942552a5e956c4406882e5cc6950176cb3eb710d1436afcbcf82acb5c2961e9a75dc9c670f47fecf37c7e892a9bfa8f80e14d5f3744180f5359cb30eaa97fc0d5a35053ef0fa46b37aad67bbe6a73a5435e3bcaaaf1a94269bea4d8ca57d857f96a2221785e052f00081eec02c284413385fb4e52e2830e8e7315535b2693b59020238e6bd670daa60fba165c411f594b7d98dda9827edc000f91eb016c173ca1657808ae5ce88ce5f4feff1399cb390d16c63bb5c2aadf83a8f8374aa0b32249b4048feaab9ab1e9886d1dbb86e66880a83d6a3bddfc19f0da64a9169f93cf3cd0b04a29eafafe18720058dac691d2cda354be39b195bca845e2703aea915c86dff74e3fdbba071a140fc3f1f58aa5843f808f3a7bf04bbc38f088c8f4003cba67433ff007e8378b0d6756fa9b9411023c56fae1fa67df50ac7c948be608ea279cbb502be7ae3266b19727f20df9a80d420c82b727d413fe4d90053aaddba28b31ad661141392b80c9ca5207e9e9a85e551e6c9f11c943a1587da861ab2d15c158e32725032d486bf18262f25fad58120e9dc5b12200850bc7374c843e4be875d82b7bc7a4194146f7b3da74cbd95c6a5c5ad0224366d36189b71c0cc60ff3a4b6a4cff802c8427b51f4edd88054eb8ebed0481aaf7d34e9c19ba84c48f63472370f0e979799fdf600b526c383feee9b95d5568b1b60a922691064244d4b43020cb5077f03f91f777ba7a3ea544f9a1a932b6a46b546d2d09225144d4156de2b76647dda23460fcefda98feefe1a9a777b0e4ef11c5c0e6c42f852d89e21f6279eb4d84339e4b50f02c67c1013a79ca183b229a93ffd7bb61244fb10d1e224bc905ef447caf1eeaef572a831301b43263c63d5a4292dc28e62afdbfb40ae89a4e900d0e82bdcbf3ec55c1f56559093f68193d5556f224148cfc6736be45f860dfb0e68f4948082c63804a22bd5ecb7d4ec60c18b770c70a2e41baa32b01753837e2246c394b80b66c82dd9188b566b4f40c850947eefb6d970bd7ac9b947cb0972cbed362fbd87e4aea6d11e341c8407b9f9e011ffa72d1043f07264f65023ccdbcfb2bab6b89d58055688bef1e841a0300c9c7d4b4b2c6ece7b598a856918dafb1de2f63881b2535065aba79b347117677490729dceb24bd5a403341fb322bd3bc8bd98e3b97efbeaef16218c71557a5a1c0f048e314cb92c6ed6a2e23498dfde92e1f28500d75660210766f9bb080fc7488f7e03bd2ac9032f50c9e7b18045cdf6a8f4e5d7843950f396694e37e25a2dd625930382b1dcbfb8ebcd4fa295466bfcbaaa28c4f1cb3a5b00b6e1df0c837626514e74ef4f811c6f0917e417a609c41a47e0de75237c84f2edfe70e0debbd7872b2e1e84f4cafb7b95d2cb9b157bf983490acd3a16697973dfd494204335cd60c77b4b6d56ad377ff4d1178fd1965a0f5cd92807ef679b56a32f7316a803be746fb954bda619f1b45aa61ee4d9e9524ac6f8cff1d68d9625975921fe84ebc6a57ffb68646e1628b8b28bc77b777d27a4e25bee94e286dd003adec8dfdab7b8076fc4fa141e387359a9b02879cd32584a4e98cadf00e35a7868bf5306e4e66c930b38b2bd2e6c786c8c14777e0cd83209db55628823700d62a9dc1ff53271df42f972ee5b8e6f95ded02330bf566348e64fa7124e00e9765a582e0fa63f82b6dabf017ecf5c2bc37dd8ad74e9039c37c6ab8c4e8e8988075ffdb8868fcac2d26231edf858ae420f383a449002a55d222f09cd78ac57f547d2878361cd6b44e2af0b1fd1622123c0dc042cbd7c67c8a5af23b17a06f3c332f830203fd26ab086979b80ea59b6d2a18ed8c242260b82a6376644dcbcd4932534e5cd51b9b253847d0bbc8762a00d1804e01a82fbb73a5176bbbc8e4210ce1ab6de53da8560926492c3cdfa637da9485513dc94b829dc944fb6ea13156981a7cb3972fc13f6766ceaf1f21ef066d4677e8d5a2f7c81ad0408e3961d6a0ae567987c568dce62edf5796d649e391cd48024685e1ea9679abd9ca042f72ba1d0581a5ec1227b1642f862f60d4dfb744a8870ccd268e2b5728dfadb366993d7ee4626e5a71101e6f8dae63abd0139eb4842b9c05f45e27ba037b2006e29a0845f279b18ab94a191cfc5594fcd82bee7405611748f800b396909e9481517ceb43d7e2fa734ef120e8a5244dc9e76c92d002521156158c56ee9f23588250ee8db22205e7c724d9b5c403dc92bfb1660f48e483a13ef4a299ae747e5c82f6733bdbcb9b65d6c0faaf1aeddf385412cfc23c61fcb54803958423df1fd5e0c3ad88c22731792a988f764516c94e7570709ff55024065ec8a6b97224387bf6344dd5dcaf0bcc9ff575788765d31b6cc4664d23b68eaa91fffe5078954c45b7c5ed7a734c58f63a529b2e45463dfbd68850c3f5210ecb1339bc549c108e3905813037f94fdf63db435b4af5e50429c77a4f28183a136d477b3b06f731038b563b3b952cb074f68e3cdabf9362887954292ed3af40038547bdd791b3d62663f02159a4fd5775dcc584203a00ea6b4f2807735c12ea9061d4dabc93586e8c3d5386cea9037bbaa450b5100c860f482409e38295c6da43d7039086a22287435e5e519544d30d31af4c20256fac222f4d251c06fbc23a47d7e500cf15f9a4ac361eceb2fec68a8d0084a210e1fe161e7ef6cd3b856d39e3a119335ccfeac210f0a3933d2e8957b5ed896c3c6dbbc1bd3cc49d4fcc9748c50a238d15084f4137f1db4ffb71f33750994f5232613301250514d9dc1f7dfcd09b0fa31d949dec092b424a680a33cfbcc392ee467ec0ee7aac44357503400ea0c69253dbfa4928bbd8185ac747226a55efc21a40fd9da5e594fde0adc6fa19ae13c2b6dfc403eb2e00d0e3de7cf2611a9db43dbc3ec73663435033acf143c517e44277659f72777a987219144ec8591f2263106a58503aa8b64300005f62ea92e11129062b0b94315c09ee3b0623ed80346b3434a4e23790a8224260116fb5bb78dbd5bf63c04aa7f7f49f755da68cab4ebac5e82901922e3cc5cc9b3537054621f802621d5bb8793286fd337c56bd261875a45727171d77436941c00f705339666983750bb5e1a812274c15fecfa94d2fd409ad440d7c75997db3c06504a17a4ce0a32a780ad5487b302e7d498839bf57f41bdf153a20093271fb669f9c1a8eaaed2a7e187e75b7d238818c7d54530d9902806e6db4e272c9a481e0a1c854e73eb526c8d06fe4990bf13998b8891e4ce30ca50c3b9d090ef70e955f1fd73ed82ab053a9981e4c89d3cad96f77269d471cd19e0a641269e32b75c1fb1959844f7c8fca512eb5d927b9019b362bbdb633059c044ca77e7d3d37cd2b24e5d46c3034f52cbf863afb9749178c0019d3b53ee9a5a04b63bc1c17dbb3e6bbfe85684b05a32e18106ab06d7db8759447abb6b334fb742f95aeda71cac957479c126dd9df6694d60fb8f546ede90344b272229eac01e122775913d643960423bc49ae4818f1297a32b8bfed60395c9683519d01a08262abc6c163d33dff6515bd371659278cdc557c22376a6ae180931771d0c597f1ee10ea5138d9d9f7b4fc282ad2d5d04ba64f1c93dcd5ea77fa48497a7c23c650a107da469afc1f25af48b169ee7a4d798f6397c3d8bcbfc9a00f54fab1e5a6466102887bfe2c08443731dd4a929be9c7b108c58560804174c8d444aa9345400110fafcb033ff5a01444f94568a85e2ec0d9c39f1dc9d88d93c5768e6c00a16ec4ffb19ea2303f41f7d8a30dd1617ff35c2df55b366fb3d97dcb6a2746fa275483fb1ff1470ca0721e273e75c684c350ea11118a8da0059889350238a5d5b5cc7178f4b9d3a33233859302885d899658adbc20a758e2373994a1eae538995ed160b029e9ac3bb9d0dd62813bfabce3e3a45ac0b3372bdc707ca6f532f928c3d623c24fb820865df970e58fc017585688055e0c0acc370d71a6bb132d2e24b4631bde3830b83563a7ff72fa648d935db566e092a54641d8292c6c364dd5a531b494a021f62a8a59d02234159389b523f0e2b9d7530d84e7597c5a4cb6da2a307f2bdbefbec2aca580e53dab43e6972dc802e176705069b7f836f263e42c332628f0167a08c38cbbd4d8cb6368d6884a00391375e9f3004dfd6f0d1bb385dac45a4652cc56f8340affea585127596bce92650361d2fee254859990dc6f0a9f8138f007aaa71cb451f236a3480f153a73b05402876c11cf4dfa231a6e717307c46d28626692dad2b3e6e0d90fd2dc3f249e0324105754f0013bbd14003b7a917334f0c9f54e0f0b54ff24735fc226848143bbe6f835b2cf6b4445c54547dce73e6eae9e9beb00bbbbf244642ccfc69b47523a42dda5913f1bed1aa7c5040de4fce431770c7da2d32dec54e116eb10a0e7a3d6f8a53aca3827e3569695f17fd4e712ffc7d19095c09d580263dc098414ed995b0982e39a5f25b087f8c1bfd64ad057beb6a87207400c98e3adb98f4a5cff9309b149d098fbb01fc44b72ddaed42f886aa9b2977d413b2f20588e4bf109002b72ed6d428f1ee97cabe388059c77d13d7fad529201d4bf8e7707797f01d640e557a5ad44bfa53c983b6dbace0e93a5bf43668712816b052e864a9945ecacc40ffe0bfb686902a0a5b59b1f960e5a5be30acd52f685a0bc1b840cfa9f60ad9f9c3806080b2ded718b35a3ad3098aaf07916ae5e55651ed92d3f36b23bc6d9f464e569d81d7abe5d23cb5bd324dde9ba19105372f10dce6964d1f9d97e9413d65889ea9e8872a858454ed0a26ed7eb0d7d211285dc02109d3d79f901389c105b1ccfa33d9f3a789f2bb575fc8cb5dc184a0953d6dd6c458d1d5440dd0e89c1ca72ef4ba89083aafcbf7e8e9b3d5f27025cb85110cb0d2ab3f17bbebc1b1b9cc49cb957bed2ec0b159a8728d4c6531cd324c12fe8e82883ad96a1e3d27b49b6717c2cbb5d4708c383f2dba74d436818f48cb5f3840e0172d7f781bf45a240b8cb6e91be0e120bd5019ebb700baac0e8bec9d803e666f9e50e45ab40be2df2e4b2547279870a1b0ef4487aba7d4015a25d70c8f8dd1961d1ae3190842352cd824b0a206848ffaf15d83abdad2a473e64aefcf654ddec907909b014650d4d5e95b937cf882320c63551ef432e52a0710947f381dd5656a1c4eca39cbb7d4c08833eec3cec5664594563df6e274123b85f3c55033873089fdb3a478e5e9779b0dc9848d5259bff77be992dc562bea3257c8f755e766c196520f9a987b8fc955ffef9f2ad9810baefbd6c7b112172b040822d46d95be376487afe4714a8423be1234882d64a93466095b9d5c2ce7ec54f8952491734212b7dd8f0a8fc7377a051c1bb643d9388e8fcb887b1d2a8a0097b5ee30409687495e90301d0c2e4f092ab7ff36f48b7c53442fc95edbb16456b6b0251a85365ff10da25caea4015594c9e8b7cd59330e223dac82dc6a8658ecf1b6a88055761d89f166f44c8375dc7f0c635c21f67fde1c0b8941263ab3f2a2d79f14af83a666697d677f11f83b7a084eb1e46746c6a3235c7493070d5205daf00500ed073a41143694dc0fc0b269b60c783f7006ff478e171b10d9e8e7004090198423829a4d9465499056b6065f710008ec3630ca6400716a79bf53120df8ed9ef7414ad155070d8573990e34b0528d3afca3d3ccf5a8e767e22c4b4df4fdde11ef9c2652fc4a21dfb70df10e27633149475a1a1fd4e84303212438ff4cc8dbb62cfe8207c7fded656f07478a96447f22fbdd723b8f6b081f620ba8357f5764f58e641fa310f621879f5c58c64b3f5f1bd385c45f0195c40685f49dcd16a2138d611ec0d45a15feaf2865dc7c1bde1cb2b76c9555147b5956b035326d75ef5b349fa2c26569963970c5d2c57c8beb07cf2ee55b5e74a88ed1934d044aa17d9f98118e3f5366d3239a98ae16475dae6bed269d5262f41adfee64e0e401ca276e0d413cac1214b4efe402d5565c81f11217a716366eeccfea381069f564064c0d1e7307c7b9201d426c361edf1a6301c9c73c13419d1d4c74d23eb813dfede6d0b747b33c29996c7622580cbe07be9ac54bc58ecd762c9dbcbbfe61a3923ebcf2a8891eb228ee86124966b302d28f00ae0a20aea56ada3fa223a484c1ddc47d9af2704366e151b3a73b882c9b61d6679682bb80560072065f00323a83c34861a20dfc0078d80ff23b80bd08cf92acb45307dd93e2a272ecd0dd26e81d52d1097c21d7c35e3c8d28bfa730780f2011c6c619830b860670c37fc99bf91e5cf117e4adedad07ac59cfa1b4c577a38ef1c572ed2f2f8b19ee5ae9bb5db0e4509ccdecbe0638cac7fda4a6bc0d1b76c7653e1068cb6cabe5c14c3d3e3b3f5a0c71f1affd23c2cd22fcbd8604c2e48aea52b31c87dbf6d01afb88032293aed3d64d569d5026a53a22036cf0e74d8e050ace61695ddb14c96dbb682b58ba620aa951a961202b6f2ff1501fcef8cb403457cff719fa538e2bde80148b8fb56c46813fff9c402a61566f0cc65f146d626c8c4c2159fe3400624b36be5450eb26793602aa43728f568db518d1a44740948d7fbeaf89d2f83e1657aed76110a7f580f9ec5c38f4fdc37aefdd57fd61237e7cf16b315e8833dae142d1c3aa173ee52ae7b5f12513f8a8f4fc2b43384a624c2fd1e84ecc8569d147439244fa7d03b9feb3cfae01fa70cbcbbd30614a47b437ef4e95a73a2f84424a0c71e04b57d742a093c4c259a48c02f89e915e8063893fb5f72ffa41518b6f8dad03490ac90e468c66eeee2a9d54dbc871c3e090ca271ea3712fda73954a210e496f138552d589df4776a3724ce21ebc0f68d0b06fc01a4432c9a9c2a32ab436e93c1408268df80a9f2e0f569f9bb9186859d8294dfc381802b048a9457ab57417f048bb3d02c0428c649c6b1f92e4422d219b63fb07aa695467c4d46b689202fb9703d75a051cb23b82e5c7452cd8de86b8c8f9e1b48ca2e8951acc5e754c26a096d2cf83d3493281a3c367dfc8aa3d860f005cb0a7083d338f1a31f2451f82903e4d9336b4bc39e9e82bff7e94442dbc87cf5b03a47fb429b95d05e60bcd923eaa5ff97e2cc4c4b12ccbfab3d7e19109ac3634a57b4cc924eecddf5b8fac304fd28422fd0a41eaa779926ea89bac8062876e2864d6e011708c3794851a22dc65fc99c25da713bd0027a54ec26711bb20b5993ba79979f959affe4c0e8f433023b267d36870dbfb91b9c41ad528b3b19368a1a36ecca257a8dadc6c93e72781d318e0fca097bfe4aaef5ad8fdc5f20363b9ec2de4c480006e80f2014b791f644b890fc3ea1000942edd46974cc878d6f5ec5000cbc18408834b393c52f0ad3d4bb667f9b1f155957c2c9a8d2169a902b78d64c7185e58748e62563421e1a9ffef08cb36d1e63342e5f4768c675e07d248311f17e8972f2e3685220c1842f21be2a99c7ea2d78bcf1dc7112ee20c11875dfa53a763c017af0cc25039f6f26804a7bc4ee5624034b8ea3f4c8d3d798941c7c92127852abe6a1045fc4ab1b617236b1d3869db9ba09ffcafe5829fcff5d8f8e85ff6a8d657c5b519ef7206ef83f5c11fff2bfb06ca9b95964fcb1425fc79ad83540c2b4cbe072e9e8ff0a96ce6dc295e0a5763908045e9d0203c8365c72b1763932bb3ac0cb07109ac6bc0b479d595d69033f3493f8f82be7d6400d21d3077b3325dc2170bfd98afe02dd8f68a15cf859dea4e39e8f7e20666867408f6accae4c8c5e5cf22acfc7748e80c4360a59ee1cbe7e9740f46e50af1766e1366a4a72988fe787ab01b604a91e3363c770a809bdf11fde0e8a24bfc7f05fad1f4a9ab8bf81cd4e4c02f17bdf690184cfcabdd672bc37e0f2972e3278d16717607e0a1e93ee9ed846338c2305c1b31fa64cffba0d07e76e5b123f2f3aee150370602b62a4b489f36adfc9b5de312634042a6644c50f5996e3cc34e6cfdd56560164d73659e833b865c8706bae83365ea3563de1fba2d055847cd0f0af95b1f35e6631ae0bec7cc50e2ca126e3bbed304275cc176c53148eac625974e78b0a058685315565da4e8193d00cd80ee0d8091fa5e6553d4e39b932ab70d90cfef768f9fe2e5ad3bccd2f21d3301662382eae932ebb407b1980bac067cca00d4d342e97da82addda7754be7ddee6a41c43600545197c0aaeae924af74b98f652c36504e8174ebd7e82f02a0685e5cb3499b6eea696dcee2ccd671bf7e86c5db18f6f00c4def546c8ed2174f740aafae5c3d118419db9edf0fb6b2a32c95c17d64144c1b68291055ca109aa156aad4f3d9858e19b4b1c7baa706fa8bd87eea38db97f784dd4ada2cc0fc868ed0f60e66b3c3a78863dc08927c6eabb5ae77f240b371593d6cf3b107fd4dd6b679a3a5d78283411b91b9051822027aaaf03ed7db4018203d4d41d617d36b08cdfde4491dd2f041a2adf2d0b7de8edf1a352938045853a40e2c95d0bfa01a7a32c8d0a93b30094597b00857a23f2297acdfe5feb4769353b6654e4a88a333c0b26abdbed19c9901d0ae6c03eff12ab740b135a013507e6f3ed99034ea0bf062c6a78ce2919f82554bc77d1bdb28163127e81f0bc2a4dbe9432d0815c1678be989ac0168763ccb94265c7c12853f9719dadacd87e73470a439dfffeed502a8b8cd2657a71a5e1ddedf34bb55b98a1a9a2ae387614399a5d32b4b18966ee0804fab4a70ce6fcd10ddf57d86020b7a8643af0926ab727a0be8971a8c8240eb1aba4d25258e7b4b94ac1ec4a16938a18a772375f49c2e7f5059d56be417bb49d12af3f8d422b9c370447c266de42feacaaf62e0560fc3cd329b6385852f50a009459ae86c7baaabc5dbc6f360d8e24c2e81082f4f29f4d6db956ee9ecbcad14556e957ae703be5387fa72606e493032088a4af99fc51fe467683dbc93f4d7a0f011f30f7ade9e022ec0eec10856172f27e417ae64fbc68600e5a0fdfb353b34b54d3d2d3b13dc423155c419615b2ee1fefa7bf172e72ca26bdd4598f9986ff725da41505b77d85ec93679ea0d0a8bff0fad572116e80a2072d83633b0e033f63dfba5d2872e17f98bc1742cabc65f3e7adecef4b356ca894907abe73d0b773ec5ce2db7f0069d960b97141a111708842178316d89ee616b3e7b28071b00baf7f23d2a0c128d4d5d26494600afcd7949b9cc4b0e36f7bed117fa2debbd5b9d1fb0b1d2fc2d9dc5507beb06d6f5e02bfb43d2731648d1775bffb793e9a983e33f77b40d85745f4fbca71d13c5ea62949f9a8617a1f61a53201333d7d310c90ae1ff6cdfc598f8c22b11f0f5d7eae49eb59673241c55c9c29ddf5aa0b7bfc1b8cbd9e2569a8b505acee2fcb52b9e164cecbac4620648819d91f62ae7ae5d809fedc654800bb6ca3338f127179ed85a6c1006d434672bd986e593c2d06ceea22d36d2bfe210b2684384c2d3e347bf31991b30e0de3a3e68358ac86614733a3ddc74d4faddac75f7caa694abf5605986356ac3fc6a6ab58591c90d294e94c48be1e45d31f16ef3e908d67e1da8c4af976ef3e5d091a1eb9debc6ff9089ad7a9ba6e50753a2595e410c802302fa87f81e9bf31376a622faa27e456c5940017c09c94a1e40d4ed2e2b88911281f39d1a2adb89e2354262665fb15c5d17cc2397a5a3b7a362e29f864dc0fcdc4ba59211470a1787040c907477c29e62e7f4417c27e25e0d8744856473dfc9c8a0b4f4c2ea47118fdd3defa2eb319ec189ad0ab460c02bc30fedddea0d590eebed33e36e06e6b0fe6e3825211352e24b8a991e08f106f8698d2f29de323f86e53c57a1ff5fab7c3dd460316e249ee104e6fcf7b2ebb417fc7642994d5a859d3e17b56c8bb817c434c5a467bb16c8c5f0fd3b794dadbc8967d00f568b53e43c75beb283fc9baf96ea9c081c34e2548a6244b64479060f300a50bdbab89e1a1ee63be723642924e4d20ab460538f26f10560f9ea34a65309cab5e8fadcc49a94626b4b47d89b1b64f82d0c272d78d7dc44b36aa11cce4fc527e84f015a0c1d9d04de20d0523aa3b7aaa60deae3283f9b65cf5adaf79bd17bab1ee901fdfc20d45223ac5cc1487411fee0178bdb022c8e14b9b1185007470814b306f42de89f292b00a34709e2dc81a52ef4acc503c7f2e99baa33c9fc441071b848acd842f769fa38a77473df5877316fd31ea6a4b805e2c7aad033000a5c9b48126c692a8d2856855e6af6330c71acfc835bb389e03908a32954afe11dc65f8a79e724c8a578f9db46b4ce99389e80d9b0041a141ce590e5007d96346207fd8bd9ee435eb566bc4ad8dcbb5b0fb3a54f354696770a7e4e6ccbcf90a3090cb6022ab1155b204865763077924070783f8120526267d8fb6d0b6feda500974850f2448c9c1a0504e137703b096d4afad6d816f5b2650f8afa257ec37a1d10e8abd00b67b935b7adbf0436b1d5c186ab2897e93e1631652a21e094e33b1d5a0cae6e70e1014d43905d8d0ca68ca49ba37965ed38c9eb7211f6939c1b2989095a4c9a70fb9b4ac7f054eb00f13a6f0232edf444a45281bd5c35a0f288c3e372499bfadac112d1ce096f7afc7a3d68f918f41b6ee595a7853358a14dc70cdaf450c24f0c42e6c8fed448f6c7338bdabe983e43a4d45a2ef1dff0478c69c53b64987eb1ed47c657ac0a756f5999725d0f302187a2d9fdfb258389afda79f749f6dae38a8338aa7574e38947a58747b5b83b2844f377c22280425394406ffb99bce1923f35d6125de9630fa4eaa9222a0c42d028479c74b73d067a00e698724532b0523af1adb36254cc987929029ae1df18e496fd831a30b3174cd97529132ea09cd6d19009fffc68eb68ac7603a9a933702f58cbbba0c1d61c3ffc5fcbb4e6ec00cd7b8ed1d2cbc9e810c04688e9e688f2a7b442e518c42859be6f16c02c40a5478da86fdfb0f4af59e704fd50586b5126ad9c7f022e6e120a25ce3deb5f50c15064bb9a3e6dde07a41368d57b583c4345d6d2199e79ca9f7cb9a49091a08a3160623c6c41f050e54b16be21ce916aadb28819bb648a4538c60f68b5a516d09a6de4ebae0909f97f8c259f3b227ba43436de6643b12ab5827435b9acbd2c398c59d3f818e92e87cb1a8f354b8f48a81cb34db91810ebb0c69800c0713ff91b1562fe89c2bb0f937f5cb348b55b4d011a5c75960c49de2b79c75e315ce8a868d10cfb7f6d61c4052d9c32dca69dba9d0d949b8897d465b9e7c3a483629491d7f2f4762e812ca8e5195787822e7e887a8bfbdeb8c0d06aedf19181162fb9f59eab9ca9aca26604cee5ac24b58d7558639900c811ba65357fd3411b72b35818d556ba8729c388f87e79190a0baa0b352ac019d512a129a4a5092423e457aa0a2e63ff3231ce4fa056dcc48bbf42d9b345aa14d217f35339d02658db3a925f665b2de9812305db837b0293e01027a9b40e8856d43605edd7ed917c4c17b561645d2498fbcdaa376f0a56fd4bfceeeb9f8258cc48a7a93fa981db32716dc62408e483d42c3947c6053bd758e76f533a694a89a60087fb1454904f215fea009efd0b9e7354e86f8d1771dc4a0ca4837738ebd932517073625a5e5a13bfed1b7f54e346e5defbe586ef1c85724e0abbb92b101573708f4268f2a202a1c70bd40a8a9b779d7a68ba67812b5a04f579b59a05f27cc99dd3ac97de6260c335a2f87fef3d16bcac1c0164ee993b3fde93ea93036494bf72eda48b661778b73e0eded587c8c7436522f7ce336aebce41d95d0f9f439adb776367c8bf119ac4113627558022aff8b00d1d4724fad3983895a77c82fe09e7b1fa441e37a83d2bf443204de76a06c4318028e4c0016cf30637c8cb531d45ecb906d33d0451552b6c2c0d8d86a62563d833ca63eb3c70dccf6a1f9acbc6c4b0e85ef3fac83bb3acdd3648570222ce583cb403a2dee151df0b439a5eef33cfebc9320e2aab002a225b100712f5ceb0f59d2e4989534fdbd1b987e6c01f707b964682291eac5b513887e8944477e38169fa213cbe5da816b57f20ecead940c1fb8066522986ad3600466635e26fb2e482e4ed5b8b113993aa62c370b3d36050bdeff6c852964b0f7511cde0a09163559e432331f377962a3da830bdd855a4c3f8a84d896a2d0351a547bdb1a3ef39cef09b940f68c438bc45468d9e0cacd55df2e536f38868fd36f8e913e4e2220495fa2d284553442357b07b3fea6115b42666746091f8ad55f050f7c9233bda10e9f1972247499aaad4d9515c919964d1846d8f915d3e67209d1bca575504b150503d465e29a08aeebb2cb16dffac56257ea03d1a8c2b99567ce23ead111263a94c687ee2f64d27b3f807a0eb32277b96ab611f537702ac349b04c412c5ba899a896a40a9275b6943ab633ef975fc1296811c22a1c1c12e9d5922563d199e5c1f08988db2fe46f940f45364f2448c30992f17abeb7308022e1d6ca59c58e2965377e5f7368c2862d4360bb5bfb688cfb2cff05839427ebc094a5a041ee0effaf9746c05c123069d6aac6cafcea28ae8d341a27083ded1125180c97c185813b57fae221a82403cdee0e15112aac5b394c5e6fae3cef129bae28ab171d334ad34aa4dae4d7101bf678bd4a48c97bbd8b7b1fd6572aa540c58047ce7ee393de140584912bfb5e2c3cc6a51421571f1e4dc5bfcdd529ce5deab631c5e07b7cdb1ae1ad80b384ccda784fb308a7eaa018858d7c992368f8062d4cbdc38cce32a53a8f086208794783dd5a6710d77119821c34e83eec50dc633721c993858fe5f9b4d5f6a592f098f26f4fb1998f6ec47ec9423de1bd810ca6a401dbf533866ac45b85509bf692c44175c2d3fd87b5581980d73a70d801abf99c63d16fa57b4c0593a4dcf794c3d1d8552df85bcdee98d120237f15267798e2469a5718408634f75416ff44105c1e20c7b2558587c6b402c441a80f1027332401346172a57c0c1055c9e355158731432476cba508949f5926461bc99cbe9670dae82ed82dc92dfffc3fc2e61786789b11178f3c5dd562629afba80e84f3c55a2c7c972e21452bc30ed754c9b41bc66808d72661278705e7026bd1baedb9338e268355994333f7b6d90cfda7b8ff0451e573f897bbe5cf47aaf921233f9654dbe599986a0f6bb1a0fc7837179a6776e400949be71403bdfc554f00fbc28020dd9f7c033279993d119924caceaa6c5f71a94a82701b334d82d7cad6f44fd4d288752ad508a53eebf2e0652f8ce6b625bb78421bc92dd5fa2333b7a1137c61295632c451ec93acf8a7f278ab91fc2af1ec16a76555ab653268ecc654d46ff90a413196c1adea1fec7f66b5a92a06920e5744c2af1cb032fe61760b7f33b05159c7d267089e6470ae7bb786bc5a47dc406bf01ba33637224d1fede3acb7e44408ee53f76969ae7f5fbfb1f750b30e2bae6ec246300d2665bd97a21ecab71735df804098956f58162ac1adf42243d5b1406379255bfb78889c820c78784c69f5632fca15a2a423b22bb12a0c050d2c5fbb0ffe9e7acb9a7d55fa9cd74a4dd12b65ff73bcc6702031a590003845d2e5f20092cca4a25e368a4491a36f584102ffd1872be40a91ac803e34e390b9ac00b276b6c20d4882923eb35ae29ff871f9c5697d7236018926dd93e0f554d543066e0e813375135e160ca0c1e340a090322f54f4cb5b18a04fe52f776258446acf5cb763f02275fdc4e8d69b5862d5252faf18f3c4353c331af40c2c323a30a4880b7de9bc4ffc0e1bb6d9b358c6b13681db9a1bbf50726688e236e0d6703c4e8e5b1254e9f6b42fcc60c3c5f79737c7e493de4a7f8201510f3faa6008d6ee58193ac4120588da4ef91e0c0f47b2dad9b26c04d194dc107f46daacdce2b20b71c4a1c386410debb48650ad522e5f816e0786899aaea1fc14eab105553a5ccb51a2fc7076f544f137e38110b5d8e4a76ea07fffb1bb75e8ce38b5ee2f4f77f61da78799fe8e479f6e2374b6ff51044f3e5dafce10bc5a805111fe36deb17fa61cb3803df04b133ae995f44cd3c5046d550ad98d02bd549b3ce702f1cc54424c4ad50258cd81fcac664e1a0d19a87ae7df3157a067f4c51385fc714afc41c5e698d9941c1bc58ba0a796ee5dc62673718a41c8d9e0036395cd8b525488c1ccdd4de32f28aa8d91c96439521cabb1119f5b77547bf78e134f092719254e8910d2b3d55247c8236e16b801cd08df4459fec711e61b3e422ef73861bb9c930b90f7d91895168b5df4d80e1e510e91f6e5e3b6e778f275db913c081a444fed1803cfc04a65804bbacffe832263c76075897f11283c11458dfc99661da5575a69e957825014505e6b07035354d1360575a46024c50af7e258cee6dc18c139f3995293e5a2dc5a600d021f870c053a671b7c49e4e88ac2cc13357d0a7551d6f42e2d52cc5273a15b898884387b68f3ed0f54041609988d3ba424c2f5070db5bb6af9289f01c3c463887c5760066715185089749cbc647e85d1abf0834b79de0c846e6c7c56f15a361331d490c88c1c8290d3d0097a0314298364fb8b662f9de547afbb0ca70cb0ae0a0cb2dcfd3056ba8ea3414228aa07cbeb3adc761e37583814d5ad8c003b1983df1f7dcd169428d5061a259ac65cf5b787c8fb39a7f59829f6673302b8f6ac68986133a41e39cffa07ed2fb4ebc00809d6694788f542c4e2d62101d526af12b395e8131c732251f3f807e8b30d32204204cdd64b1b8daad473c855e94ade0996755a4a20e6a3f8b5525da4a667f7afa7969428a6c8f49d5622283a52d86f1697a7cdca90ef15654ce59e997b3793e818a23f5dd9dbfb4dcea8ebdad679c008d1efe43fb776cc31e3eebaf0d017d5554c88c6f17c604589e8015f820189d357d68354c16b44f548b06bc304dabe0f0f6878c133ebc6dd02f0a5a931a5525d2f4886a647c70b68dd7628406f89231c27ad4b66e39690039937e1ed3d33ba911712a6d9cd2c21831a8a0542af1881bb7e29e33e58659dc782ca52f1f65da4e4d382dbe11f48fd763f112ad7691e942412462bf0c16eadf6bea806473aed118a951c93b1679c7650a89294b9811ca46344fdf5a42f1cb02efc12819e08738b856c05b3f4a157cddf4444c227a3a3e879dd4fc8331b1b8a04c047d606320aae484c024c4f8a74019c985a7af2d5eb89ba0a3696b93f1d1ad23b0b54035a3e0a2fd1b913a70ec63ee68030a8037b4aa6bccce6193b3b74db26d15b5bd56514ecc065f154a36a0889b206c265f034e990a992194b2cb1ac242c6f2901f8fff7b8511e3670a5cbc8cd922128fa141d28480222880273e5cd53d4de72318e7741e32acfea6f90afa9c259b77392c19d9c91a2cd3c99542a83acd074b1ba4b3727d528d99ef7ad5ff0e28ff516311acfef3b5357033830f77df8b98e7d3bd08bc240f23ddd16954358169a9b4a825a1ece6035d07d195c61a3434e2ac49ab2dd16dd9f77396f4c86077e696de511cf813626f405c71140c1b01259fb393e2352daee3ee14ff782e6ec14445b3457839d90c0e987be9cf1c3e031f6501033b9d2cd0cb10360a427696e90aca312e0b234040a3b46c740fed270ad4cc55a901f85ff4b9571f9f632af91d681333ad082c5e3ac9dfd30557c9f9bb07d1dd436b699fc7b84f836f58a016700739ebba44292ab00dbc3ed33aae32a924b8f75e415b895ab40630c718f8ed777aa6f94bf40a28f1d354d02140ce750ae2f7916c65a5d450798a005204102d850fb07de843cd4a29bee1420d0bf763ba27bf5809534e7fd16e9a12ab6d73573abe81252f2f8c6fc77e572df2562b201d9f1efa99767863b4686b7eef07fedc03f281a5faa53b4a004723b67add7fa4f3d4e74203fafc3012a5a695e67890ee76550cb33ce8a5a77ac0d40e701c7b50aab5e4c8c2cb220d56eca1d50f25d7951e9bc2cd6fb270a208087d37912a19ad6bc91a81e334e595d031f8d01abc277132a07b05af4ad31eb0c0cae703febd51ff3f3846ce0349b0e829abd2f51c7b980c04530a381977e6fb4563227ab5adfdcbd3f75a2ec9415daf8f59bd41b845140f67cf42333632689fae8f51b057faa82401ee7e4dc5cf9dbd54353adba50a646c28730b8941460243a992cdff43c4053355e4d8e04c770fa104091c1d82468531230737de5f7a0ce124c5da24008d4f9592ca13f3e454427e3b0bb0c1d7150a96513a32418d250eb18a890cae9cb3fbb8edfd2b96a79727044131925d72a5396c3c000c7f3932286d62b9a71c2178113bebb6876c4e79b37b87ad2a8d60c86597c508fa84623dab67b0050c84d413ee044c7121949689f114d7640f57082760d5ef8618264dc975b3c534116245d471401338ba7dc53e6898e795c6c84330262c99af4f26434e85bc0576ce064afb598ba5fb0cc6c3f3b0fb4be4c15b9327bf2ed23bb87da76ceb43b25bb28d78da1a028ea3f512fe961c31d51734f7aedced3130a64cd13e71b8f8ffc24b4810b4e975d536290169e788b4da3d444b895a2fc4bceaf43bec1df11638c49f0824c7560e75a112c0af34bafeadd90b1a1aa87ba8218c2466a95597ed0bb49671b17864aec5cbf499cc0b941b2a3d64109ee1c4b1a982a373f0850ce4c1d297266db6c8b5427532aea34c0c54b386562c2a23451a886a8d55431a3cac5b0c801961261138df7591a8f948ac9ed14d27535543f3d81b1015222a67934d79bf0003aa45fdeeb65f160ca24db33b49a036afcc377708a65799854e1f3ae159434ad474005f175706888e924d8e8e8447a5e04a015cc5b599079523b1c174ae7bacf2e703dd75deca46922e5a7a14e2019c740d04ed6556593c6c0db284cb1942bd41fd772fc4e7eba402510d4c6b4d6d723b13ab1d88cb387ea5a5363a1b199c84f7941793b0302574c69fdbe2e28b86f3d76c4a1d35ec0718f869e076bf88387ce5fcc02f888b56a554973c7cd1db49ce74a958a578d0b5bd27ab8c6dc26a2656bd856043689d7469f3ae80b473af7667a01f6d167d27f214837e2dbdb8a7398fc40e7bad24a8a932e769b27eda508758e91af59a048c8feb499b509b171f504c381fc320f62d81cb49b7a6ff754423c42e519cca69f46689fa9902f813c563f3a74f8621dd4f23a549a1384d632a2e70917b74a5232481be1b1e0ec736ba91035426f555ba763a67802867620201edcc1cdf1453f260609b6955376189a952b54374372b0d57c279924b7d5ba31ff425cac06be41a9f1384a157d5fd6b5e9c9e4cd8e91350b4053340e8955c7900fa08a81ead5a06560794a054069cdaa92ca610aec63a540018728d3eed619ad144505d1567a19f9c5e9363e1d8293f179c8e25e59aa103ba31fe2f64f8c0082bc06f08ac3e5611c19a65bf3ae0d05ac68f666636201e910f9f84c34613bde30725a00809b1bd4be921518541dde5c5142b02630f5c41192c4bad743d1de29aace5e90d9e0b6c3e320fd453b5a41e12935d5869eb9d523042ad3c97733a8120aef6a38f612abb7ebbf7c8f516c83b22530e5474705d66a0025d88c40f39c9ed7f54c1446910e5076c402747fc0b12db30805cd49f82d67bf2822810b127fe54d4eb174e884cb5d71eda12c93da39036d92143788d3ccf7e54d3bed7272535c39d75f675243bdac184d7b8d013bb2ecdb25d2864a44ea8471c3c556ab694f05f08fb88d8020035218065ca71c9efc9e4de383a2740f43425f036a444f9df8fe99f51eaf6c208bee7ead802e7b786ece93d4ab779ea6d394c8c353f5b8088366d271bad259030b613a267078e740c0e296420fbced181b7a0064682dbd5f883aa9c47e873d5822abd4d4ed8a7c4ee97116591ec017acb1567abf51aaaf466b617b350d6402f0ef97b5910e0f5f3ecaaf8a848cac2cd206fb3c3e79a3e6106a7a32d5d48c3c930aa6015ea4cefa222436a4f9ee854a590525325ee4acd43415aba75b0af9f1d613f220050155fc20478b6bc6f553c1203c7b92d37ac47b7c8ed1855764943a305ab42427668e8caffee01abe0265431abc4a39fb6d6562ba02a64c8fd45c142eb951286140383b61dff8abdda1c6f1aa16f4f36e35a2f5fe4c71d542c8524e37c132f1f11c4f4d8d019318705cafcaeba2e874b71fc0acf9ebdf41084a881ccacc169b7adabd4a693fd16e074bcfd75ee5809d4625eef22741928f151c32c1b2d71c57f2a2259d4f12c4122841901f737f3fc24904f282db39601085134c1fd3f5ac574c1f3635b6e9fb609dca2524842d326d77bc6bad526aef1f8e307599117b7f01909501d6088a726fc17dcb0a4d098165ce1f07596238172a840f6ea6bc5b3b944ed5d96484bf84dfa29ede170f6af41137a63262de208841b20008072931c490331d88fb82423d28c9ad99523ff277d8eba39babc68d13079d923c1c22b62f9d70ef12b85432a3844efccd330d3b7a2c4903ef5378e092035a521adbed29ad1daf989ab378fda625aeb6abd3120de27c7d2a528c920221213fe36a4d9d630a093d87cf0021d794e75483d840a693e0b22a0457848dce5be7ddc218926ba6ee0562e539b0899b82adbfd00a48a19fa33b9a0dbc48410042bbb090ae480edf6cf98153832e5082d415a3bd595e404b74e17a3bdb0c48630dc8702f0c221f44535d598c0c760feec9866b6a2b3d878638396ab8e1bdf434228a7bc1f21c67bf01cc94719f09d1785ba0ab56a9010973121882745b6c72ccc5b107209b692ae894a02c3f82f035e5a342ace22ba0fea4a80b2c86ce9cdcf58d16d6f15dc84afebe3f161bb513c61953082e845d5d9d03f07ecef81f3ab47c2cf7cb7f857f738fea33b2677d1094e131771e0fb3f4a4ebfbc267575941fedecd0f588cc42fe90fa781e32492a6287e66fc1ab957cc35c4350fc40ce46aa22f967ccd30801b5575d877385943f0fa0c4530190ca9d62b0902a71cad6b65dd076ee718ee84d2020b7b2e0282991aae66f24ed205689230453045322a731c8cb34fbe1e319b31b4f0063e3295dc6276567f5114c0e0ad37640d3e88a7ecc02f6c9dd2eaae80e51c7c1e39afecd36b622101d04e8b66a28099dc03eb2e8e05ced173afd3774fefd9d5e5b36fb159367048d8ad6964b11f08c37f618a78637fcef6b0d5c5f7899a05f97b71b672a9bf179f321daa4a524e169dd5a820dbfc1ac5ec4c3703054b8fc45ba13c500e06cbdbb274bcbd96d82fd7a8d7fc73004d63e52a56fa11ac723f3d627de309976d0b1a200fb7e748eb3ef74f128766a8739424268c091861f09f92743f33eb9dfdc6ac1896dc58e5a8bbed6b576b138f9821e248cc2763ec04251bd05738de9f3749eea955eb9fdd902ebbd7ffe8c5cc05b564d1e3f4078cab5355d4cd7d586feaa1837bed3732b27998e0c2571c3951606e47d9c9e055de17a4220a79e3f39386b4ccdc450d03a0834c5bc33dbff4cc8a4c1ce576124da4dc172514b8d0c14406735f39a33ce148f1944fca9f6d427d7e660463b783747ed0a999e5028675ad406e1982e5f18bf6de6ab6e54a5741c914b0b58e4884b7071167a05faff881d437f1fbebc115a039337e5d2e58fef1fc8544044de3f6865d1d9a2fd23fd4197d46cdadd2f60daa770caaac847854eebac3ae47c17abfc985455c0ed79dac4e6c6093b7b5620ebc30ed054daa8294a1a2a67d12939e9c2c4dc4600beae57d6f57319aa78feb2b95d68bda8ee4b24e08ee2c9aa6d807e801efb690f10ae8efe86e8780adfa031740eae118ed41b21ee8cc1936c337ec0eea1074470ef1e63db08e87534112b39f587f83caae5bc7db5d44c2e369a881d33fe5bb62285b4652f24173f24bcc166f3fa6ef06266238103bf3900a2c19639f339258e8f0ed493994f82ffb351dfb2088e3dc66f16606e2e5af48216e425f175daf97fc7496d61c7a57644b0cea7ae77a1628a4c488e3f329dccf8fa908a0cdd1b963f96b2c8fbf9cad45515cd976dd782e3906101eee75135834483ca8eee4a035fcbd3217c7e60cd07b14f21d1ae408cbdd582ef7783b366e9c447315fc9dad84bedef3cc5ede7b82ff67e0127d33a89073cef25428bcf74a77fcdd4df868550c92393fad109f8ec9de84262efd9f2be2249228ac5618095075241319effbb98f35253ad86a2814a147406552ede378eab1440813ab4c4aa88f117c7862d33727efdd511fcce4f01cbde798c27a842ec09a96016e00d1ecf7937b045bbe0bd84968fc0a052bfe0bbf10677df790d08bcffbf9f5c5a07eb23c0bcf3fdb1031053f7ce7964fa34c82d10c38e6645259e9b47072f22382685980114dcf7a7990e2ef78eb8a3d7d385eed35e310a0aac1f3ec2e4bb1b3d4cc40866dfd0f513f080f3e99802ff8742c870f2e205be061a3341d69d3f9277f5dbcd982c66ecb4c2215a623fa860ec4fc642ce0fb9fe739c15a75c39d8d95feff6a2b6da686410d25480c50094e9632c9858c2dc52a7a4cc2d58b1a583c566416428e63c2985aa80aa4d61b7ff140209380ec1b5a3ce1cb0c023c56af48bb31bdd9ece7ef7f0b319ed95b916c1d982e6e8501c1e74212d42ec632555de8a55052c9e32928c4ebc84c1baf32f56068b505d47f0e4fdc7a817429a59adfdcf22c1b6dcc9b3a5756ca52fff56df24592e4f4574571bc85c9ef8434282268ec877ad5d5250f37905cb9b9f330500c76a14b7ca71b6a555361ab5059b33995f983dae855a7c4fb026415a3b4fc3389f96d58d8e307451c9685184ce6c2868c3ace1ba541d1ea57a5c87c6622d0641c9950539a17c1d0970edb09ef36c82b81db75c69f55d8e608baa4dfb087b636975bbe36cbc25c7b253715a802a05dd410b9f8a64cb480d52122999499e38e181794849992967cf560af127e7bfd8fc2353ed5e312c29fab7463015cbd3691cd71a10459cddadc0ac5f2649029229c18e34ffe8904c350b631088f5e9f913a801c7b90d8c642a1692a855ed18b85772585730cd1710998ffe3139622d153c44a2664e00bbf3f3ea40ac2d1370408ac2b65e8a79b8d295090c8b8e89ca2131129dec1879527d49d2ce45268982b8b6eb609442aaf18f585e3b8a5be035c1c0dfcdc07bd4205cf6e7c73ff524c191019fc3e31b51c843fe100f12f7788462eaacc335f5d8bc7707ce56d571d5d229574b5a22b3cd7e201e090d42f560f93a74f3b7ff63d6cfd507e03b308d0280f5cb78a6085dec7800866f93467f7665dad839f1956eea8a10c64d81e2466f09374d5d21d4275372065cf6290ddf2c7685ba19f9301ac9aa1079c17d15f78356fd3b4807aab8f015fe9abc619be01ced26e0f650da4a55d24afd800b6db382953442e36e640e7c689ea9366f584e409e104a50024a5a0994fa030e5d2d869550092995030ed0c621f87c5e2318a98e466e640e7c583532a972c77c85785c42bd80232642692b81125642a57fc007b49d0c8334564e6c48ebaa04bd2461759044236eacc6d188e0c01b6ae8ab19bfcbe012ad8cf1491020a346b8f198ff8dcaf87cb4db88acbd1775e7a757b06021d58807e8dba8d0b46d4d4816447ba093ff2bcb6ab3d48ad6bcea6386509a84a6be1a2556e6787d9f1975423893b14643821d19076bff63227427e91aad539584e58812080e87575ab738cbf529321efc0312c112618d2a13489006ec6201eacc042a5150dcd57405fcbf5a84a45cb9bfc47674404f11780d37873fe197014f709c38e385cb50ae8957450c5216defc35aaf4dadab52a5ff4a242d21e01f003c70f1d41d54631d7993122d6daf6df7b4b29939432930286028b0276c8e3410b903edd63eefaa1f0d4de000f3421f799a9b90f58c725cfe5b1d8028fa063f55c46fc68d08c3fa60613712fcb0a4dbb64ced98d11c38cf931b1704b2dadfc70d288bfd1e624e1fa73b1aad987645fb54ad5cd9568f5abbb9b9b5591ae4101a54b3683db344ba1a10d35381d4cd9a1db287f785b3e2da708ceb00537c5af3543bbddab6ba7ebedd92a96d7d55410077cc805b82b2c8e89be28ee0a3a6575956d1120794c250678bd9c7ad6b26cce3eb98d6259329649f5f21d3b6459dc148ba88fed426138ebaa29ad2dac822c1f0ea34d62b18f7e501401d046d1ebf039bcd0e3f037bc0dbfe46b781a7e86a7bd0c1fc3c3f04afe8577e15bf8a067e193fc0a0ff4485e854fe167b94c6e23b3d194c9e435b21a398d1c95c764a88c862b57bfe99b5ecaf7f0499ff4571ee99d3ef7b977fa9abb3b0e5f0c9ce3a5bcd3277d0f6f7b280fe5931eca3b7dd2277dee739fcb5f3ee9933ef752de09cf9add5f054f5df754c275cbc2d0867dcf28d886e99e54e0a9b30dcf3d6d58eea9dfe96d9f5b407c2b8fe57d78dc4bf1637eca4f8f1bf3d38ff92923f14d2ee4a7c76d31b3142c9359b675cfe3759f5e9ba4b4a734383fd00b5534483826f0fb2fbc99eedb4768582b87d1f7a24a69aa83d40b368811d0eddb9a331f7888d8f54c4920c7ed2d0668cac700fb6349f9aa757fac262aeda934a862cd99c394084d26100475c153cbe64c4efaaa3b768a356744339f09446116816fba12b62370186de280c3bc249189f219d98cec9473b98cec253765a64c461e238b91bbe4a5cc256fc9b8acf45e7252d692b3e4303218f98b8c94b1e42bd94aaef25f642fbe2977918ff22d53c95ce42db216398b7cc4374929a5943bbc686a1ec7982b8f79b462e6579d291f9301045cea3ce611478d1c2543d9015e181f52dd9e145261180fc380335fcebbb156e690709f5c9c0ff361fc042fe3b58256bc80a7f6aac1caedce095ec675784ef032bee39ecbf064aa436956f7322907ab2a13a9db77cf96df248fa8a84def15db9ff8e84e6c990e7618fd71f18ba22f36ba19183777b1c8c8aef6eb7aabdd331bc771f4eaabd52d8a9a4f4e4a2bc645a27dba352b86fa7c4057f4da15bd7645af5dd16b57f4da15bdf6cdaef889548c5e18d65a6b75ec21d178aceb84c2868486b6cf7d52548bb690c774c3dba6a213d88bfc898b7e67be84aaec942e7e55fcbbf947fbc66f09f50983f86e77fc6e77fc6e77fc6e770c6b58fc6861921bfaa252babb7ba538fac296e90a594b460c9025a30468cbbb44c9964cee1217b24f35e9e1d5b64f3db35a9d024a97ec8aef0496bd36f57a815c7cb9f87ab9f87ab9386bebce2c4bafaee8337cb40c3a0b78b6654ea3591c7d7df12fe7150b39c6f48def04ced9956c59b61ea46f9423701875a9b664a8cf97254b972eb99c983b76ad8f36de27378a5b44b50fd11e4ea8cf8d72c79b26fc8d975a2e3c539cd3e48feb43c55018a5b92dc5e4b64c4b2d469291b3459ffe8b0eb1e73eab9bedb51a98339d734a39e594795b0d6aa9b3ee7a38f67a620830d0f16cdbf251059e7977ac0982d05704b198cf1021ab159db256154f7d79d1b80a45670ce7d0e593f31278c210cb4db83b1378de0ea7cb9e672cf0bcd9454b28f149e0a945248e207262c44ddedff4453c1178ea213cb56c82a7fe21f0d4f285f820f0d40f84943fb28ad628b09500759f544ae964340285b2ed8f3cbbe3c23a28dacb83a20930f34d792a524a19aef4501735551b7937fe86a63466db437dd447fd91d091d4fe514eab3faa1d79ff477f6484a706bb7842bd284a034f2d75512a1d9a15cea9c1f1f1bc0e87cdcd8d0d8e1b3835397845a3a3da49f18419fc999e2f87a73bd66d591dd5e500d000a600364a42006cd0769efeae7771b5370b9893901d719fcd5c89bb144b4bd949d2a805974568688372a66a545609232a4b9096db6fc952fc162d352da316d153007c243ca5f698b6d4dd95dad6a12db53f1245c253cfaf5b92328bc7f26d79f359a5bc6dafcadbac553a6b76d6b6b7655e90fa58aab71c903ca8c6326bd5e7d30d9e4f98a96e3aee396b5e3d1998d6599a4bd3b4169ae3bcebb8015e8e7b65715c15bf0e815815636154d1007026fcb6b7ab4ecd1603f4b500b6b3ed3d18b8563bdb9e9cdfa7aa2d55eeca952b57ac58b1622587019f870c6409e053a194522a6ea9a5eeaed4b6fa53792ab20e0a7052aff676de37038629150daeb9617383032767a5b3c3937f86abcd32cf8ece2a07a727c7d7ec0d8783e3c6e6464d0e9d6b4eabc134aa9466f14cda7d4a85e07dda54cfb05a3bb46e7c9af9ba968e70ef00c255ef4cdd9a358fa6c3b5da528a9f988200677f62cede018425ce16e58f1e1c2d4c792e3e8b77fa293fe5738fc56bf1653c16dff44eefe58f3e29dbb0ce5cd19c3d6d18674f25ec79386ef6d4366cb3a70ddfd8d3866bf6b461bca70dd3ec69c3aa3db30da7f6b4e1704f1b06f7b4e1993d6df8dbd386bd3d6db8dbd386ef9e1abc09718438614eb80a75747654aeedac75677b6b0bf862e0a925a5944a6fbe80830ea074d1219ffbdc27b9b5d682f46d3e6aadb7c94ff167f8bcdce5ee49de8c37e3934039a9d7cbc54ff165c4a68724a594524a29a757153ef74d98f608424a2973957adf5df283474aa7ba23a5938e94755573a4bc16a7d312472793524ae9347df43236ef64efbd01ced48460586b18a7c2d40352cb9e1454291cee184c614513506ab0828914b964658a24be80b21205ccd218162a65b1bc22a85563db45896dadb55abf3cd6ca4f621ece96465d9874b80f5c5be6daf27a8dd1d77ccd25265c8c706aa1ceed9a3594d5a53276ad1eb83c3f51fd526f09b775b634ea8264096acb5c6e2be16645282b5af0470ff563b8bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbdbeab5d6ea135cad74adb5565b2bd296527f4afc971e243d487a90f420111fcc8379300fa647851e15c8d460b4e589d11625a3bd92917801fce48cb9206ed90a57db326f95b58b2788a7e51642673f9fc641de4f121f3f9e0ef27ebe6f8605c2f7e903f67769cd2d40e99c3ddc9b781a943d2d606984a36d99a7b471419b52a528361542673f9e963e83bc1f4f4bf9a389a765ae3b7959b2116457772d79efedc008f5b4cd9629217536132247a7b287940b502d9354169946a8aed56b4ba666cb5900a58bc886a087449db69d9ef2654db0a1651501b5d5e007e4dbae7f4296fc991276b50d5bf702a9d9ce53414a877a48aa3504d43b52c04464d4a725d33ef273e4876a4dc4d3200b08d4d861a1960e9b81a98189c8a86dc4f7d5f2474a033a2dc39e9e101c09e5cf0c4bb6da1485edba6e4f83924a293da5fa5a21d0ed69707af75e07a9a78d78fbb2889c3a40ad3d02a5fb1055a2ab2bab48952f2be46b4d09b39f089b7e43828888f8d81d18f1ede9d73ef2f36950a680b8fe58409f4e4244c4c736e206dc7b646f190a9e92c0664a29a95579ad10e8ee9c82136aeda80d2b510874775df5d6140225106b82dd74ea395bac22bac8b51648dd7742994f4f40f3697ea9cd1f8268993c7a2c58d2da2a32f7947a4ae0d1232cf50924c00c8002e0d85575d8d66575dacbf1f5cc3c98439ed44e8ed3a82490f397d411060080220023170000100c0c894462490ac349168aed0314800d4c6c2e6e423e2a8d04229138300e8502a1501084000c8300088241180392248773d4d4019d1820bc7f92341bec6ed4e468eb0cbc22fac7144473179cef102c1b346a6b4963df0e49e9877cf5a4e5046ed62ec75442ae30d98ef32cf3dbb444bff1207aa94fdba0aced08fa6e76654e73bb4e531288a50840999db2c70ccd6c69efea8bf1b8a391fe892c35d34d0c8380a7b05ee6f8c138698efd77647ae375e6988c5614c68b3b88d5509a22e6e029017bb0d0913956f14ecfce28dc8b3fd5ee9cc9090c1de38a51df355431d1e1665a28d3266248d32c27884deb3745e8f809504d4b1c48f48e81ce0117a2c29b7d9bf768a63a31b6e47fcb82e11c5ec34c760e4f670e019e3700a8f39c3a514a4e99a465e4b4c8d20653f071029f94d22668a21de6be6c354ace4117fa5b84014e432df058f6dfa78fa4b733a3636f2d6d7beaa9901dbd6e54bddf2ec4c61bd39e5728f6021eaa4ceda39f605409d370e51fd5cae917f5e291da63cd72430c967658fad322119636567d63252646c38957041ef68c1f0c32b78026c8ea8cc26ed4182c9535b39a1c2ea6dc951efd3a065bc0477f063c18d6f03b86ec5fca66c90f0aaf6d49c22e96b49ecc520ebecd02b961b2ee8284877da5c5da130eeeea9d4c347ddbfb98d4f4e96c497f1cc8cbf4293d615d3292af07a4a82b07edfd6ffaf97fd75c3311914249e4d03670682c235c2b8ff39e8657ba50c324075758ec9221d1638c3500ff87997e20b9eee3945500dac0f88a6d4de3bca6b470afcef9f989081d80c4b4bf864898e17dc9e952b9081ee47dc9e952b9080e8cf601721a057a100f08ba370f9141c93f90d4a570912dc8ff90d4a570911decf6a7d2b588f72a44a49ea48152bf084ee465e48052bf0826ca9451a3d4f14424a652cf8840e467248152bec826b2392315d0cd8a37e1e46912c3a68a23e190a1a10644cf3b92d3a7f2111c68fbad8ab4d9bc244ed19dc0ed4cc48c130dea4d8a9038d76fce8e167e3d9c86b7b346b60309d88d67becd4431f423e101c9895b33fcbb4df58ee7a28c133e2806024b5a81d8d37553a84d9c2c2ae0e6de05ad1e580e8801fd020f57652f0ca48ca30523f18ddd0d94aeb5c601e460986aa2fadff45ec22a39a4124f43aef86904fe805d2742397b0919908cb49ec8c362b8544d8a8697ceede6fd02de87c5c677e3223e04f8c103d1862d9b61fb858e696db89ee8b969feebd80ad9b0431ab67d78fea541251d2f76cdc0c40ffc2c60338c8a4a8a0a792d612fc4adc7848eceb6f71d3e9f0d33c8e893b228695811f9245d8c38700f6037e2c6cbe49ad06024627aa0de19fc791a7f720fadb7311167d62e1a3721aacc385b84567b2f474c7e1cb5c98eb4a0dab6e67381566302745e9048ca81b3b28092b52e25f4011a6e8bde09288073d210930791cfc1af579ff2136776fcc3b6e7d0706d1fbfa933ed708cf27e5cf9ad7d610b8c9be9913da8d5795550220c244e1ff117e42377c8c2c505e30383b629212c494f4b129347e82c78fcef506d591e4675d0d22490c6c38d388fb861e4870903ac220296aa674f7bd92f39b6b520b76e45d2830d8282ed636bb9f9f1b51a11d3fb9b458636bf604c36f910e3fa427c0eebf2cc352e9fb700c92470439559803c79f7df59752bc1d199c678ea65a2c66ab916b9c50acc605d0264bf70c8e58dc7071c0f1d386a84384e6d72e59a6dce907f1bfb32cf4bc503c18fedb29f7a7eab1b706d1781ce0754ecda84e9b5aaebbf56621d4040171538516f8e4400bd179eddbf93c4149e93cab61ed0d557164b21dc19e5b1b2bb87c2c8277b22b5656b7e4ad1b9a4e5852cc9ba669358a556a1b05984bcbd9eb3514815f5641bd857bd027b6bad268f20b2d012371ae85a14f6b7d445bc635ee408b0416452de46a9da6774c7e195d8b8310a56fd786c55b2cf8d5de8763444b500ef608b6fe5f80563d5ffbdf28eea32ac69a45e114775a900618346013055ac024a9f5fd001e35b4f0b042482370f855e50425be08d8ba4727a70ed787a2126108ccdf056773db64cfc4227c84d0fdc5dd916e7dc6d23a17b16e8559cbc04a074e39551879f15c3e244625812790b8e1578c118424966e3c72eab688ad3f3852933616ed2b0ae7b733eb9e91e6c8b38990dd966739540dbeb3d3ba2baed3e8c0946657d708a4e65fca8222c6f73bdccf02d89948fdcc8ab8be4f25721c3a910030940c20b8037b8a73375fc1c11cffd264b7e7ad96c7530697f65a3fca57eeaf0f09725aee017268dca6116988322011d38b21da810d166085e1c7179fa78cb767ebe743f53d29b684eac50cfc52234e56de0ddd8d25ab6531ae927103946b46303208f4fb26044d1a31e5846edee4d1ccf3d39073a4907f6c7c0643bef169e33c95b33bebf28a49603a2d605c185ba70a24d9163e12a397b5164ecb0cacfb415081b26259e03843ab80556a03ce30fc4e8e53de5843dcc93ca476b06df33af0a4287aaf4ec8b2263212a39f7a250c8908a4eb45e142254e513ad0b1d0977d1498b226384557ea68d2fd003fcfe6060964c55f3bca3e462fea0ea46d7bfa4bba878ddc5834f3591e9eed1991dddeaf465bc722f3bb297aab34eae834a5361968a74df0055c280580e7b0a2ddad25f43414dd80cd141ad9217d0d8f9319dbb8baa549e55fae7dde6f9b22321d0810fea73b26ae2ee743c6efadc4ab38fd28d07c9e41f3b25ab2d5a737358b0d076c9d7a1eda36fa25bbb6126ce117e845b728c631fbd02878c16e5b63eb37d247b66163a37ad4286b59bd04cff93a0e06ceafc30bcf04773df482875c195ac8004cf5f733cd57755f8fdef4246183458daba369e74d70d898dda81893222c472bb007b54971bca53e907392aeda72c65def250033fc67b020a1c295e950ed748d0b1cb807a108d30ed42570bf1c8f040b4eb8e3b5a4637f864cbd0a8517b8f5b09b34f54a2efcac01f34670f460ff242b6ddc5624f8c8b7d80acad18597896c6454e5233e424e21807b47470b674b96d8ad1045e05361ba256e3190abf8edfdcb1e8076ee2e654d838f5e342275f33c18f8bb881f2ec0b957bf775d7a37f0b338cdd4ad07b7f53203b47bdb9386c04986c9b40a12d1324ac8c1b0b1ac5bc37eaec188f5744d9ed50f9179fef65b645abd05be8842f67a37e867f83be94619ee0f549a93124def038509e31b246248f2ae4e632a52ac306dd5633576190aaa4680b210b1116059c3ab078a7abb2ea9f91c063db22bfb0e110b790a478a9f0be585a6a8dd150b9d65a597d7da8d6529c99e4df2f1bdf578d80c06264d1f442ee0b07ce7ec2daf6732560f48d884bab129e2aa0514e33740935ac9c46655e5eccf2b4bd67a17b46254f62b4434430148debe518764fdd5765946c1be97293ea8f75683ee875cc6337178753e74fd9dfaec5d1250b51e890a1ac629f6ae771fcbc27e77e4445a33339cca89495059d0b61c3cc3793da7a36ec3f704e8f7824885127d3a2a2df7ab4094b8a14662e909226ee4f2909019e91b867a48f4052ece88dbd679d89098d25d9ac2626d4e9248448f3e9d1aa330c19f72d7cc39d53a983d08fec86037057d6471bb74026b6fdd4054385b6f228a3039df025139c8bd347c93de3d3e312e1448debf45891ba81470e6db6a7c8b1183d56456078ed988cd1b2fd0cd9afcec73a538f75faa019ba2272ae9c2fcadb7a383137e2e4d43bf00e165319d16fdf596cba210eef970d5dd4caaac2371c128c0f56d9b537866edd806c8d9f90d5cc82d99286214f4d9246c1d8b435490eea709613c8d94491a6005cb70a813c1a289cdc3ed170dfd2fcd2340293a58f5474b25d479a1b5737b46eedef0fc49bc6b3fdfcd7a0b1ef9c89d8cc6faa0fa17191408fdeecf0e455ab8869f49fa3fe0beef772f2fb29523baf4e06998d2b51557e6e3a57cf4a0ee4bec736798d162f983b404ca6fa6d6a6f105e6cbed68acdb3e9e6f867c57deb8633f19b4490ed88382d597bd70f43c766ceb4f4f7dcd1cbbfdf827961c104b68751e5a921570a1b1077a45b3ca5ae03e5ec5cc754d55c75f11f8fffb3b00d50aa8a55df096eefd2d1d0e694c22c599f3bf7ca90992d02787623cdc79e6724ba30b260ebb7fbaa3d15fbe7a64959db086719ab9647d5e56c6f51a1e36851ec46cdb29b07203e2148a95a3565af363332568600a7f8522ab84083713700cb8b72ca23360b73d841c802af089ae19186ccff07e8d626878d063e2f6f239529391eba7e5ebd76a3be5522d2a91d048d01fa4d1be9c998585da96b057b3708808d423713adfae7545fd98e683c9eba592a7c70ee83e01dc8e738b6301422ca432ae6416a0019f76da99232cc8df8732d94323295fb1f189d71bdd8f51a11f3072589f7dd4b88b5cb2bf954b618fac34985971f727b1dc9ed84b7aa427d95179b69e5294a8d13d94efb80acbfb94bca740c682bc6077dc63994ef7bc8abcf8676cd4cbd6865d31a03c90415b3a97c670e5f496f3f7d61ac72f91bb1bdde018441a16e6ce806a953e3525bd86e1001d18878850a2574dacab16ca3cdfe3a2c7b71e8171f72a4aaa4106dbd68cbb6ea959a22b033fa7ecab71e5e5a301c1ebe46d77df5b52af3c0d0f4130963d73c858d7db7a2ad1e8954a2a64e7e05506c914a70059675a8f5ac6c4c77c6a9106c567a6d4965eb7ca1b1c3819dfd17ae99a7006a150f6484573ae86ed39f1ebce0c7b581071faa46327ca88d829e6b7cd48e63a4173667fdcae0418b666c322d00b0a53d1b50b44c55c0507ef1f2020e1d79aab28d2a67d6a371848205d8f16e07e4fcfe066b9778c650eafe419f54ed66f5f1e85f34ff52c10c23a18b9a13c7d52dca76dcd1a91fcf3c93ba411110378370da21e13eca4689d984f0799eb13ac3550c1ca06e3c9fc1c1d9aa4740e6f5f3c27c7f4c52dc0628ac7738dea59f0d05f2614514cb5c1d13262244c15ca9da425c5ebe8846e479752443ad1c6d40c914baaa5cbde917a0d415eed9c2812955d55705ea698b04c37751ddc0404f77ff317c05dc7255374960a3f53dd18d59df9f5c79f60baefb95f0addc3a50adbae166b649c2b6107d4df808514d51b8248b55af7950dad54caa6a9ea13f14c8ad504ef467476a701cb33e4d21c4acbce840b2bb5259a09d8d9e1cf8369d44c772d109ad921c516e15c66ec46ea7d44e5d48bf539f5920d76c875ab86bf0f5bdad30f518509a0021fdda052fc2cbf2aa30093d39d39c5b998706bea9b870d887127c976dda05f6b53a6ebcb1c3a5dfb5fd26cbeb3bdace7764524be44d0948af537986a4159c23877f89a8c602f70b3e51519a4fe1dfc9e8281bce4400e41fbc90541e423d4262f7a9e0bc7b5a56ab6d502fb9bb764b03eaae3454827791fb842cee332bd4468ae07198da0850577dde6ab8ebc523751c9007b853990a74a7c0704aa111eff46c5e3924122a6a999f90c25e75c9857c33af1407bb87506357eef2e64a691b7d53df2b379f71318fd1d3ec3c674c27f55486328cd84f44e0c158b059f6130121fd8db2b0734db1aaa8e11fede24a947d049e9557c28696352dda02210643b4238d8ad033da3646abf52a7578dc081c7995467d28e7b81aa641976acdbf4d11172764190e18a2f68f6fe6cdc75c9a1985dbb784533efb5646a203f38906491dd7c700b7359fa4b3bc5252a61e146c6524d547be98f814ad41859a8f91873b093ed0c768ca83127497d907b06dc0d9c3f3fdb1a09d0ab68f18353459073c0c295a97434f7fcc6d979eb3c475200055dcba815fd711ecec47705ebeb225aa54129baf884cf6951220fd4575f86442a80a9fe36b33bb8052fea509adc6e61ce52468fe6390ecca478aa4ad6ececa92e32f1d9a9c59c5b0afa5bc44d73b784cc0b3552b99994a66f9ee9ebc370bb44507e20664f26cd521bf78b5d536e0c0bff2bb8ab1f10236a44e839ca00d4ae6ea3e3f9cb824ce1168dbc0d034fe634b2ac859f5f0d8692c539d1607a9cbb8131bb7e8930c4570d597081cddce2843ccaf08ad325d523887fbc750e47826eab8c928b6638649511aa37d6b952ece5bd40f1e9807816aeb5f51cb25e18c8b5bc826b6cc0bec5ac6f7ae4462e28504ccbc52041b33f8f49519b942ce4a1133d395e0e5e07b2b5297633c5576d415e56222d19497e5e0f4a5c7d4e4f285d9a4de36288ed251349345b869a8de895cfb726bd6f5c5e414256fd967502f10ba35cb19f910ff4b9baaf1fdb6f801c29433e06b50a64b54468897754442c4ae0d93b9a6ccf81a2b61052fd0a34838538de31a0180ab1095a009e1be8fef5f025e6598dfc98c6c5351d0708d2d7a1816889cce5a42ea4b6e0bc54ca6d81f40363f23c8d666143d799e37538c047c816b15751c727356460f9b1c98efed73afaf9a08ebec32e020c3b1e0445bfb3eaf23ff5ded1d1161c81e7350eb8741e2b18688667b9c21ecbd748b125ae3abddc9fc341ecba33176201a42d4f8d1a82d3e9c8adc6126e154df472e20c8a48c20c34b2cf04b321530483c14043df609d6428292a233b8c76d11487278225ea80c015acae14184a53983af4583ce084068f889193c728ab9626972234c7a85d490814621fe901f3325a0354f48d2d6df38499b4f43638b753f0261c7302e5e5a14f2866258fda12200f13bebb09a19cc1710d19f5f5e5a8ddd27dbb16c869f7988c9c387fd97178643992f72cc498480a33c6f1b7997e986046773cdb5b2fe43cd808a97967172dccffd69e2e82cbd1b66ff591bb3de9c56e4837e1aef6f94a39ff9ed0b26690458295fefc71369eb1512ba338d09391636b4ab7623c6821f35f3fd9ec576e7b12adb8003ccfa6505e1918ccd84d11a007512dcf15bd23b6808cfbad7942deb278578902ecf1acd89e808dff330fd405a8e24f45853b7b4817abe48cc7fe4f1440ecc3e5d87189b905837f901bfb9ad6606198f9ab4428e0f54c4cb33a59307a6a0f0f70ae0a0e01477eaba96ba2fde62b655cb5fd1362041910964a2e3ac87fe7921367d385fe716ab49f3dfaf242b2c265ffc9b0289c3ee9c2b6b6a9d6b7343190caf6c7a2d3c8aa5d6f5cb186195414f889362e396f032a653fa771503444c66f7fca1fd6021b1729365dc1e27591ab0a7c58ddd7a009011bac8397e1567c6683f82a60c5979f69916c4c28be511453b940e88c44643e88a6caabc6725103f54389ecdbec3c3aa63b8c4a4394057c5391c180e18de80201bb0213129dcf3d2e5051fbb46e8969282418c36846564c9c85080c80aedecc4ba8944821efd0610a92c4678b12421a938709354fb6451a58f1dd3951080ec7224814125e6dee995e7bc6dd20470a24b10188d7e03adc70974f2ba455fb57ba87dfd22efc080e4e5f155297e89f03bdf639e024416b210de5f90e04ff0664dbf2221c68c95f8e7eae3042f1625338228f9d83a3fff56945a0dc4b6c207546d236aed277143a78e3d9da9a5ffd981bb410355ca2d2bca7962c55ef9951bf784fc86cf8174e79f2005bbb60b59876cad729b7cec4540da77a9c5b5277b1e38bc34c67ccf8670e1e3c52d7c7201caa28fb54ccda360c97a4c4798d3e95bcc70b13f2788bbbe5fa33b25499636ae98c87445a7d017492309dd66c13d4ed765133671d5cc432fbba7e95a51aeb5e8b4df5a8a01b787f2b6698280a14f58a82ee785589ee180a05f17228d6f6568012c5992cdc309f503571c934b6003c1878b99c9b1825727a2cb5313e8b355fc1391171975e3a47faab073d45a4cd9e6fd44f8404df5730942954270c2c35bd5d0c50cf6f8c5531019bb5111e6b23866164dc872a3b068e3e210f7d6396d4eb1f0e09ae38a529be6f0ad5cf5cf28a16d110335dc1f88b2dd04c5f302c235436f6bbed544acd989b0b11de2317a112e89f82ca8993a52651fa601ff57f8243d9820f296e82174e64312171ecc0725726b6a1adb532a51f4d05da61a150c1294ba076fe9f93af07775621d96d4c214d5a0cc5f0094f048e7175286f155a2faa8e7eae2ac7dbface32fe4d9f65c8a97e59b364cb02980151ae2e3a4235a189e72ee25f0caa941fe169d370243e961ba66484d7dc6c98c10db039f3a4fdc4ceaaba9fb0ee4174b794f6b8039e730db1c3a1dc2ed7d740c54eeb65825ce528953abc7bc96ce7884e4d5f81f7f7ed51c8dc4fc5ae1f41eaa45b9117998533aada565516d7e33c48d14c68b7399dd2147623b1de6306bb88e4e81ad7f3756848aad3488bd68ee2efe62c7417d5c9d6d6b83c2938c3daa8c0f931716ac2af32f280caa26331cc0e59963460128143b06a88fb10fff9ae4008618f38fc696d90014949f5b0d833ea05c1d8f9f142ec6d5a4346523b5e81c47d9f709c6460cec452740ead057efef15acbddabd6697a00a3e58c688f805ab0194b21a037a7e691e8d54700a9d237303fbd4bc313da7cf72a5a340bc86a0e8e8986bc591da69c2914fddf6d31c6e5740389c8f42e92bf7b77e6528c68b585116c61044cddbe90469bb0491a1e98472aa992b40f3e47da4130ecf588f17ffcf53f5e814d02c179d1cee009bdf6c26703927919b97a30bdefd1dd03620509ca6f19dda0f9993a2a3b5aa0041923c4c0ca2ddee43fcf475db921adb9591d9383458134f35e7375fc8707111854b4478c565d4f1a2b56adf818df04db5bb8fe73a8a23c54d6fb07487b86138894fc8c3aeaade0d5256bfa498bda0d80e99b7f133c3202411855f9271b6d20bde5439c61dc2570e7ae2533d50fe8f0b57d7271c05034012d735e00a30379fe5d476a4d7f4948b2351d6c02e50cc45d4b5566d66b7b7d9157fd582d99fb0af30556d0d40a000502752a42cc82f7a9bd8baff369c13f467e1c252a184d8eeddca334304656e41b33aaf105b61abd679c5bb458c53fe912c749130c5b4aa47ab9e1ea5f6aea6ee201a71e0b845a8da0a619c51e1cc3733ae625a756bd922d9c5950b1cb12374832192774b3432bd204bd1b6d41c21c5066d2b395224ad0aff6d899ba6b7aa031ef6a6a98536f22f2a9b9e3fec98e0272431db0e40db02a5f0a4250cea9b04aff057032e44327503ec23fd652c6a13b6647f96fbc76ead66b53b1fc71f8fa04197663eb00c59b72fccb09421b819adaaf86ed259401ff3ebd3c789cf70b710d800649a00e9160a3b0f1732e3449f394e1af76dc4e67fd5ecb27e3a96c2ca94516324e118b85bb1f29ca4acd3f0cc4646dfb08b7543ee769382c879e8e05f91bb1d32272c209da0f4c665a0ff13c5ddaeb956a6a7a44ac95544e04549369468a9c7491689d3b75b983fe8a3014383de67e71e31ec4aab86ac24840c25e2f135080349454f58541f389bff6c5e1a582a8d3558408fb9e990118d66687827b813d90fdda00a22ec3cedcdc2670b4f446e2302f8f957abc132b098515e9d601f0ebd2e011e1292851c1f272fcb4e05ddb019a231fdd6ee567e16ec64f601aeb6aa5a05dcd8c5dc342a0a16b2a170697ce83a664f4d8b312d1d9059ea98f72c3df5b05589e794a7500eff356a494c5d2036ef477168da76593d2e7cc6af603d9792e583d7468cb4983005366205fd498a8147329af01493f6cdbb02c76142e64f24c088296ce312ee6883301c9318c7f764b2c47ac283f0069ad24ab854e54834597546088beef24e73fc3d5041cd5215443e4825c0739732504c270c2df9cfe15d271880ed4b51e272534fb04d8680553e56f889f62f2b525a44f125ec42b232b840b7601e2ad7fb55deb52dadb2336c3318fca8c37a1d10c13bea8a90597bd9e2c8d805680daa9aa979fdaf8956a88e640807aec09c158ee68f4335a8d15575ddf9dc0b5982c1551e85f187643c81f070d737cdfe782e4e49f260af4b57494bc69a8fed6f0d43df62f335c1971ad747a5b301b84ce96c13a91f0f4f36c04028008d4d30f64e1fbc389082f74f11bf6a2b1dfad3eab21f978e9b2552dd6884a53aea66b40c3326dd5ccbce499f834d3150ac35428a4a80e08201fa9a36e90d14d3d012d33456ae98712183dddd30174d563139ae5838cf5a5ac45f42284ccbdd96557f862d67c1e93c4decef0b23595d3944613b5e412186265dccd398af5c44ebe47d2539b635a3d08ebd0175ec11da186553bddcc6895457f241e804df37dffc06e45881d30842d6d2b485949f762ec5eff79b2a6badd88a267c90007da30635958661d1717296901d8e45354ec6551baa43c69bd148fae2caa5484c9c80968354ae50427d147c178923958442c387f889cabf9605e7f929716f622ed44df41b65263a279df7c9ec8f82a98332d30b1c0a9012877e0c7cdccdce2a8c7c85537f561b7c26f03b5916250a05d9047cfbe38c9f6fd609f7fda275c29bdbe7a673e6d4000c88b8a2a8f2fb0093aec388e756a62df94de7fb1734bd1d9aa9e75c7989013761df397c199c3e597bf2f9e3d9c3ba19cd0cf96c249c745ebc991ee8d7a7136a97cbb4947039f2a84775132fe637cf80e074afd34bc6d35b782fc7c140cf932ce0e1dce74ccf53edbd582dacf14036d0bf65a33516aee24726ffee926bf0259886afd00220bc1ae529855a376b4868d358b9067d9c4b198b5bac00cabe5fbaf3bccdb3986910be7636b358f4d1ba0feb01506f7da65f56f1ac5582ae5deaae564c10b0b8db8e001c5375884a42fa72f13b11724781632cfa09008400fb4e92f8b04181a4c65502c635a0b024a030b0ac18ebb8d50a9d06e30af50e417227e27f4badfc97d4fc647ba4c2f0a56345d1f84ee42a9c27db211ba6a5e01c26e119be4b29c293b299f6b047c0de4a89defbae2d2be11338c10d620ae5bec05cd87084dc469a6f667d223612f13c6c595bc7f99bc706d1c2c4a2049e1f6b41e6e8e0f3c5f04891cd4d6ce791a2ed8112457e640e66d49afe1da3e2ea92e385dd901688304bd58c73cc13f203c1cd4b11330d246a1258bcf8150586d185f99b32dcf51032cc479700dcc9c9043b934551baabd13c90a20656d4194924a6cd3d17f5d3d9f468afe61bbe100cc0598369580ea261194f8c573b8f225bb86028bc01c495128433b18ed3280bedbe74ad158b791df6f7da17418a4a949c782b153e057160cca019f9cead0754387f95cc9e5eb660691f0814c2f68de3e313fe99abd7e6399529b5e9c6742344a79013a22bf853587cdd3c298f23ac4ec4082875a9822401302595bdba98d002078ffd9c74b8c5ff898fadf85b27f37b1d7d341a6507c5de39a88b43644ad777945754ee5d220e8e4ddb24f3c2badb480a1054c013db800f052788ab90986fce4f564419246b070c668fdee3411c25a6cc48d640cac702eaed6c042bad74d97903a5b51d482b4ac2bd52ad7797a11b6d5f368ea50eacb07a3255633a6aefa1435acc1d900a28f23ca2aee3593cc0902687a65f384fa5ffd8be9ed04dee65a11d344b3f70c4291ca9c2cca59e2a8702bef49e23c7788fcd5570c550ec168522d919217e7dad8819b7449ad622cbd528af7e69fc2ead320b04800382dff1e24b908d9689c6fa265057a5d6e79a6306253dcbba3b08e84d56e71910d5d51876330854742420edea075d98643d7a8097a92fa751488a265c9d5094e9e27bf1fa87f5efe028a2329615424174cc8ce365c995fe122c84102f8250ccfdd6bfafb7c1c2e9d2162c697cd73de0bbcdbf25844a3d04c565827c7a9e2923a85c2b81fe1316030002abb831cf812a84ba954f694e2cc2927830e998353a2471e3b9c1759459f3bc48a3bdaa765d58a1f0d7336d1f8cd0a0d3c0c8fc03f92260e001c90129f67c1401b09a997ad410abb272014b297a48010f38dfedb5a739548057fa495a471e1d092812f541357290397531bfcdbc45f2bb8710f42a91ce61c557e91a80bf1c2fe65023f9777e03b761a746734ff1d356e162b40cb8a4b0a3a7cd64640b084398b9de83011ebb0b6e96d05e0a091f0b5c301c0ec6bc0ccddd78e0773a6dc9cc6f60603fc1f900f647a78e22810bfacdf901959288230fe828d7b146a25dc3a109b7a1c855d50cb3566c09d31ececb440708ea404c994d9aa45709aa5c7e1acd161d89ca39c91f601a80ae49aca95b8ce623512430222d2e9033ced9a0256c2c09442bb0231cf6368978d890c79a8a2579188d7e0d20f3891a6094a46d8533368d4a82d4e4cc02fad931645d408a59361db0a263280ba7b6540baff216efa5a6f3a30807de336ba040127337f02e1b853335633ac60e8a512bbbff18acac3b5c37d51f40736cdbff6db81503c087c2c69a4b38565c5bd99b05c7d3602822126a6477db724b29a594324606ba05510662a05075f3c4b54b3689c93239c19d80abc29d809b5242b517675c126e15e29cf3ae8f3381d3123784d3f2a3991a742bbc7e4eb229913d3540214cd4f5fbcaa35edfcca0871f86b81fffc4ef6b60bf5affceaa48c13a58414c825703f54b9a9b34b90dc3300c3fc4781c00152054fe896b7087c25e5686e9b2346957bfb634695991c04480b03bfba70676671f8742982c46087ece3b14c31275db9da6ba34658f4f3cce15638b71a5f9b15873117c522b5eef7af029323831f6b73bac35d5e013cddbd5b6697a1a2703b71f5a1a3600179c5e75b326e19af0fd269799e93213b88b5fa6571b7010a34958cf950cdf9224076858cf5a091f638cf10933a8e34a7280d7c31224eb10efe2aeb8f8e2e6f86934d74a5a49d9d3487317d47369029b20ba2ce4e591ee01299090e932a55f46e28db342aa4391bb910ec55b07c3b024b2bc1e7e0a9a410f4ff430b43bf069fe3e2351e46e941fef306ffb7de2ec392e0837818082848e78bdf65bb6c96d77e1d34871b43f61b89fe89a1b97a2a5595c026eea681c001520558e46dc146eef5bfdbadc78caad2c904037c5df75d2b5fe4f97e62d8a5e5b8a29f2c2ad8316575a4e888475098306478cd24ff6928014d58d1d4fd8f0d88a7212a58b0c1a232db60809f122ca09170a51aeb48471c116060b4f579494215036bc62c850438402634400e1479616624a0803650b7324a7e718a2eca8a46648520b213300098d8c39f2420d5b5d9e7c8d85a1b1f3254a009d627d5de9399d627d5d8d4b1ec7630802f4c7720aa050aaebe708b6ddf15c721dd452f1527d15b9476a5f3cc0b0f95ac115f585e3c3218c1ac1e01082a1a1df748a05e6fcfab4feae3f423921755d412104a5215ea817196a90a185075ab08cf1fd8108049828b1c8bd6096004002dd32febdf9de108633a3df177720eed5e5bd608ef47bc158f57befbd392a076d9d90b9eb1607baa54cb7bf75405758bcae3cc01fa69bdce0e32dd4ebebacb3e07fe09bd8087cb0b46306c512dbc73344800b13a6242c6656c8d8a235e9d102142952aec068a08c182e2af4bebe3411c95d96386181218407151c388ae85312583774617999a95fe6e2c53470e0a2238727685c442932c71c15153118585a78b2a528db10f5e51abac9f3204a8948982c3cc08ace103c5db171b2e5d4c3ab899713af2441d545155c5395fb658e5be2b8532caf10745da4638c31d638b0cef86bc6e245a4dbc7f20a72bf64ad1ed6778218ccd070440b9424438accb17791e0f5d4f3ee790593134a08a2e584258433451f72dc2e1db419e3f8187f59c3e39544b49544a9298411c26008d5805a71654312293abcbabc0420735142448798172914a858116e3ce98232616bab0a4e94714b8e1d292f8034d1e89185890a241d3868e4f0a42442992f58b52e156eb296a0dd65c21eebed2f493afede7e5f56e818638c77d01d22bab7fc6204c607a25c7ea1728163ef9d4eb1be78f0a5452fd380c59584e5a5e98877e949bfb13fe997ab855bce92df0cca4fb5f4fb78df7b332ec72575f5965c4ebab563a7585c413a4ea7585c4e3d29a43a5db5d61a07d77aef764f31a8194245cd12225a49890c295b8044d1b155a49f6e2c5d6b9d4b119783d38e7b8f14b7cda5b28017171a70fc176bcb4cb79689959292254fe8531d256fce546bfa7d545f7d5f139192b5538a5f246dd60ac254b58f0ddc04ea95005402da2b26753d26706c77660ff5e226ef22a5b5d4f57c0f198a6956b51eb092597d20bb322bfb815eb73e48b8d9c3f1d554dd3e89ab4c878f0a3e29f888f109e353e553e5f3e593c407cc078c0fd89eddebe3c557f029e3d3a6de7befbdf7de7befbd63dcbc4e216cadb55a1f3357bc4edbf6ac8751a4da537cc4f89491f5424a29a594524a29a594524a29a594524a29a5949234289a904ebf3605d5e973d2b4463a7d1bd50dbeb9b9b6de581f76473fcccc9a3033d68545023bafa09c57537a56d5a9a9f2a84eb5a942551fbd32d9adaf4eff76d12d33601808f16a3785e8555058adbd17e35cedbd18e7ac71d6fafbf606655826bbb6cabe0d823259188ab3d9b575260b457136a3d16a335aadc6b9cd7683837170aead383738b75b4e8e8ecece88c7f1da3ada6e70706eb79c1c9d1dbcb3736dddc9a91fc218bf3d27ce39e78b31bef862b07783c0c0af152d771162eba244a8b6953fe8f1bac9eb9a7f9bd2202b4dfd3d39d72d28e196954bb38c5b2accb64e4cbd8c1f0f801f2330b2811fda48da8324af6fcbd26d2f42b995a9ca227214b91e3e7f3d9bc0cddb6c0f92e6d84110acdd6eb79b8ececd83b4bf791b6992e8b6d7b308806feedb9bfc763b1a8fc6dbed76dbd54a93c4ee569a24fa0dd1b5e9fc0d69de3cad1c800b6e7b3e019db795463a5f2b8dc4b71d8d4763d7991d8d5d1cc5d90e00c671676767e7b673cbc9c9c9c1d18065c60c969913ab8c901a698ebdf637d224d16fb7ce7fb4d99e3fcf535b9adcee744a1c92e775746ca599f3b7c7791e1c5e9a620e691e8dfde6c5a7ddc6dbedf9df663f0a91186b3d702d70135713f769c913829ac0bd529aeda7bbbd38eb3177bb5013712c7053a4d9d9157198651abc590fb7bcf17ac3dd1439ee19b03b91f384b85fe2b2b84c0b4e08b724cd17a03d63c59f6dad96841c06d673b628be6e649f05daebb5d66a06751125adb5d65a6fb64fc3be7aa3703370fb9a2c9ff0ef4dbe69efd3a5482b19bec513d88ef9beefe3f1783a0aecce9afbc97fe2e183cf49d1ee6a95f6d56a8ee62edcdf751847f87d1fe500edea46dfefff489376f5ef432bd22edaeca592be2807a80709d5382995f3361cf29fb86ce7ed07dd219dbfbd0de7addd7d6fff86c43d76f7edbedf2177f6e77b1df286a45db60bba4337dbe7bc8d1c89eceefb1af9c1eebea7cd4891bc0003dae1fb1c72db9f0fc4f7b6cb3ede8dcd59f0fe28ed9fb8ecc1a791d8eeea177e2f86bc8f3d9491e63edaa5f83607a5f4ea2539afa0945e9db310100478bc5da308afdfb350787677f5429d52f55ee8ed1342faf404eee67eb3ac65c813a2e29edfb46fd6bfafa287254f08f1c30f4b13f7f088d7c557d1c33d9bfd9e95a018ea50872525cdefb70f84dd85187fb03b9a6bb7165b4bdaae6256b118a5988876a0b79c33bf406775a4eb398b1b638c69883b79fd6c0201d9dda8690092e2f5f18f8f6915bdc2a2571de342b0b3204025a8640adaa19a42145c607f2a2fec0bcc0c092121d5ddec10102e0a37753dd6437e59893b9d81b2f248ec1bef4808e901f8ec71af15b043d5eafe804375c9fe804f9740d86808d61d1d7c73a853276a00f0cd04c868352110cc2008fe3bd1d9cc8260ad2008667097486a153885a4825848a5fa3e9b26ff897f8f03c29dac07cbc396e067b28620885e68b30f860fce6618d89d7d1bc9e9590b1abbec8876b056f6077c107c5c0c20f820f818800f822078e2ebb3cf0f7ebdb21e660f3e686e502c8f686f867b26290882571d7c10046b4fb685884a9001ed50dfd6847a82ba8da6c9daede66d592985f37a78adee013d48c874c9ea22ecaed6767f7faa2c68f64fdcdefcfdddfd1bd978877838184084bfcd23dad565bf29e085e215657f657f81ddd5bf35fbcd08e901b8f81848eba7e18f3f848b232e782a6ed2adaeff9d9c9058fd15ca88f5604b4dbb90520aff08fd7b739397d48a7aff13077187ead2eeb89e1e710106d6c39476f2611ee92a9627b08e76b06f7155b8d6afb343bb483f90e5d921ba755d8ffaf91dc2d58a5e4d290d42efbdf7664d73ce3ca46ff5678c31c6a62e975f8568074a9172ce99524a714a78fd295128256fad63980c14325038b82093a5e3408543530faf98a1b08fcbc3765d9696c9ee3e7dc2561c5d9ab4ffcc6d7badf5be43387f67cf1f87bc21a9fde1a5ae81b87fe6aca4f571ff4a23b1ac54b983a591fdef6d69543ff75c5ecb24bbfb2863125d7f88b19b243a7e22fbe9920323c6649ac09c3aaeaf2dfe70369b801593d59088fb73fffb2a55afd548aff85baa6199926c8d24bb44993e9d499cbf52c41d0a4b5de252e0e6de370c53f4ef3159d6a806c09fbfc4ed39d32c9fc234e7d0b3a71fd607492babc03ec8006a2dedb1ce7ac06535f98cbcd99faaff96f4be6eec58939b946152f696a45b3756d1e4b7be8155115a3c06a7427d5b0377858a9b2f169685736026cc849dbe7aefbd39056ecabef7e2a678c0f8daef02dd11fcbae2a8e0a6e08d337202779cbf7edf87f5c558286c824b4a29a598528cf1d30e144785d7c77705dc145c2b16ad8c315d58604958013282f4f8ce98e80308288e3b3d6548757394f104e49411820b1d104c767a2a8d755aa7a7125837837a5e4b575a0d04b650e9b86105275a7c88b1ff63bafdeff45482a1cb84a0c83c5e90bd7956542888772e193967aca0e283ad2d1a96aca055e40b0923cae9c595274c5496dac86828d1ec8c1f1f604383eb2b045c8298e9c00213a9ac0e68b0a0c25219bc9466d7a81f5ae94ac60932b85825c96edca0554d46929a703a3d9544d8147f5709049c801c0e59a7a7d20dac1eaf17db3329cb19a38ba9c5928b5349a945ae49a7d333a94cff3a3d93be2c8fdd3c7a7b20faf44d7a8232bbed8ffdfd9d31b23aa5d269af7ee91487d44ec0836aede30ae560fce0dd7b6ffd6b6f162fe74237be15400b1c5e43b068f11146858f2e251c2841d3d4e505346d483361c02e32d186a91c12b539246ed2ac1c928a5ecbf0deeb0363ec542bb5d626b5f0810884dd552c9b0804c6328018dbc2e090d5c64c0e4ea82a60021634362869e384020ab32b5a2ef3442312b46718750ca149df34edd70c624d5a59be3c4a8a08ba3ff449f35e59bee41def0f3573e65886c94cfbadf5fbbeaf04d13b5740357179a186534ccd6692c534a566042e37b498d1718d1c1c6e9d9e2b98f4b0d37385112c6b90dc211d6a9da5a52b120c285847aa54611f05681106f7c77e63112645dc9fefbf12047a97491e0bf07e5c6445ecfbf37dc6ffe9c7f9f36ff2f1a7d840fff7bb44da40ff677ddca1a93b841370b34454d24ed547a2f7c782fc3c16e0d722fefe58eb638aca5a2d26dcb5fae1cb4610d96a45557b6159605a5df4fe34a0f25751143eade4c13f0bab53b814615230245e9de8fdb12045f8b2b7bd05296a2fbe7e132546d8fefb11fa675f81d4fe907f1e217e7e13253a507b891214a045b4a7002dbaf91223782c0069116d048f05b4c7799c120445376503acd3fda96f2339c953145e9202b4c8056d6681ecc1bf234684236a25ade4b93ffb906492913cb714b1cb71a7491e5b8aa0f74745d1063515d77e3984d4e69274ba5c0c8c753468470c5f8c85ec8e62459df0e54898165a1833c379456bcc922d688434015f00401581978197980b2c98c8205364031b36ac20e6e403a82d45f8e953c7d8c5908e713a2623ee746db476380e1d12b236e372e06feaba79cb235d1f65a861f9c86f4242bdb661a1dfa5a27bdf14e2519053e2b44eb1da20e926e740bbdd4648cf9d62b591eae6ee0718ea0c06a60285135e844541534585895882093564f450b092c5c2e24fece67b2b8b34600ba4104c9f2cd0e460541d501ba6ce3bc56a8324a64f165ae70a31b46e9e5e73c01a0bf1648cad11e2f61b0b73f182d3ce81763d138ca5e9d18d91e943c2efdea2be3cc6cb97a58f795919c6a6248df55cd800748a3556c2988f6b73001023a2f560aa08ad68c11b1b243d36561f1b2f6c04a0268bbd564b0d143555bddab0a690c4c9e54078ff8e76f7a6ae5bca43e9bda08a598b91c4b8acd65203c38a55b1dc5a6bedd34b815939e8367f757b827d9c17af4fefbd379752fa892c267becce014096eceed36c8a006ee6e70921ebb92c51ebd8cc2870c7cf13a2fae8b8a4567a5027ba745681a55862553afec2f363537f36c58cebe2fa737e4cda6ed551e81906eb286e6aa19da1d02295c044d5ea28c4aeff3e8d4a60a222f5120ae8fa3952d7df3503e8151252d7a58912140c09a99fb8e9fbef3d21d2d90432e8da440905744b3be03fa1fbf761d5a489ef4fe80760a097c8955040afb40306c25862533ab51e36b6b69fa8ddea24d7de7bef0b1845ae7740c5eef5776d415765eb7269adbd449d7981b9caefc2659d628581e97768d49aa1e30abb12836b0ceb29e9b870ae53ac3022613dba191425b41ad3ac8069eef99b2f2398a912a1832f23c4d022064292121a5290dab147e8f8c51b3ac6d80bc662491d63311a1de31e3c2f23c739ac1163d1f1045eb4d82956988d5c428fef1f7bc294942c579ab1195c639a30185c58a701d3cd20233587af30b1344c5870a5a96a81e3b2fb0c589a291d778a95a69782be2159228599e9fe0ea8e8b79646f777a654025d9af5f1ebd2ac1f3ed1ad2169e6ffd0f4c43442ba399e01eb67ba2670bb59bf032a6ed7a511ae8fbb38f8b49b62075f87c8f9abd1fdfd5ff8cd2af8b40e918deeebfb9934c26f876a900673e6aa17a053ac334d9b92b0dfd7cf9852fb192c670e50d1f77e703f899e5f8b381a65f6fa987a1c0d4e675cf46d06095699af9c9dc7f999a913a0a614c85437c71fa755aeb30a34ad207f9fb7e65ff891fa3319e2d36fa4f9471ae5cfd9e8fead8c1ab130dd993367cee86c65a3d3457d9fee13e185cef8da1f37baa9b362135ae15430c746a7fbce1f4bfdfb5efc7468f0184fbcf54c8975536775c5aa7cb045ea0c8b2145a2dc68c324480b2b76d0903165634fed1095a6926452a0d4182e55034d8a8a88f34556833d49603aa17ae4d6f042385528432687d20d3a552bc6f088a1e3498bd4909b931b9d24a5630cc9f0482d8552caa820baa8a69ddd26c74bc7ca8d0b270927e4d2e4c6763a3da7a63a003a3da7c61a183469a03eb09d9e501eb410561bbc0635e37c32728e607542b9f8748c1c97eea7d3f309cbd30c2598d82640c8ee68ede58d077de1a678ff96b79c3fe772db9d26ef2dff156ff56fb5d6ce6f2fde4c71e409f195b8e409817974b6e57f4bfb73ce39d775eb9bf5eb572f2fbcdce939820a61a7e70854a68dc03e9dc044edda84ed9934b17d7cefebdbefcdf75e1eb776434c25e0f90136842da90414df6a95ea29c254b72610d0dbd34755b7bfad86d14d119bd928ffa67657b1558cb3d387f1d5e9ef3b6340fe4b23703f63a3bfe52e29dde18b459b33ce240a1b819e6a00fb7a96ab81db9e3a01d86dae74c600160bd0623d545a6b6823b8f6eb18b0bdb97256ac58090b3b7b742959f2848995524e4a4e4a4e0a0c30c00043aec9b6b9269fcef83669926308734bb6cd2df974c6d7e696f05b5e24dae163a1873326897a58d9f773ceb9347944f94d1e05198f39df2b38f7987287c09c6f16987b70c939e32b370848777af680e196b9b4f6b63d48d871b3c61e1ddc6cd6879eb307938e358bdc95170182980e1123b27a1510d103a18b8eac7dd3e909420c3da7d31384aa514b2be7a4f62fb02f33605f6bc2aaacaaaeacaaa09c48b26865d9d2caf2d5957531ce59eb6fe3bdafad3b6bfd7d7b83dfde202893855b0465b23014c5190dd368d7565a288ab3198d560331b3b25fb3aae250ba3dbbb54c76c93631d925cbc33ad924ad2c5b5a59bebab2a8aa102354554815923a5591d6541715694d5daa491a605c1369f6629cb3feeef7615bbfacbf6f6f507665326cabecdba04c1686e2ecce66d8d699280bc5d98c46abf1cb39b695cf6835ce6db61b9c8b83836dc5e1b61b1c9cdb2d47e7eae8605b7564b719036aefc9b9a4a9b3f9922650b73bbbb337820830feda712e47bbb31d9bd5da7b31ce59ebefabc24de0ddb369b92b7e52091e014eb2f48a2b3d7254383d794c9d3c9c7253383d79249d1f2039792041c91b47b956cc41e119cfa8adf4454de69b73dec5c0eb87f5525c3e5ec234e47adcc4e27ddb8f688fe756c859e5b0722ce498e498e49ee458c0f5e69ee49ee49e540cd66dbfab71ceb8dfdc139be3927b527b8e0bafddcc61e558c829c929c93da921e59e9c2008397920c99de70eac1d4e72597ac5b9a3c9b9a3ca6e000992bd6519ef6c4b1073102f488817a63b4441093962029418bc3c40d21ec0a045b00384155a391c8162d248414f328c3f80c8ca1f29ee5d72bbab1b470e1ad72909138d39d9143e8040727f68cf447768df0dd6314e41e4207420dd1fabc531c660199442500e1f39a63e102151c23188bc7350e51c472ec67b6807f19ab25ed72442138f5e0312d60f2baddfb29e3b5cf4b08a50fa25afc51d9be8f7f4a04905ae1f386fac396f28710d62221e05392a8fe8bec9a3e0ea9bdd55a9ccbb431f6c5353bf43b2143895d12ab7249c767a1504f455cac784a5182f4019a215a6c8dc69f06484cd112c165d382832839a6c54399b7e77362dddbe189fb601a5832be78d182cb478da1853ca9df09d364e1b5b36b274f3f64c4586f086e838c8aab105ee6c3d00e94e26aadb98ff8174547c7780c4feecfd59cdce0ea2883d3fcf9b08c74c53f767ecb13ff53908d373a727075ddd1ca3a6a674533c9af3e0dbdfb737c0614c9411b2540102a508192c4e594a72984253f4b9b0f3586e074fbcf58c9d9e35a0d4b87aa1b5fe01e4c51495146af0983ae3454347d79acd8f29ad8108e57ed4102283500da95c2b225ae11722e81f55be1ca97241c618224c3d2d5e609012030a5a376ab8f86accd0c96ba2442b3ca3c777cff8686834b68c607961e025d8682ce1349e6433767c62a850108f460898f7e3879e2143b4c2313e7d6f638e04be3b3d67b0e8a6502e8a7f9d9e32d400ed78a7a70c30dddcd5e8199ff70373e11b7e2c65e5252da0d00f2b42ac36dd326478014406d60c20bbd3339b05a8b42eb6352d64332508080410004316000020100a8703e2704894a561aeab3d1400096b943e665034160723914890a328088228088218c610630842c838a3d04d59017debb7716240c46e6a0eccc80ae3a432422ef572eca94e4f1eba0b0b5a15525d72a92ec6ae6e1890796ba6180fa03689c55555785cdfd62fd74b0bc9510376521ab0d9b1e76761cf1ede0c23cb7709b0d6bac4e5590c8fd157e82058f8273ec4657fc58876fe9348f011cd8b4338cb4ac21a70dac31cf89a90ab308f59559a43775e85aca75cba18ed30eece29fd98a1a68831498b6c94fe2344649e46f797f5b29f48bf2c864b4a959674e40fd8757f689d08b2725c4fe292aaf629a17a6a222edb714f9d45cc9190015203d55c04b569c85aa19da7970eca1916ac3a541e9b6abf47688e14864b14b733a5c8419baa3955dc8788118afd9539c82fcede95555b8c2be5dba70219251a02bbe581140280a1c3f8ffbf6371eca98efa46f8057e307ee621db776a1fcf3d04e8a1846b29c8286dec3424bfa708b4dc8c0302f15161007989c8780c9ac5acd6790c31fc44a44c5d051f6be200105c73c680671657e4bc9b6441dc17c26dd6092737643f455464b14a60ab8ed0b480847674202dc66879c7e3e616f08b0b42a25558c6035e2c4f8984d8f06310832181624488a746c4ae62f4bbb7d9af3c84361011e29d9e7c8f7781cd201a1e1c833b819096ddf89478de50e37049f1bda5e6ef37815091725433ff1a39cc513c209d4c0d2a29f169e35645f232c1696478ec6bdc651afe079f190dc93c14ac591e8e90812f95765944258bcdbd19cec1f61802cdfc7fa47d619b99a88b6babaa96ab2bf700ee316cabcfab6eb95d02ddcffc8556ae3c2ce49e7547fda1c20ebba8be80aa5fa7b8a9e8a82f039208c20c62b58bc446dd2d3280e13409315bda84b2ad190ff65df683efa83bd7078802303d267155b77e31dfbdf20c6a74238cc912ee129271fb01b93177aba269846ac80e32178efae7d83a12d5d6a75d33a466c3329a363cb28de8ca21501cfee551844ff52ead0242d3d6a32c144655cbe67113feb90d70872ca33263561cf7376bc0bcc4e178498a28a33e6592372757ed3f0de79f7c9c6a49ec7110a7ad1df37759ff4315c4622581fc245df1ed293850e11d6298e7fe948003b9402b673e73c1d8a422393a747a32c6866748f4dfb5e4f620115c24c7e0628d5b5271ba9e85596cf8330b8b66850e63e427ca188e76e1056ff7ceba4ff903752268b35c647ff37b1423648972b54b74c2d9c40e97376c92176fe4e39f77f87a8a460ff1ce304f63cd52f15124a032fb9df6641dae8ac5b123ad6355b55bfb2c8d3c15434866e8d91c6498a368ae8ba14ab5c583e3e6467dbef0e6e78f683b293dcedcae478cb76bfc476a5b9552d885ed94c3e8972a4004711c11a47ded18adf58e55eb020743e0b68144c522e2c611466725a35202da3f1a2454bdf704319e1313cf76b1fa1bad9308d16ad255ce701950ca2111e38a4817b254f69e0f2d2b1011d7c879dedb827b8b740054edc143abf8fb1ae4ec621e53d92ed8f5e4d8fd2ba8090678829de5f0772eb7cd7008223fe2e915abe3df312a8e3f3b5ae43e27c22b9ad0030b2a11d863edd7fd8a3ae5b6c45546d85606dfca5b38efb65b8e07f2635ae243c50551f8a7ac0c873799585a140736040f10b595ff94fd58dca01fb6eeff111cc6393c61159cd696691ca36a28b6011f91666e5ed31a9eb69df663bb05b2c8af8edb12278ed2ba74f0fde448537876194cb2aa8420e3be4cda9dc90382145d1fa0343f32d5d70be8f0e75ff1db27d3b5592b1c7622c16c8608bb25f2532b04716ad825f97ce1ed12d4601ab190412236f14dd986fc7f6199e3ab54c34f17ee76e281d02e3bbf6b49d146d1e4eba8d0c514ed779361f0bc705057957ce0fa670f53a7db260f35fc17063df3b2c75c14e1ffc211e88a3cfd56f7dd82847d9e37ccde265f34515cf9fd3e2aefd932c8db6831e075eb78f43595c553596e0cf205b9fd33c1e1a9296535192503de7a073f78b0043a4b21072693a03712ba71a101af6deffd270bf343dda73450d63ee9eaab1d531e8c1be040575be10ef0abb821f489d142c53f018d877e4e9c5513b95770e50248ccb2ec70932defb3d7f248c99279027f10dc1041bf4f478a1813352716ef481585085ce55130e30fa0ea1ec5f53aac01ed7842490d1eddf4243c1802eb43c173718ee0969bd04c81a452a024fcb3209132db17c1fae231201dac4f2411e7ba8ba7d46faa7325a8d4b112b2cdf3cdd407563381a7a040d4d117d261d1c13325812c9a8be68173a5f23f1e91b61f5585563f239b401c3d8831ea7c5d801cdcd9ee3795387ce131ae000d47345674c442e552c09004ecd0fb5fb5c290b8dd02dfa2975bb00c74b31eb5a2d5fcca3f43b898a331ac0b86181aefd4880bad0b6f6e2eca99dfcb4d128d8b51e365c21226e84201de342151fb2ff8a8c522e1b4c548de520bddc6190060b2f7739d6dae85dc45ea79c1230ed965948337cb1db99c7f96b06abe42c8d098603823412d579e5b579d97d5dd8fbd25acf311da65b31254eb5c817b744082759d2fb5727d250c07caaf7305062115afff2bcec01ef0debe99a3e2e0e493199d2d970caa0ff573710d94a79da943b73c98224a90bb9697dc741412694282e0c11aabae9917368a57bbfdf53eb5eec663a9aec59823f33f7e0326db33c2b20d2b3293242711068548b4bd4acea6174d22c38aa7d74fde7a880dd8369e614f5f3686d726c9feeb3102ccced732b320ec31573a1a94834dc3027d342801496e081bbfca199e140d620f0c110d0daca7b24f605096385d99f82726f8adc5461a3b2e029ba327b0c1f7182b1da39b3d8bc4ebf1913cc014352c6c1f3b930e84cf2da705371c1611a87aaa985da79c967f5728f19850217b41f1ae82a6cbb115f4d86b59cd81b5a6898b6646f4e3c28622fad3131a3cc1c20a95d3e8ac188294d15304ab1818689bcaa24464dee8601d5e71e85e05da9ed46941cbb4c76d7830bd28211aab256f5f61d6a11880a905966656adc85bb0db705b3ae836ab259a31045433ba7fcf075dba257e40d4e1f480ad0aa687b5d1f95649f02ab213c5f63ef055c950c2bcb063500171bcd6d1a1c4651619e2e06ccaadc90e2b07ba90bb744f0eb8b86da9afcb871719a59320e530104c5fe65a28183bf377e2b349f41a28cda01763316f8dc18750573e3d690bc0ce7ef057ccb92cd6952ef4ad542c1269a12d02deac5a23f5f005abe810a40b86d96f397fb519aefe87465a39becca40a855b24cea86e1a709b750269529d8c7eb05a2dad4b10881af89ff52eac4bf276e77b31f375beadac2a0bbf01b94c870a6e4edadcc7acdedbf7b8bc799ea2a357864a31be58678bcd340203583b916029d05c759ff25c8608a5d5a44375e0db76b935095300c2563be15c961af1e184f5e678a74b363be739563e2c84f2820c78a9b28facaa839a88c2a9c61883b6a1147394c97d1d21a92378435135c36a45729e416f2aff9dd52efb88d92a74e24173fcd07da30561008f2cabd69074b6211f98e014b6d735e075c4a2678235309104bed1b5f6eac0c72aab00b502e5c47bf4a6de4b7e8388dcc3dfe2ba61b384307620f9ce20ce8605e6b0bd46b358f574c55a69f1b2f48c4c7947be981686663bf095adbc2a0900e1a341ec352135578022f6b888955770e059c55da92fb09b8703ea794b0f3bef772b9f44ea8ae6b0cfd0477d8bfd4137af4ed606cda0905026ab754f334d5096a5b5a7258349427cb6a3d4d2322371be6d9bfebca80cbbe4263cedbb632b5714b64569bf6283ef1c692a9b845038150421e6ac711c3a8424b7d4d9d106ceebbcf11569c3795ad70c49c276e6ee2d76c3147183ca241dbf50428c9cdc07c782cf78be356cd7edf74c39360566b9c0190a19412d6347cc4f2d738c6a2e148f082864794a8841f1fb45c782a56506b15bdd4b6b485a29e30e3c97ffa872977652043b2a6f186848f85d686684596b0e05a4a4f54d766f207222f130e321f998b536b3ea8fc6280280825a86ea3fd8147592d426588dcff1328cb3114e412fb29e223a54c40447fde4acfd8e788b65ddfc2b2b31a61d4d346b173b07a9b6bcb6075d044ca8ae3706905ab54f481d8ec1bf446bf44429d7087a12a65f2485e891875e931b15a1b57741e6ba816906593eec4c476ea73fe4a72a7ab9c16aac91fc416e83521c261c8af7dd0b4bc76b37b7cfb20708850e4028daa54376d1ab03394ec2407354ba19e387e008ce87b6a9bc00fa4642e333087105247c830d145304584ce0b4e5c6d46f78908fd618e66083f7e5556b928dfceb1307702deae4181ebfb47f63cd1206c192b1f48c21c738a1c6c3d160879319d285b715fda77ce0b1fe89dd1387b00b841506d43eb1d803ee16261f6d1b893e275037cdfba6bd81b33fb409b6ffa1c359ecbdfba7862564e0c25810d5ada8092791b165d7757372247a0058c48f848732e3fe1a39981f64e11f622eec2cb1320477a4a583064972ef9e022aabf624096d34af7f2a5ae0c46c66e36daf723de988af4f01a87470f1a408b175c420a0cf2e789fe395a3c898251fa5525705db4159697f24dfa688a309974fcf55a64a2316d677ee7ab5e7bd7c3465954008fb40f850a3d67a69e94b66e985c2ab4862d05e55f0c8ef2486c9412c4764400a418747bb69ef618721d45183997618f79b47cade8446b23305ad8aec91da691ce00143fffbddf0d31268fb6c7e7b148f08825c771cf35b4cf5912f5d4eef0c1c3297bfe51667485a06fd3121a85b4c40e9f18cd6084ba5c7e098454f714f78d5e68e68680ced6395ccf20477f74e03fc65bf0705827bc3b766ed0a0a0a0635a81e80ded646be26726be7530221dd7f4573b536529db5607178237975cca3e135c742f7386a9f193191fec745c978a9074f3142a1b8b8df70eec072867d3d99137e5167597b82baa4fe623d1377bae15b89be52f0a3369350f237cf9dfcd7ec9acf45a240718c8ef4cf7a3374d941122e7e0f42f8966f7d865d458efef075e9dd53bf05121f301d3c63f1d7f95474b91c84c0f117a20102545901a28d188883c504b808a0d25def07813f40d8ab0e94a1d1f34ca0115873d94550435c9bc0362bd3c87a66441237bfc6f0ff166e02b19d8390e2b004629df16d65bd1af38c3fc4e77507eb67c8dc21680d3a5b1ab9185cc11b061d1acb37ec1c5865e751223eea959d9b83eb22a6509fc15e0bbd90cd64e603ff18e1b30cdfce38cd189b294640bdc8d52d08a618a1ebee893deaba8a661a746e41307e9d90144f58146f831ee253ad14c09ca2d81c08a837a9cc55de43b2e501988b844fc21d9bd9b2e586f32451dfaed796f61186f19191e238dd9c5b946f28dddc3e37cb84f974333ebf549fd3723a5443dcd98940ee423fef9110c768d75d36d0093bed7a8560bd8b5ccf6953dd09ad7e8a1d2e57919e6a55498187cdc4a663d85b02ce4d8a46bd0494193e10d293d6d2a0c97f6bd01ffcb292ba0ac8d7fcac55525ff961ed0c981947c0a98e49d9375b8a79899930773b2b5c99ec9d9f12c939c499f5a2f687cb3b8e34048ef7e3261c95afa786ad946d3b844be5a73904a0074e9b9a256ba17ec35342ee2f4e56581942b81237e84b3c98cccf4dc5be03751642df8e56384b9276c09a3665e48bbf14e21406777cf4b9749eee940eab73ca2662b700a951c19d4fa41ad26ae55bd9f86a28166948562dbfafc4ba140a05db539108ba858fb3ca81e00546d41ddf3d9dfd2009f65df7eaeb166928a6ce3e5f6380d80d1aebf1998b4f1ca31a4c5aaaf1ae619b03b4d2cca9080286f59e41212a1d7db04661a06d67915a5f4a2f4eec2c70cbc1318fde725c002a9da96c462fc95ec1622de2481b8ac744ff953e5cc052c85e6ed8f69edf3a03b0c7e4f0b43b07054cc6026661cb8183fcdce9bc4e168a544e970067ad263f1a460f50b3185dd880ac41eb2e0fc95d7a6d416815756c45939503402bc928bb497fada8d43aa7b76231da22aee84a4cd2d29b99114c79b762e0353618931573365fbb0a31289a42c27b6702f02e0c6ebbb7cb99844cfa2d73b74e204887bea0168d85b333c703cab562c43b9d4d70bd33c7bfb9ad7cdf62b03fb01fa24c517ca6581ae5aa354e6774b68f1abf62f8603c3748d892d3babc64d6694ca9a7759f4db988e2ff61818d1fd409be60b26c9ceaaf5795f4f9cc2f757a6c551af0a0148e2f977f2b3d3dac8322665fe97403832f06ccac3db0f1b238ee6efdb8cbf294b4362108db8c312883612560d00f1d6dd952c33b09401e190616ea58d34fd116effa103d88e97cb03532d8d3abef82b39470d6aabca9823be9435ae801b38ab74b587e072843e71a0b071d0e2f801728aa6ce59ec6240752eff5fa707fdf70c5856fdedb7bd8f85180262e4529a7d9c904e2103c9f0165b2bd2a96db3d4ab8778b30fe5895b09bd64d9843f7951518f198ee088349b123cc352b3c93173e36806b2dc0c08a0afed6bbdba4880ecadb852a5f0ae2677969d9b40630eee20b8c7b039885f63006c27abea6966d1306600010a8e8c9d231c4414bb72055384771ee00084d6158a17938d1e54f84e20e14986b9f9a2cd28348eb0105241b98aa4ea4803b482f5bedaa2f269fae3757071143e6f0d691b659b168eae4705f7e3012abbcd94b0dd4e10699cefa82579824f2e3e0a74bc77a20729ee5af00a0913c0894d5bf2b4a88731c72147563c3f1f7e4c243658236dec0656261270693d6dfe536d4bd9213928e5a21b96f8e73b1d88497a3e42f4db823382c24a5c7a80609a3ea61687308d380069e4826ad0cc22293252e0b15c0764f17c6defd54d02e0c099cd40fc23201a4130ce06af1066d12be4b31358039ef098b04fa08cb68b1d48091156d12cd29d3303030db042f9a1b169d680769d7365eac54ba9b11da1267db81e8a448e1bec4cd0289b456edccb1b3d87bceac950f1f7ce6c4972d27b1c3173258eae36ee6e83509ecc50e7783c620258024e847e38213acaba32ada1a68cf8be586abdfa53ffa66feb851a3d9e2480e2b82e48f679ab298844138442efec3862ea5b0bcad4f3076d527d01082e97dfa1504a4195269577b1fd20a69f4300479e6180b49b742a79ad17a39cdb2e417622f0234d9429af70839c6af87e0df5aa5f6ebf732991ba88936cb08c4ef41f9a0c968516f5e667b06e62c0edd1842194fd4a57b68a5fc1e6e5a2f92ab500882eb019a199763cd4ad6df21da8a463a4d315afd4180574c9e1f01a79c9aff3fcaf065c2ced236c1eae1d0e516401145ed67a77f426b470bf5a41c5dc625c873f843237e591e26b2a6b559290bb148679c515a9c84d9b767913d383f1ccc4d21c42bd30582f7664f2ca3e8e66d5728851988feaea47c5a0293c21e3a1229849142876224dc511e767bd8d2ed98dc2951769f71f33458c84814ae208392d17a20610d67ce45a280929fb271b778f904650af409d60f8c477f3351223d5b1c46ed962c7a162e6c883e8a30e444e8af7e7ec549840be1a261c2258f2182ecabfe20f1762202ba8c8968a09c8ace18d0185f09019f70f0b280ff951bc73ef2a722ca2ff9b0e0e9d9b2648a34b1cf9164ce88cbd48d01925c7836a45262ededd139c34da6437eb49e28187e5dedc98b78d7e275bcc7cd7c09389772b7c19b1909ffd42c0fb66afd512f0b9443f7e88885161b80bd90a6bb70d4a91537917572c8ec2c24602c67c1ed91f6f2c70a06986c3fad2793635e3bac9cf68dc33a98b398ae6ab317eb268be519ec0a02892f3de92640f4ce5b445003abc58b12e1c063937057a9211434a324e57979a250e7709c0df6509871d626b24de5a6389e318d1a88af22075ffc200b38ab0f1118174cc51a3857ad062dc1b4c83139290f5d2898f1f235c124bb893c22da63ed4e63fd2327c884105f86d0352b7fa0b6e503f6472b496c3441dee7b8c107b8656fb894e98a7acdee48b72edfbc705b020c263c9ac6fa1c6fbed260c00df46cf09bf0ca4ee5e7fe8ea82b12922cc3e9e983226c3051c0e316bced25bd8c4b4cd8d0ae7e497d17b545dbe0d2c7b2a172d73891e887f3dee642e31c9aeda2e9a5b9d4967ec104ee68e4656f0f710db78c82c2f8ec35a0d593bad15e05d324dd2b71d7c0cf5181142ce514560b94c8ece153fe2a7f0db433a8b89ea0634f115aeabab6759b146d32b7cce1274d13c0a66d38e6ba30c1786717d1070178372a8b338652ff7a4075da351358af0292fd15b04c40f82b48432dc7f5a66e431c517c5ab4c4637d9dc0493e799f434a7e0af2e2e75cacaec3e1d3309e1a0cf1426d74374dc39674ffdfdda70edb90cb8c6a2470b2239a80042512e2b19a70f31614d49fdf1e5114671b0a5e0139c1dec113a0c3e6ece59265b14390071b4b011065561b2a5c9315fd478f1c626b49a2611a94838b9ef503808d90c80337b67243a50178ca70a709f030dd35a5c58481cccddb8cde03ee4a82d204c27c0d588cb57e5fb646282572b15ff071aacc25d25e040036a780ab4622e9f6a4d99a6f9814dddeb504005e6f76b04734a64df5eb7382ec5a64175c1e3327fe2a6f5575906f94fc12416511730dea1d9ded0ac0df9476d80e3a70e1ca8a1d52db7a03d12b8fb403c904698906295bced3be2378d82aad64e7a4f77059fba82b1671840dfeb969bc571e60d306b03f9003e6e229f8b2933edc4d53868722c9e7e8bfb04ad04198872052ea4e0ed619a35c2cd7d3051b91e04d0fca60bbc709dc396ccfdc080cc0c7fa3fa8bfaa949ec2a22fbff08d11820bca49378e7d29825dfd1f30d2bc09ed8fc49aa1ff1daecc448f46919d1901151dc4a786071501cf12970223b67c9dd3c1f5e507b648d026d989eb44cca092238123c5a67d2a2ace788279ab54689496a84e70a03c95459cf9482e73bd9f06ba5ec7eccd93ad1ce3920f5ee25f6ec161a120d58b950a7df3e2a96131b4429c353951e715abddc5c81bc6483499400e56cd809eaea73567748b16c76acca53da77c8a9712301a6f2def4c9544bc15d4698e37ec7b950856b8898f3748067a87f72fe34cefb0e6bd880f6d3c7bb8aad40905f085e5b68ec9e2b9b9eba689ed5ed4a4929af0f606d41ef11a9ef35a36c8c8c1f1b2b109e0c2749cef9d2d0206a549ca389d3f61a553f373002e634a371d44ce4e44edd488a6232f0a1f81c33f468470f8a323e9c895d135436bc501e11790223191bdd7c52d584e31c44dae120515a0e61990a29aa92b7d8868d41a8d239401293ad6795b0fa77dd45f4ba6744a2fdca535468413172b9500ff0a013ef38d84abffef2101b332acae7ee1009074d001f0a92c97002d9a2315233c0a400bcd71fbaa1aa5f24e0df1388be26cdaa3f10c6f2dbad9e7a6fd1e7cc0a71c6fac84dc83251cc8968c52dd52295c614f6203cd12491fc7d116c3ba97ca765e8945c587256376b54d9ddcd7a912ce22431912a32c47f64911788b79fc45ee20597abd127bae2b4e8240b620afe36b6ce41693d63e4755571486642a2beab05f0c93392d3f6e19eebeaf8c344c423588d53393f5e0f785044a7e24d0af9c040c945f1452cd2e259a345572d4eabd738f03e7098c7d93c6deb50ed78632d610f318c4312398e5ea452afdff51ff7123db33859631b80a15636bb06d8ec7081c726ed49d4b37907b00d505e40219dab3ef6b4ef1ca4c35fc58b43923c44f162dbbd3a96fc58152d657541235d26a2584e305f9fb55cb256c766367810083d5169e4b3c39fd09be674adb1025d6da3658f1f84df823ad5d051c8258b4fce634d213d05831537985795d287ffd3c530ebfc4360ffd76d78e7fd5ad0c6e8c4e3b818ccda6cc080e631b1cadb5f289e1698558f9044947a20a63cba4dc260328d44e287ca6c1a807b4c5f0c655287b241a9555d1ce7f3b0fe845c53f55e27b62d68609f8ec7b21e8a1aa449d51af617ebe1e1c84e09c757b8784a111413b7712cc23893baebb7e813fad104823464431827b416a622fb9d495973cc3bf47c762cc60b02fedb69a5bce04cfbc7f9fee9ebe15ea482db8665a8f338775fa96da7327a41a69b7f8c63c3694db0b897b8c97fedf40242410788c784e7404591897d3fa11a941049a97615200afaf69adb02cf8037ec18fb5692b27ee75b58a84b0202dec69ca725bd9658dbb09fd6318ffbb1580625fddbd8f96903b3ceeba3ed75851d83825def415de683b84e676046d2d06d96ac4a7ee4d4035183ec5cf3a956b44551089f407a49a4eed8a9eb35a63216c53142d37b6e23b89f2ffa95adf5aea72130e4416de0153745f2140b1c139d8064c3976639b4317057a241e640950543782953cc29604357a1f04eb495fc3c5c982bac67ecf2c67f2c5fea65298970f7567ee91af6d5869152a68c6cce35304ac9c191d2fc756f15582c2df19ab54641d3b92cb2c58199c6786451b912890756cac1447bc744b73f362b1d0d5787442bd1c8a2a86f4fe45871aa118d601ae41b81cf4a022a39e9a93cb5878ed813670b23cc00c71ad2b8699c6e2001f019f102acf7bc502e24bb7372e125a93b628db440a5a2290553a48d2251c60f32d559ffd1aa4a493443fdc5eb7f6c97ca7b94a6748a861476d3cdc1ca248803827e76159d9274721f6dad43a8a85a039d5f9e0c73f08f816314c70ab3c08c9d8f1d5d1dbec9a9b09349c6e5cd038be81aff730f17f5a9d956d96dd546d491c2b938cc8287655e91c149f8219e1d2aaf6fd9486fda517e09ddad02b455fdcc6c629d56b95ac92eabcc05a95def46f196f3e53f183f57c03c2325b18a05c576ff604d06a64353ad3c44ea57892e0947a2577477291bee52362e714f1482888c565b9c181113efd769b84f2d329acdcd6571528f9da1d7f64316eaf818f893b979ac48911930966411fb91483a02f7152e4e5cf3d3d2080a0855bc270ce60c0295275c320431287721e7baf886ce8e46788c3b2b7b985c163a0382f331f79fbdf4e0d0c6231c106b1d8f2caa9fa504f13e51c81a5ae2b7c3829728400f720b205d821bc72430a36c2e7eb8e12e5dc9de590d93a27a455271460361ee0917289e4e8898054c94abf0652708a6a10eed2f5c5465023285245f8ccd53b860c6b381d693873af5ed7137f804ec216183c41b60901d3f4cddc14794c04898aacc7e919eafc835f21582fb2561b7c620b0a2331a39f14b97e4d50ea3e58759529ea58b18b8c85bbf555d1a58e73f38f0fd9536df6ae4f61ae45843f6f0a75ad7c6c5a1db9cb51ab437cf70993e7188b43f7a4c87510162331c853db06c0a5780436854ae9781ac320e283af156db4ce175400bd7583c06e7b758bb27d18c60419964797c60687a02e071b9ea4da793a4ee414061fd5f5ce83e00f4d02ac4ef6f2f1aa56f8bd2f76834f48d6b50cac64a1b1e879a9c5e5afb8772aeb91b87279ab550c57b79ec9e568cc045baac0b681fe21a92f995fa4a405fe3e609b23a671f171008dab10da2fac12c6979b34c60cd5d6520be8f96e0f93c2db460881a05ecd9fd4901a47fa220e87520ffe1ef7e8020d7228844a5b440247590378057b0d83c5328845d06dd0778c49df45fbdc18ad74105ab199d2bf764c9b5261473a393748f43c49951e922fc96976d20f95b9d4e9a1593d98da1a651d22421f8006aa55ff1c1de10cb91f92e1987eb69113c53dd5faea794b92c9bafb35f68743389d3834e1257f7f917bcd525547d85cfc4c6973bb62180699d602ae8c9674c3b88337a2c0a6439e1d9ac64b19bca033defd87c22f35f5292b659c41884a66b230f676bd7b6f05f7db83e98e4d2709ea4f6543460e817d24325688c8f67425bef63918b8cc2f8ee1a6dfc302c0ccebae53af71f52313f4191f2321d5d025506f5f6ea951410f3093cd94b12d5379dd5400c1c995a8a3a81d3d0517490242666d266f097e06c712e4db0a06f216e8e734d450895db3a1e37595cd3ef525b6840cf8b588068c2030c8e4612ed20462f5bbb18e049bf62e16e923edaac2dbe32ead92cd15eb852529df66b3e747d7382ddeb74b34b8f674e2f6baec688c76b45a014280152112ce3d49bdacddcef8a5640d19270bb7a2ea3b4aa8fd5853ecbdb61751a7be630a3434eb8ee24e14c7c28de1a6a96f8795e46ea025f2d04286dc9843115972fbfe411f553a80febaa26bbc051503fb2bcc2eac763b9ca513d80360d55da05fc49ec2b5e2257b2258d0ee3014d2fdf43829aeab86bbd1e05b593b266bc46a51a72762004226ae92b33a439c14119a7a91c533a235253a181ff419502e5ee9dcf3dd7e8a9bad20693073599fae02da21d9efd9b5986be20a01d052cbd6e9bf1d3fa034b95f61ae2be05ed5e437d196f8b28aca0c9cbe1b8242049d2bdee901258780ff3a54a46fa26ddcda2269f0f0e0a278632875021145da3904e21cb3bd2e5d4b4a05d58026b6efc6b86d978afff4b1b7c911b88dbc1c43a2aa0ea96be926bddf684287f71d1dd1b163e42bf3b4e50533e08f493d47d8cdfc0663020d4463b7c6b9f2e0cc3b2ff2ea16ef7fcff21f880588c4901daf4c4e34922b8d36fca6c4ae8af4d84d7e4333ed683d86ece76c8ef482de44bb11e89f980b830a857a8825e566a54d5c693a5fa655597de93d5a8fe6b1aed7812105c5bbf659c7991d898d4f39127d8bac6879e9a7ba34411456d12af1d2597f965e31fc092068e6c13584eb94978edd992d467e777fb17cbcd338d03579011405d24ce6432fdda3d7e65691d2b6c2d2899c62da465ab49a49b32a807b3f73c14e6c7968a1ac8b9b08ba77004b5d6739f9c83fadae6fc546b09dfc860c62698b7de819933bcce4e65202ab4f131424accef94701e22a42d4d710f21f99455293f9805067dd77b6a74d309a4c18190101fd58c427bf81ac9b950604410cf7500ec710c01542ede1bee5bff4cdcc97eabd867ea3224e2225eb12d399d4069a3dca583c42ef9157eaf7938879390bf0e2bc93e570059b46688a1e1b7703081981f0f119c7aa4cd89d287f4c700161d199e11126e1bb3c47fd14784df8453b5e63453b0298294af434c5289dd251565c870832f34216f53a31c8b5549fc5b807935e4d310a97308ea8419e2941a66f483dd56cc146afcd2400a1d710a1b7d27861a43c45e90914aeb665b3761e82c7aac9af98e7aae9fee0d84cb1630359ff2b8d7bdf6030b5b40c50b53f313a1920586e9b756c4a1307d99851d00ceb104e8d16c54a4e87e01a7bba15ebc60e25371b5f8c4654758c18f862de5bfe6f12d770df198e1283240650f374b5294b709fdee51c569951836c4ad9b77a35aeb2460f60aed729aab9a4c505eb404f473315c368be3e21dc5c5a0439c16032bb52691712f07d3323cbadb41863241e66f25a21f0576eb80f93644d60a0f0125762614332ac98e5f6d37880a94d763a3699222ddb36561cb5ac9dcfabf1f6b635be302ca14dcb06af78792ef63141993c6ca965f813ee9abf7317131cee4ad731c888441e2d5267430efcf6d7a94e117a0132bbc449b3afbc960e776671fb13c28a133506a4d491482cd64811fd085b0c4d4d294f1d4aeb990ec5917b7f16eeb7298f9c2e924f903c70b249ecb370cdbb316d45d15d1b21bc968e982c84309f295fda480642fddbb3cce8f1ba90cf86ba4ead60d30b2d2f0974f0d098feef3fe491bde0892b16356ee62041e53bfa97f75708b5d3a5a3112cb0b81f8f9a420c44963dbba94d2f7c1843462bf208ff5080caeec26f4654048e9cb54983c70fa435adbc596510701ac6cc9526750e52c4160b78da81411d3e083e9118f47cb200c451987223577642874872d229907e7b09a75b2d6b39dc378c2b94735eb5c4def66dcb43c8e4dd32b5c099c2f49a243c1b3e881ba7394bd784dde2ea27886974b2c5348a4809de288bf4c7f4338ee589ec4cd7f54be60455b50d5778455fe597040578bdd25828b3e684dde47b473315186103c134859ed260084c102ae5bf2a713d0f46ab4ddd6a12749d280b9cdab05361c139e7f3e4c163c366a21557a12a0a4b261bb7d5de6fd9b49628c6b5d295a2d0cb6e32dd458c3720470ada95cc8c307be32406775b58a1817eab0557551f92c3e842a76f098e9ada6c8bf3ce90724b388f4e9c797cff3c10422cc0bd81f73db38027d0c89bdc5e22f703e5110f453913873616aa98ae61ae26f593e419e1a25a588919011064be2ee5dc45674ce0905244b330187a2cf38598b779d45199ede3fe8c3d6f6c758f1be16780be569986923f6166e6af2a96a0ac8ddcfb80924b20e458b3589470ea45f34c7f47c99322e0a419552f2f98254e8a0fd075e50661001a15e43f8e625c67b6cbee561bf8a652773ae1fcf290bb7e278ef816e8f2de4eb9d1c10f3c740c8e23c57bd7222adab28e17aed1ecf1c265bff2646bd8211bef58807b7524efcd69895ec39adb39995be8a72c90613152137f36b8ba554ef6c1ad0b098801853d541be2d497dfbe3e38e5495b7ce5ae6d09703c1e6107944b3e4000ceb6de3ca5e18872a1aa6e92c6c5e8da104111f22d5ad41fc0a9dd4f0e15036e315d29c0305db652602d9443468592110a832a850389c68d1e3c1c1d3f8d0720d6e3c94421394f1af15e087c2fd7d9143c4d5c048a8241d6e650f340713cdd49f569ffa98edd2abfc223c08354f2f48d8d6a651ffc89e4042bb89942f59951aa9653d0d294d67ef467471add6f8e61cf0643e1c4af4e2bd1bdac11da9b27a102a4e89001b9143ce80e9cbfee8a868f33c948d8a46ca1bc01c246061a16faf6ac3d28ce49f407f252933171a598bda958562cbac75c8148995146f21e9895377c763fc4f5f47448a52ffd1de1c750299cdef603e69ef75e0e86050e3fab275f1f7b88a71f36483226683a81d030d4d804aa8c3b6c4e68604a13e73524ff40d9e64e8fee1ed782d2080335862b7c963fd412110a40fa9b75e5c622cddd773695e8d7c735d0e31d6b42fc6393fdf1b6ac74b45b83362a685b18c77ca4bc1e0f974b15dc03afbb92e541f33ea292ee0314962b49629489684f1c388c0976844c19bfb4d53eef3242d23e2ce8765aa0e1c8c10d7a829c82c42d33a705a4518b772b0ea2f1b66a30729276585067e2ac54346951fd782a9ee781f8dae22d4106188d617faf0116eb3ec0d1109d8f39c5d99f41cabaf847f3e88255ebfe083e990f846082ac0a26db2f12c9cc98a4304b3dfcbdfa6c1dd56711d158781c48120255a1f9a2b68d6154db28ce6a8dd6184718367f2e88cdbcfaeb496a02b21746149dc245eb54ea675457d6286a656fa1aa67ec3362e0a7fe45e15f3e82b61baca2d159e2c19df91dcdc1b6af33e3ab62cc919e10e1556af11bf72a2bbd98c434fd0fd426dfbb5d8840ed6b813094c475990740b880fdb78cea32e9ba04d4dff882be94f73f67d22489a46fb22b8700e8936cdbd065ddc8df9acfdad484717ec20d015b53270b8e0ceca80660ba4218109b1bdb2bec10d5618bb278a7bcb00cf89aad715c5d0367f4f24add40edab548d64804cb388a3735189702cf2f99f1f3359a3c5691f2984f86b9d5533159c2218ee954b02ff159a9d00735f18140874007e36ffa79a1d5e8020c6c246318de7ff842987147697175902050bfe71b40384f693ce31d87e17f0f3cbf46c974eb63eb81c4c2484475b029a194917a0bace1d88282848595ddd56f90caa91ae6daac9a7548f39a6a1372a0a3112c70ad3e32e886834438a56eb9d33a6d716e7af290789f66e0e425708456f7ab81feca41d4fa8e4b603b95175c30b54ffa1c2c139d417b8e79031cec161cc5f3dbf3bcca9beb1c5c43abe98bc941840ce48008a48462b01070b607480464a172c538419183595f056034a6f4b1210ad3307652393a004412ef44cb1a9f0d0d8578d7a848facd693ae528f82f38cda9f32886b18e502eeac036895a263b06fa374fc22d8165c958df6b1901adf13031f2f8fb24ee637f8aed3c12c980ce4fa43bd45fbc76e58dcb9c9be91db8ac4ae0f9da2fdfc052b309ea3c08cb8c147320f44563de3a6ce47560ebe1f35e5e0dbd562e7edbf46b41f0c26faac1c58e756de2638b4cfc421ab01502b4ed746a8236f6e28fbfc9f38488ddde1b63a37544bd692872fe8d796ed391d343ee822bc844211d7205f58eb5bd17072abe8aa9924887c3982308399cba1f6c14e8d85cb0c0fcd823010ed683fa8c9b64144c537cf715678c2b4a9018ee666d571ff603d9337583e2dbec212dd87ae9cfdb99cded26d7918019956e72ae3b34eea9cb609e280acb2cc3a8fe71848742a44d78bdf5e2f56a159cf7e4e1ba4a43a5b8e5dcf201f8f643625b0d6200260776d3a3a67ecfd412f54b50e87b29f1c9c9ec4afc077f095972d9896fae93ba61a5be99edb505d9148e0fd7cdddd0c7a4073b65ad219a25a381b364a52080af893828eec96804fc8085f11f5ae9666d57dbc78acb7616b9beb6c916d6b83c047aaa4b98a78468d96dc4aa699be85c37b99527ae2a608292b8a7dd05c82a1742a6109fed843ce3a6e93cfa72268a02b0ab09e6ea9f0faf94b2ede569a69c3babd1a3512245c33ae73c6d0a32f4d1b730483b233410625f561a1fe0b39b20f17de5b500af494d38f212bb873a2fbd9fa428760f0f48609163b9028c9627e7f6f6319b08875a820f0130f6dca063b80c71199416dc019de61499381aa1af9a1074b727ee83d8b3ae9abfbcd865f77c0db9533416df02738fb6d90130e9e3075fc73ba5580aa2a99660f2275b706e0d31a9f3435e7e6fd6343e2777a468fe5e69fda61f409f7af30833d4168f143f81b30e27266133301d9023c5285065997aeeb0848e7a55f030b28d0694940acf0f9a4f138a1871e5267b1f8fce79dc2f2e24d26707dc820afe70da1776084cadf9f8e28dc9eaa9db6338dd1a65f9ea15fda30036cd60d993432ffab97c574bb26b4ef8826e078f4ac6d531c40365e418f6fa48db3ff711082b0cc3e03dd0fcebda5461f46752b88657cc3fb81dc08f7068440c4ece644d09689e9e46654118d256dd9cb0dff128ecbbb8a39c84f1ade40095e0978bb00e0f087f5abe4c841b21ced7813dc3e34079d6ef3959196024277f16c19442b61f837ac278acb2a08287c3c0b4ab145bc2c6c2da2c36dc006773159335b3fd0992ed70ef0de153787f69a6d8d66fc2293dc29b927f1065e7c0ef08b0e3d49816a61d8f29e23a4c8951820c9f715bae449de5e12047caf3fb2a05af46904ba259596671956cb70ab045a278d6aa708d39c4c2395ac00bdac1bd68b8b78ee4464c47bf0c6edfc6a091943ee0ba15b3d36f630bbbd1fbc7d38fa1dfafa32e4be606164611815abbaba0830c7ca41819a9f275d159441a6fabee72a0216328e8031796b4d2e101d910b6164c9e3a84431569d28db0d6d420365b8b3ecf26fb66e1f1cad96b55eb0e692027494e5bdd9aeb13b33f49e264a191e4b076a70476c7bd8656d96c10dc7357ed81be15508dd109c00d31230987f3bfb40008241e340c9c24e35a3f0bc3a814dba62edf6d1494d9a1035e21ade99ae641e9b6fccccb9a0f9a397bede62df0596347f842e505566583a85402b8910630e22a742ff21974bb8a85916840aafd2407af57239120ceb4674c88679c6ccfb11c9493df92a0815c89bf0f1d1a5e31087913df04dea912436187aa888445a3197a81284447ddf14e8831aed7f31385303373d03b5a65c18569080840557a5f26df9fb8816543d450c2c2c12578246a2e1873d261baa5d65c76044510bf69d09597e791b4d4373e95edc9f09d47f54df0a06f7d0c55ac2054835c8551a813db388d7ec2c685ac128c98071e234b4890624a07b7d1c7a535ee440b50c5e2a536dd9ab6f1229a7aa8fc76f0085f9807953c77dda8f4c7daee0e7edb582645efeea892d96c2689fe0f263cf4d68edefeb2a5f091498aab4246d3076d5ef3655a061fdf941582eed74c6759880b8bc5988eefc2804136eb0b3dc653b07e924d6483f24ebd90892e98a8580c2c4bb0546a913d30c4ed0dd5c29aacfa92076c64af7b1142a9fdebffa860d47498f95e8a3f3c761ac5aa34593793f942fea18218cc15a2159ca73aaf661d73b8345fed3a61e9c32bf1d9906258c3e6329e97b6ac4b9f6bc11c94b82d2457a54eb23aa00c4ddc60c37473f3d09905520b48e42155c60df470b433c7c218f8ed12ee6a90aa33314171ed23c63feadc38322aa31eaa585e7abcc11d5836313bd3c0d03dbaca51461b8d50c04740eddc02002e61ed43e926bc8384317dbbb6b02d4916c732d207e1dc1274db08524ea8dba73cbe6a11a1e19adb67b91c7c5361b2935e126cab424c350c853b665cf66416cebbadfe0bdb1ae5e427086e048d09b999228e2f9a149a92b8ad80053cecce66ab5063cf7a40401ec15ea1590dd62b829adf08f7af1c28fdb62395cc6d01ece858f9da2e987dfbfeab88e4efaf04f17243e9eb3b0455c0f10ff419661073d275401679f41bba181fc2f6853521937cea92c71ce9d5b6ca7f869ddd9eb2c609349e011432010fefce0cc66c5ea6669bdf9fac71536b7f1e07d7d012aedde00d189b5fcccfb10b0a1af6b1d8bd07588849f63ac04377976c69d0b6d599c19fda522d61c6f952db37db06ae142989c1300600f63011a8f8c398152638bb31bdc12ab1b54906d92f0fc25f25ac60bf5e2197eee9334ca73fa3ec23021c4f8b3d89e98f920393fa61406b7e2e0347907f8c88934624639823c6275f9a11252c92bdd12503a0b2cd627fe65fd7c3c7801c355b310c232196c8f8d546ae30629e0f864a1da948ce1055d8f5c40c04d942a6859220c404e59b9b43b051d083b4ee2cd8f5d7dab678bafb930fe9910684e8fc5b57aa290847bba30db7ddce48372a10b46bd283ea5690a0aa49738e81bc370322b73987290e1a1712f2f21ea0d86f5b125ad6025b88407caa570ba4d21508e33af309107cb8b7034e15738ed3ab6a503614abc2cb88ef6508063ec4abc935cfe77b8e57e4499566d6074c94463b55124d1eddca973d359d577afb0fea17facd1428a42d460d08774525828bdb9cd4d27de4a6927d3fa1e2ea15361d525de2cb43c13714d5a1422cdb24ff481c9fc94010a8ee4cd37831ca15bfc296a3d4ce336b849b70bb97d308f0319de7a30571f90d60f7d4020e113043701fc057396764cef9c828add6b7d3df612c0c4b653aa4b974e47e9f63380684cfe126546a324abff437e384cd9f74a37a50bf9a5e088df8248cd5ed85666fb74332b301cfbf9aa2bca5dbbe4ec16d2551eb226b8ed8f374b97b379839f08e84dc847e47b105cc927de222b09f132bc89b8fededa444a828f6855d66b6f2250e5fe6ee95bb0d1dd3a5b31896bc67bc98de98b48f81826605790dbcebc56c5c10eab0ca9850729bea03d4b926a760f4ef41579eb50f39d75cca39edccb16bb927b9e33bf2309fef0e2d31f109bb95814747beb9b2a152325616c43432654834c97ed2b6e446f10259e2a3dda5308c23e2c095593d6a0b4ea619d6ad5c5d1a854a36fe8f1229a6b940a7a875de6f7d2f1014d5dc2177afd8b7a8b41b4f1ec9179029c881d03403b23542df429124310ab92b89f9192b2c87f8025f6a213c6c8b33345e6c3466ff63e82fb8fcacc85b87457b17dc773e2c6db39ffdb9e78cdc1cc0ab1e1e079b1d63564a04ec5e115332eb58179dbd4d738c21b8a03687e5d42f094423160fc18e8dc3dd70a463c628f8e3d0f00890a17fa0eaf94320bceff7c656fb642fffb4e1e04dc42103e840dd12b1d22f4acb68568792bebe7d388ca9f58cfff4eca96e83da9603d94fd4ac82bc954b70ed07879a44743bd6c60e14a1b4fb80a40c2170be49079bad5bd8dcffe554a0db56d41d1de0e092c354f85dbeb226d2c1bf39a5cd73e0f333ea9c25900c0aed5cf184b347b2bdc344689c793ed5a574097b35403c190b23040c441393cd486c8629f8138be467543849abac581fd6b36e876929d779e726feaa50932d4ec433b5bc45174c24804a8914d95c0ea5b68dfb29e4c3641514a753d4b8f5dcc539447dc8d6c43f2a234411f5bb0525007154d8fdb8a473a27ea839abfc0e6931b4d662a2771078c8ce914b4cf405dabc74af4430ba4325d964c74b0994c248f045965cc7f27cb49979a5f60b20cc43712e97c9a6e77a912860678ec70b32709ada5d09c91d13c19b12377248e368f0b138d92ec6ce7604d0b1b5f01b45286ca3d974d8392fb686a28cd11602ddfa67b47f2ba5cd89f79617a8e3ea39922dc92e8c21da2f747c4e02c97c5f60e9b61a1d18c2e28c692eea7c59f6b0fbce85a1fada95e9f4145d1ac01e5c81a4c56cdf5201f2bd2ed2227b9d2b25be60e377f43e2f5dcd11947fc3dc853d511b59cf1a07fb9c047923528331b81c500378bef20e8f53d87828d8ca6c125ee1bb3287e10bb42285433194cad5bd64e21c9cdac302ae70953d8a0fb31135b049254d0b71144d7d9baa1ca0e544a2d9244f10344e0180d4de190abc54d3da0ed8b612d4f306034dd167e633524bd0d6d1d2f227689f7c220509d5cc8bcf69dfa3e06ffac33942ebd76d09427fbfa1afe082232d304a595a4173c54c4efe8eb7dd985f07bfd669c1530f2d71aac25a9dac001e264176af14ade816e58d8c2e3fb956373d0e3679189a283074cb06f91628861cac9de74db8415a803daec2786b0827240cffb1478d779bd03552b92dc3ba5708b8400a9986ff09cc236e9707817064660406d4eeaf79df1016dd7c23361b91a9ebb168ab26ac282d78f050e605699d3c5d620863541996144d2a76052a4c41d7fc570f08cc5a41c62df29365801203f721599f54a88cd17a2bac5f9a25be4bdf94212d49ab137aa34863cb2485c761b2145884b4009317263bc85d15c22fc3019240b52102e734a3956c3dcb485f2f951807e9bddca20ddc8c6c08d60c09bb980f45e1cf490c65c3a784e55328069ea35023ad2e350a63a831c4356a3e85407e27ca40b6fe4417ece23bf8a1ca04e0450d72a87f426229a7186cdb8700e632387d14bf3760ad02358d9140b905247cd09ea088486f9b8ba412a3e9d3a844710cda8a3a32287806a92e2d6cd37bdcd91151fbe0211a0ac974b6e22c71ce18507ac1255b6082a53d969a798868ce37e2230a01d021ab81516621522f0d2fc083939fe9d42346ee2c802322ac32e048ed13531234d210d87a7231a594483a38361b30801927fe90af357badb3618297980fe6436dd4a0ab1181e05e3e9d6cb46311b11d761f7254d619e1f2854be04808c164249299a6a5f389ce25388958730dcc790b6b1bc3d3ff2ffb3dd619ad7eee3297836a063f2c366bc763559cd9b3aa0d92a8ea686e6ee3c08a4b68f668e1cc49f22b70baba754ca08c036ed3eef23f1b95c2220a9e16423be4b435b64846eb173f4a46921d3909598818b62699a1f8f9e027d5536b699a589eb755d6e10fc8d5617efd733542016184e4285bef7eba36022ed8deca38a66fc828a91b96fd007181de80e060697a90c571a78ab79828f12a9047d504c61f93e7b5355bb0cf827f06c6a1415b669081d6db00df329de9984f2729eb58860c128d310a087fa50e9f572e914b0399d4922ca8f1cd12f43be7ee414419f0590d30404aaee3ec522e6aa547fab25b717b17609cfcc7a45f117b04f61d3f5190297bfbaa29956d8ae09d4ef28ff8a054b778066255e231d236ec6ee4a79b41c63e9b8a645919742b259b608daacbefe287ed1a006d1f79585319f45aa37f4ef80cb5ccc8655b28e6f6ccb386e4401d2bb6278fdb1e64ca83b09da1af5fcd679b53b1ce1852c1dd66c6db81f9d186c1f32d04bcd431245bdaf5c6a4b0801dd83112defde6be5f043637533be372e836deeff73f36b9eaf70c845116209213877f5da31716d356f2174f6bd362e1afd1de1a19246c2c55426753a92938505f3025fc869995fd537b9b70acd667efd5d0d4b91097223d6f24a6d077e91a2b1164373c9ea81b366d8e6d8602b8e68f55a1ab24b477a55c41454b9534224862423637e1d1c5e2c297550260e8c8176a908ff14ac7ca1911839057d83d2144494d085e95364956e5b751f4187ca231dad721fe47bf84c3b06447e5458dcada56604b8b454fdb83526de79c0dc6efbc5d0fbe29cda70a7394584c431a71bccc645baa667d092e1384987090d78d86d3ea0f53e31769aa2d6e62bb7820bb56af403134085007e1244be44d81566762ad602b2fa62b4921ff81c05dd21118a2835eae6890580dea4140ca0f7c5cca768b1e2428ec8e556965979cd590edaa49fb49853af223d0c5804453861ccbc29c5dbfbdaed50d2be67ca2d15d0cf32a910e9834055583b9472fb62125d55755840cf2a3f20d448c19476a96102fab9844c81876989095ce9c4b050ce498206beba47d9ef63bed3c6142bf0feffab403dfe1b6925fbe7e3e3c6c93a923cbcd8a52d7f0e1bba5823332279e66fab528498880015224ed74f758d633d125ee805154719c85cc6a0f1ae89e8f7dc14f7da88aa347fa485568b57fcf44d2b0d2d09ee57c9f0e652de1f0084dfb83613a32c0fc5b10da77c7d6eb2d08a2535b808ce0fd40534ff66804c2964bdea41ff5543d7644c01488dcbcad01d84fb98e10fece4f1fce80ff5113055c5039bb82034b721d73805d834a953e1ba2a0b1d3a7b5b2f976ac1e7f7ee6d86cf8b5ae86f14580e9832d0c7127b5128557b885449d7d89e91e7de1c09168c105ce49d22443dd9a318f4296784f8b466f54e1098b88e995391cc32d42a6660b0c03ff39aa2fe32b85503ee92239653e404a061c18141c2e735e1404963086f850608189c6757d3f54f86b5b5e056864671e8527442e0e875a09b4e5035251f11ac7e854be375d5f3834791ff7b62f533800b707f2191640aeac57c5275daa8ae4dca495b25e0aa919eacd138d7dd8ba52fcce76852c255ae85537c17cdf90214f4628efb0c054c1beeca595aa6b42109c904a662ea5952471d9b22be7c7250f7b5bc67c9f4c806e9a12ea907215951890c0fa7e8c417f25ac76eca5eb91ba6d19cac8e844820dd19e55db035f818dae87588fe51fc5580ace9d7fa99e86db977f6aa8d495daa94605bf9dbab7b4b7fc3eaec1c088e4300c947923482a53592505a5ac753c92403a3f520c15012e864342be60e66071e43c0c10fb3b9362945d596a5291ce1bc92f92e0a909f7c0f037a4f92755c30503a4a738be37dac1d1daf5ba2bed79a4a76d5829d265dc5f5967f9dbc9c39c9f88529ae4d1727a0d022a432ef42e8e8e63ebe877931dc793af298bcca2e2fb035a3a311b9ea370e8d50a3f79963c01e11b1b6b5d6464b4b4b9b04f50609075807311b11ea2db69b0133203cbb4acd7e8ec7733722d4615dc5fe9c9cbfd70ab137161abb72106c6b2b8d8d7fc41dd9e37b328c6634e4d4cf70379ede8af0fc78f8d12582e263452cbf41c2df208364144f7362e3c11d0b3c872139ab1fedefd9359c3d270ec21d98e79d79e66db5da71734e0bc4e0e238c9dee4620f9f237170205c499c599b37bbea5a9f7da0679c70b3964e8c62afa50b1d7fd94a818cb8d0f33e8cbc0cd2377fe423d0c592c4d9cb109fac798b392fb8aac752bdbf8ed836c5a87b19e6857fea32a4ece9b29db4a68b73c723f597b72239179f40743ff196537ad57a64a73928067165a4c479c7a8997cafb53735326c68661cf5d4bd3603cb57ad47ca5a6b6760d7292cfb46860daa86e6fe64faeda19d7e329d9ab2264b189ec25824f526171236b559df96d8b714d13dfd3a7dc4f253eb6192b1efa516c4952953a6cc6d538672411306cd1a65ca9439bd4803e67280e469a9745def81d7aa5fa63dfca35f04fab9e0923ad3326ce1eaca57a95d07721a68befaf43b3d0caebc3c2cb9392b0b1f7db3853265cab47abee20b573c90a503de6a7d7c4fc53d70258fd1009242c7986bc47908c440f132e6dcf30388e3f11c8fd74bba5e15e4bda2b292c449d2ad1bb0ba74693a0604bb4ef3696ffaf5de5573e76e5c286cbad380982e8cba901e2621435e783e16b319cc6f4862d390179ecf505f404a9740a6978ec29be9d76bf066da8624607a093b6555d6c7e7ccdc47bc698fd46a42ea6ddea290d265de3421f298e9d883ceddd8e4af679a907ad42ddee2292aab8260371dbbe9d87529731a90fa2d669a175f731b4d48fd967d2b3d3ea787189c2bc0664f6f83e1d36bf04693b7a10e7a5d3afd0c0632fdf5c540da4ba76c836dd4b93c9a909ae598391eefcde0bad60d905a85d7554361288999ec693eae0a88941ddbda3ffb190db3187bc47e5da507d13e723d880a4901c357e94e43c1d928e54d9e6a3dacfc8f9e76cec6264f7fb0e6dc8dcdb4035737de0fece8c21baa819a1397854ada72694b3373ec2165a76fef0997a79e25cb4d7a4859b84025f74a1a124a7a48491706d2ee425b67edb3b9b2a04cd76a6ef3065b7b4dde609bb8f4bb4dbec436fbac9519c874edf625d9a4243026a4eb2fe9ea62da1e972bbb7cbd5623f3a6e520d859ae529a32d07b4fe465b5fcb258bc0549954dd2756d325ee5eff5974df22f8b8c1c24ed254f4fae2c6f2a64814a5e161b5396a48bc2d96bf0e910df3ce2d42dae8d04573333a7d1decc7bda4c967afdd855e30e996766b2ea7a599e02ecb59b4d29eca2ce0d7651479eb6f7244b968794854b963b0369b660d9600a105f7aa75d0b8824da064f7a00ed5a484cae74d6f4f1ad27ad205a41b482680561fa924453b3d78cca7e8c31c6183345c53bf925d37ad097e50c7c4fb1e91946f1c41d2e45038704fa120eb51f2c6f37f5f2f698b755d3bce1c0b2ec3dcb3010bcc9b2c7188ffd2fbbc933b6205843c88c1a7983ef25e40d471209795b61c1c9db99a091b7140f54598914a519ce8ae4c00e4fb36a1a12a27fa6b7991e0481a2c46acdde3309a64d9a90cd94dd74ecdbf55af326bfb4e9f03b3a78c6ccabcf516da6bfb2db929484e50a13567830ab9404f014a02bbd296330a319cb59de68dcf4fac7e452dd6a279b5f463ccd1bfdd384c42e4f01f43db1b30de7a66ff24bd7a3f049f3418fba89caf8738d1c4bc8246455de2ecc115e85934df49ae6231e08e6fb2cd298371586aa0c7a9b9346ffaecadb4e9b7e615a8f8ac28f067e4ee043a2d1f487a36a3e50cf6ec2f13d7106be4fe199dfe096816df0095fd8de2db5f1f4c4d11b0fada1b951679e06c7ea4f99f2967a79bb31bbebad488e97b79da6a171c20b17a874ecd6cb0ecf4e692b92632b9263a76986d5675856b12cbdc80f441c3856ab3f957a4cb0d3e768d3b72239dafe34835fbed86a3e4caf9a90ac4d7f4ce2dc8942c70a6ecd9012f3562447633f656bc2ae65e9e5e1703dea73a2e35ae9e525e1bc1ff114fc043fc11077b31ec9ddb2ce501952921f74901269494b9a82a3a0fa00c6872f5ab66bfda0840accfd611acbc7e650143c300dbf93a4e1732ca086fb1c51a49562e753877c6a8c31468a55f0d3a76215cc3f5c0b749ece7855f072618c515e05191d10f0d1276a280cf9443ce4e383961451cb960f4b5a1e25b1ea9dbe05a1230a381f787ae713b1eae51fae0578e783450a381f22177574e4efe50496f339029a57616610d4e8f72144f17007cc411d73ba396dc52a56b18add6119085ab8caa8084bd0966c8411f6c458f17661139bd8b7d7533ede8850cf4b6967068a325e8faea0dea01a63d4f8d26a128513ad264da46e132a77ea00854081b1a486070c1b8076ad26336852c3497073f02c003e6765a3446b88b9d66e2820edd99b1009c96e3520d96de9d99f668f6558bbc54080ea2f0b046dbdd3b23fad3ec340ef18762db57a0dd767d7707c2603a56fd7255661aa926adaab9e9546845a664662cbecfac98d4dd1d8af4beccaefba6b51d1a5b15cb23d31cb886bc0a2916c1f8c21690cdce623564d394bdfa8aa947f2e95cd3f57ab6afeb9fad1fc73756cd8af5df74423420de3b3843424212902abdd0ce6e64c0657286de55ecbc996f14049c7961062e818630f9e88d2f30ed5523e2bddb54a5a08913adec64adb5c7b3c4cea9adf3c1e76e97a394567a7513afb7d10a874ad9faf4b3a3e7e7b4f3809c2989ec1dbeb995bc2db14523abee6f1704e6c5e2d486cabf9b83d6ab299b6c7b4ec693f1eb8f1405f416a7ef393f682b8f29b76ed6526cd87a669da7ba016c4e6da4fb462a099c7bfedde74a8f9b89f353d6af256fa9cc137799be9eb260c34ef1a35e38106c2db0c053e440e3e4051c5113a58e2e39ccee99806c4763dc51876d27ee24f782bf5363f6383b71285a5de4a0f41675ba9af10f494bf58cb5ba9e72550049a0f41f7b8ae1db3c1f212cbb8868164b81ba599534c3521f555fb91bff6f40b5bac0a72facd4fbf396635d9237589b36b18881ee874cdfe7ae946e91653bc3d21306e34a75995ca3f436dba0a88cd4fb7f9e9a76737983e33d1f80db681f2333e31cd40d967cd4ff3a8f7444c663738a331f4c28c97c0b9023c9f9e3803cbc0291406caeeba060365c7324de60992fdf4eca79c232403653450184ac2051486865ae6cda4456028092785ba05ec429786fad24a87da853d8876e958297b106d23a7ac85b6256ca446979268cc85b6570641aa8352436df3364d3987f67cb8a5762d29581db34dc1991438fc1edc4d4627e1caed702d27ba347cce7b4470628a762d2758bda35dab8956afa468357441c3ef44155cd76a026906cc411bba25450d0d77449febe0be0cae5cce01c3c205a75d0b0b152c44606992d3ae85c504368490a35deb4a0fa876ad2b2c68c013348ca86162c2210a2451b0bc306961a206890c8819ed5a50dc40a35deb8932ad27b4340d37d5aef504151aafe266988678281d98eb1e98eb5fb9efb9a73d8a1befa26c23b557a0d14eeff01dbec3a701b90f0e01b3f0f1528ccd4218e77d70a18d0990f112db380077a137f4e21302361b091d8e406a0ce6290db7ccd1163494cfe74ba358e8a0685192d68084137994d45a48687139304b1ab047c37d872b26f08824318c4c9a54a894fac4c6a01405c915062bc28a115a32164cf1349c297065a28149026c1cc089ce7025108c88c1dde1c7826b2f4dd22aa20b7434251360af871eae9321816498b2cb521832f01430fb491e92e4c2254bd27b5f8eee12f94576915ee41796cdb04ba8e9a7cb7943399ca6df9ee52da7a98da679dba1f43b2d557037d932252fb1099fdd077c267334b5172d92a34bb2e7cecad51aad2d3d27c2584fe773a4c4192abd4984d42a424a8b881a72c82e4e7e99a2e4ba1611442d28637aa65d0b8a1612ca140e0350a8f496ea2823928e2d28513ac28e1152f8e66db85bea3dc29d245090a4baa6706aadb5a2ee829bc249a16e80034f01afebeb59a8a9c77232c49876ad21626803b46b090145cf2925bcd1a58fd95b308488059e025ecfa7b0811984ede6208399587a0e1ef7eed2661795c2813aeacac675e1588a314208772ed933788561275c316d6df69da7715d2bbda4f5d04a1a967de54b0bf24e58f6854f389a606b353be157c3c984a565c195f7367c3d0db650a774ed3048ca2e4919c40165ca94e90c7b09cb9697944d71bc540b1e54966198c93dd716ae302cbbf692865529cfb9b457ecddc6cecbd78754d175b715ca597aa60bbbaeeb8f494379f0d042edc2f6252c9bcaae44882092990b57cfee70ddb35fc8eb57be8ec3bdae6c73ce0aee96d35247477e15e58e7bd9f9eb0fa9af78f90b1b101b10c37d6003e0e55338cf813ab2a79a40fce91580278221fe945d3c0cf0a63cd417de6e5fd7e9f5d243ea4b5e70c37cd46f58de721a7bdb694d9fa7cf1a6bb319ee66a3e74bb7b143f349f3399fd1cc600757f79d9d707613ceb2c536cbeff1d9845c68d98af0386c830e3be734325dbae3e96d28496bef81af94b50c941da86e3c526e59de783afb965db751c769cebde4a096b7ecf53d31c3b684371bb28a10524ef3467a5cb74634700de9f9886bde5ebc94515a9c048d1e32ea76da8f2b42c74d7e6215907a5e15247e3ee699a4af6bb7c140d76d4e83b7eb34422ce9795b6b666077ee469661bc4921335a8cbad37adc600c5789d1bcda2138106a14703e3838f805d54bf07c4a9a0f9bb2f6a9d32d9e313521f3b6a409b98ed50c03527dd0dcb91b9a90f92dda675590d79e48e3aa63d91dbdea97f2290c6473247519189e8137d3e55358c8bccddb25e45e66ed3df1061b790de47acd65601faeafabb0c540da6b8ec25b8d765a3a61a00baa7edc1429338bc9ec18661f441b3b8265f298bdd3507036ac0ad662a56c5e06a9116740f559c2558e1d1f2e6cd712c24abfd73b21326f42aa1f27bb074ecd080b44241801ab9d5b39e76cccd84302ef3203b907f31a05fe428d2f361feacc5973eae2a0e63fc6958f6f074ae181125278a044114ad0a09f03949041aec04991e161ac60071c84b0032423a66c41e6053f281a020a2b8ca82106576a93304874a0a530c4d45a6b0d03a9c700c38b1c6892deb502b11c4428526bd702a309183af43b46b3161848ba5deb0b2ffa3d73587c91d4ef0f87fb962facb46de78593762d2f741840bb56175f1accc9b5369e2fa034ac4143db90062beb6c50edc776ece86e2345e70aae6b81f9d29b6b7d85d79b9b12010a01b8e3f938b7510ce9a12681874970a5732ef7c4cc93c3e5e9d8031d8f232bd89e0fa20d9bc23713ff9a067140cf6cc4a1c0448787c3d11144f8dec562b158527c9c95f8acc41d72c9afbcf1bc94b71dd36bde56da67de9ec2f269db3165179f6517e3e592b803f2f0ecac564851e7a9d4bd71c807c62b8e87eb78ecb1fa075e69a1a397afe83c2bafe578c8c7c97aac27f4583e3d51273e06d5a6b4e22da836cd403d6036e2ca34fc0ab561de864cd0d7f9389f991fcb273a31fd2eb7da308813f303eaf8f1450ddcca75fce2c8ad5c6fce071d5fabe325d629739d9876bd392074cc82480c95322d008386964f3422082dac5c99410daa18e1b38021a278f9018c135f9af80cc9a087317a904118305b74e1e39830410d4008830b1c6060c2672807124831c31258c45c31f289df893b6a28f1c50d9290a2046168e11385f025081e66c821cc12627ce27be20ee7b3c4da00737063c2075de4604a1357584186c905c6548141134038c183146066d0032348f1828a11acd01eb00bc0b4c0d8002649864d30a7c02c1c82d0858e9b0a2e34fc7d7677c3e62dc2fbf8e0cd2bbcae4060d7256d5f919a47c71f982babb7781924011d33bc096231ab2d2fa234fd94d236662f833fb52742ad0b2b5d24b5fd4cd44a874078348cb9033cea67de03abf6536f5541ecebedeb4c29c876dcea4df874e7780015e1d130778047c3c37c043ec874d34daff74e0b62e3c5d9eb863dcb0e689e5e94629cc3e59cdaab48600e621cb0121e1e7c83b53c191197da2f2c2e64687d1122ba2e36a6c08186a226556021039fec47f81283dee49d10323998b78473b2462f4fb69022021838b183151cb4604b18ca05ab29e5c104247cd0c30f5228090306ff2280762d2f58bc38c145bdbb5ab87e3a865b382272651775ea4f6f016a7fd7e5895c97f0d963167acfaeddf4135897be7ca34efde1cd67ee6f7ea1232232b01379c7b0bf973291d44f59e8f4541e1275ea67b2d0119199bf2cb4136ff24c16ea893af5c22cf4a8532f23d788b39bb0267f9d882c5de85ebe9489c46759e825893af5e59b853e705d59e888c87598851e2b27113a22f20eb3104be88808fccb42da614e21ead40b99eea28e3dcd4ecd9d8f4bda1ecbe6cee755d45f126983858e86a0b2d0f3b99f790d6e01fb751adcc21111ec7a0b27b066f2cd0f29ead4fa1360b8795eb98579e7b33da4ae3f611f0d3b22f8b7450c5b2c69cc41074d3da886d057e0f9ccfb486967c20d8e8839b258728316536ec0be68f1440b56f7002ae24effa83d166c393836bbffc0d53477c07d1e02b93a70757c3c59386908818ac4673c2f8b25b00332cb11ab5d86f34527dfc2e5575491f32adcb1e0e1a9750b22240278aec5a2b674c1228a2eb56b6551830bc06001a6bbf4fc0ed4e15a5cacf47c8e79ecf4ee8302cee7fdcf65a1a321efee2f0b3d9f2144f5563581982ba09a407c3d9198858e88940e1fc3f38959e885e7035fca442570804f7c0ccf07de4a284d3dd8e19da8337f44240b1da926f0ee4ee4dd65a1237a21177588bcdb2cf4f2e3fb09aceb7c9c8f238a49f208f85a5da2b4b4f0c1b5c8c991b202b8054a0c395b7e3841cead90cf426bd7da52842d567adb9964ae486a5d71e50a2bb75deb0a2917f8c176df5deb0a2687e25050e928df9c314a3763bc944e4a299d8b0e462833088dc40cae1c7c3146082184114a079d6a4a28a594b01d6d18e3a98c51ba28a593524ae7a633127bba197f328439c3cbec6276a587156da71b1fbbd8ec3ae79c73ce39e71c640283598c114ed1f2f029e029607b0f9241b6eb83645f3faf4cbf573a16238677743edadbbb8bda57caaa2032dbe2df679c120ac97afa57a7caceabe894703e4bcb74cd5a98cc5867400592be9e5d94d64a6bad95d67929b3476badd96b0c487e6e0a88235a99cd8b6adb7520094ba5286ee91008b3d737949412cb46ddc8579c3261a7931664288a0c657a1b8ac262b50ad15d79932c56d77ca4c56af9a0cb0aab2f54c730accd4961b5cc321e8e887a38e2dd11babe074a1b7723de692877233e0817caf1b81e7f695041ae3b713754df743c75ee062ab60d4ac68561a36c2e6b5e47a154efa2b5a5bdcaaa3055e9ba6a5ef4c97ab9a905a9af5a9454d1a59f2664284a1256ab30940412abaf4bed274261b54462b5cc466218565fd9943d1cb189fbae034d4d441da13a4a379349b823054b4e3abbbcb45e7bef2dddebde8b5ded66d744c4b465a037fb6cc6495eafb7525e97bcae96d8b177954eabd6e31dddeb0e5e19bea694d259a2224a679797746a40689c40845db9d7b143202cdb20d955f25e5a8f7ae18dfebac52afa7a62c54662d74bec5b4627147519c3e8aa91220a298c708a88785f8c1c155a54e1210b24b490e08a1c74b8f06004152040c1c196115841bb30f16284c516464cd860c388d78353d14d0c8c5c17b7680c235c444251038260a485568485110e53e460c40054d10c8a6024a36804482f08cecb124ae603838c7be2065188b085910c8ca2aa688b9c450428f2c2281761792fcc99d19e73ce99d9312a16b7145f2ae59c65bacc502d1ccaa11e75602ada297a55ac783368a1c2c31546618a0083910e8c0e90538415392b84784f94311a6384008963a6e8044462a4f430c5e80da10aa31b3adc0f9c30d229d2c208464ec018b92c460228aa3754c69420892a35481aa308313738616224051438f8d09466c1a484a21bb2307a91109874f18ade0eef8b295cab545483918bc2c888a74807ce0a16231a452a302ac228893382018a28a565c6684ab1684203a31766556482ab62534485d1116e09288c5c13ae073a7c61d4554c1006111064c25062db52849431b454e10409583ba05c6a30a328082ede10ce0ba305f45b92c3ca00142292c80198255550a08329587c40022c288841133e3c1881134db79850a68973ce39e7dc73ce39e79c73ef3b1ecb27e20073391b0e2a8cac35c61a638c31c65af3362505a3c47570f562883a29c8c35ba0fe3fda7eb3f5b122ae9905b87a42f2722d220eae6284ace902cce5b8978083bb2da0e3b76785e6241c00720139f171893b4c78cc5f49517d3b0fbee26e367e5dc78fb7c07be278d45ce7f129c41dabc7b3107708e03100a5c33f262b27253af9b76f4f49e91517e1e9223c8dbd8495ee0a95622f77bb331e8fcdc0db6361ef59c15a5cfb230243a28455e28b9a91b799836ad74cb3145ee11e0b7c800521560d0d7e4a90ee09bf24302b2c8000e014a2ce3b00f0cbc16f49d4793701678c5fa3048c63a373628c3fe006a00e9a02267ab045bb7694bef75e2e41895b3a8de37c7b56548fd9e31f986705fe71795d3afe07cd4578e8b196bbd968a8c12ff95d1e8b852bf488dc8df7127e4ae0ea3dcc1b8371f08b41659f8310420883b69dae416f0c18b87a6701ae5e17cce5c2d7ee48c3c33b74034f3ec09c1621c703031c50e13c81d9b883e8a83a22f047dcf163bb3bd9c49e82e3114df29af02aeabc67e77995669bea81d8853884410885cbae1dfb80e321e46e04a1697cbb3378e20e1e0c0cb1d7b0ad9587d67a93dd7eb4c934435363636f527790be044c027650e726cbc00eae6c322a6fb2ed77f286d3f630a7edcdf599ec6d476a7833e52c0f51e1cfc07107c45b6a984c3970036914523a2bce39e79cbce29cb33d7072a0768c3b608ecb9c938aed11b17989619fd385c159dc2bebac62d4c35e9c21fb75cdecda5b2a1d5b614df64578fac21576ec100575b043ecd9cc8132073ced30cc5a2ce3b1cab3e5d83173aa65a77f0ed4819d7e45a9b5d7329cc23970656d0a8de5a0d9d8afbc822b7be5c395fda954737dc51a8c449895622eee661febb1c2740a63912a5dbe23121e35c3b0306587195816429d152a4cc204d602faa19e794efa49b38703da234056802f7b38cb95b607ce9265b65620f4311e81bd8f31a65d6b0ca4ced1ae35468c361280eb1c8001e03a9c63c2253a92f359422873bef10fb0745a8ab2d47e5a3b6fad95544e7ceb898ea6cd4097e1299873ce8903571888bb65fd4961ac71775280fdbc2e41555170556feb65cd2e2a75e4b5bd8551e64cc2253a9a2f95e6b4a5493fa79db6c612603849c870b9407c4da090d84fd2c32019c8407003c97920f2f035aec0010d2fa4c75a6490c0431b4977b3d1aa998dbc8e3ff23a3ef7f0d5834d602a1a33eea6b5f7ba9cba44471322203eda1f2e8c62eddddedbd5f1723a97bb116dc055fc8e1377b37175bc8d9d1c3c0bb8b901f6f35ac68db7a133bb9f73f6506daecda9887db8d91d52818100c35035d7dd519a9c190238e72e11e7f28900262dbb4e62d7253a82f129ccc106c01d0fe6d27d09c0ec7592521495184b1bdc2d6b0a53f8c86bf9430963bdf34447f110860ca7630e5c6120ee869373e435bc84b9072ae90830dcc49df7e7b59cc1e27592cc95762d32ad2640bb1619254d7f6053145ccd53889f5c89517ce4353dfd735a8f875d8b0c92761abc4447f1f558c5f7b515c7c3e9a09f4d3f067bd7845da223a223e77460ae4a80d2acd6fa9ce6031a61ced9191468f004ae5c6763c0f46bfc8ee8f754880f1fdd0c5371659431c62bef0fbe07df7b6fc77d7763baf483ef1981ab983783f8e52d6b183fe79c0f1e5701658e100b7aba923e6a40e4e5b1392790cc370ec9f774d06408284b601e0200031149556ab0840d59dc10ae6882821964f1c31739a045381973853e1983a535c64a8e0e592d8d708ee9414f31b264920b964a425a018790ad3146d83a8687a9431863c2484189cc0b557010c30f7840854f184dc520030021868a9650a962c30b4d5441c609122f626469420c2b45991852daddd4ae25065267d81263496fb62506927eb7485a63a61083456fd0f449bfbb777bd345125cc24c5102871b681037bc20c2862e3bd8420318e4f0601bb00d3c928c6572c5287291469c9b6a411454ec80c40e869842c30e55bc1082881c6011a28c8f94456859a3a50d5a4a99ba36565ab488f1222ec1de10efd58b81aa6286d05bce082a9812d41fc4481103859896944e88d5116f8f861bdbb5c444e9c78262a4f8e0ec0b2d8dc43869d70a23a5379c9e615ad038af3bec039a34a844ad9595492c19aa11010000003314000020140a8644229150381a11e6a4ee0114000c96a4446a4896c7a21c46511003410020c6104208000018620000c62090d20112f025231cd17cca3aec304d174d2ba2afe627f9d8814b87e87518220d6d71901109b4384a421c6dd529d4ef8ff81119a839b9aca92d6681ae4a99e27cc30ddd07838696aeb53aa9bea4bf0f8d4fe9071d29d2456351f677e05ed34af4ebd82507eee328443a6d0e32893bcabea6350d297b0e22acd2f0b424cd3d1ae0262310698083908548839b10624759d6694c14b2e320c23a0d4f0afaedd370cb041d74d5811074698811a8e018218e76b6a8001da77da3d10f32f201eda774d0912b0e7be390d6f14a8d49718f43adbb688cee44658731e4e0d6d1b5a35e47a5eb20718576a6a511edd3e2904ccc31d29af674799f83afba1ca1f46404451a10254450d0b078298b9286c5c9084a1ad1b2220e1a8f9b3149cd75a4b3a0b29dd8efb2042af4e7448f1fb650c56797e090805a59d24da1bc7db500c82c6f8066cefd445d982abf889973d132b8225ec80bf93aca3ebf9216a732ad837b0d57b8cdab092ea3512eb452702adeb14d3e92c25a59cbc3b15b79ed95534740a2d6c5cf0fd3f5c625c85650c26ac26321114250a02522a9ec188dc5d2c820077a7cdf816b0e7b3ba477bcdec1f53b0e294693510ee8a0371de7efa67deb42083eb43e241f738ce4a22d6b42443f8d8f48c78e6e72b4fe0e92e8b4cf51d861226d0e2281e3550eaecf31428c26a31cd0c14d87ee0efb3b1e4d07092bdad310b5eb28e4551a13d24e2d874410475d75945597f6456464251a22252b3b8a144f3b5354e00e68bfd3f896f4c1f9300707c84913f7697313436cda850f2e867ba205247d13dee4d9a6903db84e46f2a1fd2df3a0e3345cb4674946f6a7fd940e3a72d5b42efae7d8050efa1cd276b8ea586f0e1e8bc390ee34f0598a70a4f11921c70ef473a3010b32829fe6a37c8083ba769c769ff64bc9c80d1a6525371c490d66e1cb6c2fc835b18186fac2274773e190c43e9a2da57db08d8f7c98b2d21f8faf7a2b873601a79014a09d1599011c64bfa5f5218fe668ee1c44ead36c49316b937649b111c7a3e9b091abb4271177e778ddc1753ba62d46035969fe028303ef86b68941447fbaa1b36464a45dda9c2241c7eb71607d1c24251e192973bb865cdb2fe84df4ef5cef3cce4c41e75b36460602738d97c4ea3a85cd032db32f3c6249622d02f671709f5914db97fbd188ed2be30b39dd30d8127e19561accd796fd61d89da5956109e1c2049087a2be6e2fa7118da7c16162b35321e3098a8883017132ebbd3e123cc0368ac73c90189e3290e42cfd29268e0f321f81846fdc0f897ec5d270a209b6640939b3f29829e13a1a520a73d92289dd5c9f804fa085211a66da6e56956243931d6e3dab0415110815f4d7a354aaa4d9dfb7c530fc378df9cc1b6b3849514abbdc93e89a5467d6c59fdb837414718d4853da856b78c1178a6ee63a59ea7b7c0c9a8c650caeef34f3e9618608f5ecad291ff8b74e7f36fc5a9040c5df3fb0be17e1234f6d2dbe8aa49d4e7dce71ab2fbe9e87b82fe4c76b35f09d4cb9f560ba47d439bfe30446f0d0aa987fa5374b9b23c8d0e32d74b343ccc7be61fcea1816d0b823a5a2ae748501ba27df171f1c80d568a07bbd6110a7206c0bf8e9601e22ce61201d9d037405728561aa83e25214a69ea0d107b20920d2b16b1379c4ee6c69bc0bbf944f23809b8feb4efea5b755a18d8033398e0c6762aeec0561ba23a6df6eb84a98400abfb33809567b239b314b52cc72af8c139382a957b840c78c60e0f92ab7f3713ded39c9a0edd447d860deb07d036a21eab127b6bf3637b52961409d2746e768bd69e08028057ffa405f000e75e97ca4c61a9b68cbb756aee060f7357bb2aaaaaca20000b411603600bcdb3319c8ee4152da1944631297ab3ab918df0d62a4a5cdb7b1773539394026e68dca1e357135793241a12731918f537c6d98147011d2c3d53b9166220b9c98b87135cae02e17db9e04fbaa24e46c4d84a85b44f29a8ec2576bb5f73b2ffa80cd3e398156052c11ca6918e6d85cbad1d9d1d7d01bc3e9f18cd5fa3939bcb06586797614b34799eada711704da6c807061ba529a2d34d5e6caeeeb97f6d53ea8636704e0133dc180a0f41f8250905177923053ebcf1402ef683bdc33b2d7c609fa15b69e34679f4471534cbc1472d496ba964dcc9cc4450115453a5fae07fabff9224d41c6bebe5e13041f0d31723541166a69769de6f75370ddb30e9edcebcbb33c4b5086730304edcb22b557265350a3de5625a992706fa1811a491238b741a6f08b7ba653f8c9db7ff162cd9770d039efe099faa5e7e7afe600c8386cf38b663e9380d9c9008288c77f297ab612df9deb74d9a3e14caa0f44affb753f9b28e0836c24efc0792d34fbcc0092f2dcd6b10e003de3d2176bee9f94a481f811fac2da21c978e37f9e67189925cd4041db1bb4e0fc74d3c7a2e9347536b4c8d29c5cd5571e5e1aa7552d1eb5e00f35cbb300eeac3a40812200f554ce3d81b59c6d55e29c53b4bc23432af5c10fa053c8589ccea38483cbc59e98ade4ecb26000f5e30c09b80824b37a037886f3acdab5004c3b55e5f70643e20a06b6f2f844f9fa3405918f0e6e4ecf24c83a477858f64c0dde4fc9c011b3581ad75ae5f5700463f43844997e680e1386348ab6e2bfc68ae577ac988080c0b7c4d711f7b421d2e1cfc4d4f3b98164608098a37b3c965a0baa5a07d4b8810af1bea2edf9b7b08ac8e9a0bf5f5ec82175cf6943073a801c2eb51bdb0d9b82f82bebde1fc7d66d363ad91f3e5a70fbff4176812dc7808c990ce210cbae71ee166f8271263a2e8e3d7d43f6e349643fb91b010629412262160245124a426782b3ce17514ebc1b711dfa25893d66ef9579ebbacc157d2b671f7ab135ad92c8797bb9563a3d73f6daa671ff9c045f4fd9259dc82d8f929e63d4520dc2f0f8177787c64ddffe2e8feaf2727e4f28c233055ac469b1f10bc2d05b6887b3e29326e0ed31c8a5d4184f5b2330360ac18b424620c6dd9e66373e379e6a8701aa3d076fee29aff73cdcbe0ce0a54216fc397bab166b499cc81620c35bfe7249a0212ee05061da1f1cb9bba0f2376b4cd86441e87a8ff99c27c865225fa0748a3f42ea848a0c163c65a5c22229f230c196780a701d0a6485fca18e6642930ebbeb6bea7428710a9d7036428570773031bdfa8382e5dd56b825a6ce752687d256d739077ca0fd54819e3557da5f9b219def147fbd6841969898161be0c6dbe033c6fab206e27c08efd0302d68777b4de616adc9d50f4e7dc3fb43ca2182fb47607a6b461313f68fed5de0efcf288b53a71fc5f6977b257771398fb2d546fadc25959eb304b4d3aee6e7aef90f66453e1355b7635e52bb81d3ec5be56acc7357d2d8ad26439fc2cc430601b6fb470e2a8dc82e134c040ba1ae84b91576c0a6707d4cd9e42e7aa5ebd53380a8bcc47c808a270e54b88b9e9b9800d54489a42edc95edc06acd894aa11a8c0a6eb8aff01500538ac453356e1580f93d07f9a15cffd2a69b3f17224f8c77d47e92d9a564117c2574cbd08f73720116f40092bdb326087dede370456a7a137231716bce008a580bc3f687e2623f4ef0c11b4c1271ea831a2683a2af8d2d6fb23cbae533a33348c160f7dec8db61f3c0e6489795c970e4c548741558b40b532dde65f44681736a442a0f03d19034f0dd0125b1f8786dc4f1b144a71e0abdf04c5bd77c9a052d947032752280758965183e1a243c0b8f498c032a108950768cb0f95230dab3086fbdd9dad518b73acab452b5d0106273b4faf02cd43c5df9bf11ea2bbcf2ac9670d807c3eebbb0f73360e54ccbb269808b5051ca4993df434d768f88cb36bd3304f31f3b9677c264a6da17b55847cc9d0ebf18d9fe2a9aebe51867977f98f83d495f530457afc25a64e556317583b05228d67b7607717ac193ca92f0bed673d4f489a7118b4176d94b6564d35be28abda91e6fd4edfa9227146c830675e1f9c9641b451aa8857762b7b77499feb4c3a3692a20bd80d70102e0670afc174cf98554896a4b03ae5531e9334a71537f18364fa6f09375d9b3a428bbe4aa9444f9bffcd6219ab7c3247eed8f1dbc57fd5a813427a555b8905f193c91339352700958e9f244d9f92717bf9c12cba2f7f7344c725a8c820fd003cf2f4029910d8c20607fb5ef89cee87c64185da3694df063547021fe9ad7065a5238cb15cb5e92483a23dc3876b09a08c23ee995a2515e6dce56eccbb79567d127c97c98e87c052c2afc3326cac0c36f67dd07e6e2e1afa7df2a3fbe33903290e6dfb3e7af9cc5ffebf17542ad0189578566806a621815575f1fcaa79df4b8aa0f9ca6999e595f9e3d3726739dbfedc42085df84bbb8a9428201248d618f4103bdbe378853fdf98cad13f6cc84da980c218454177b78d17fe4a5967ce0812234298181f64a5f27d4c67042705b031caa5eaecb4cd933f4080bb0a81329fe20633b44375b971eb23d16df0670b67aa341973f49d77d0360c293badfc27a93b0a581e3ebe7f5003357379f922ab032f1abc40b13bce7117f3d7b7639e966793d369d84ce7e6fdf56e21270c9809141226ca614e827237eba3f6958aafeba242371f14cbcfeda4fcb7d9f1a25431d0f842052f79668436b563358c9fbe0e1afd6164930ab4f86d6eb804bc7f1e23addb9b961bbf81c2c8772cbb485f5e1d114a17315200db1d4812bd68071bf16918ccd43100e51a057530d0af1653c36bccb07ec3a84f75af8cc074e814ba8a918446ed460381532584add66c514c7313ecd20b1ca0131999e6364076da0daf20eb0b6a44f1d73fc6295826948dce730fb70e8a90efdd800a3991c286af051c34ad7f68def9d7599bdf3768230482912223cf858c55bfac7d1ceee28634fe15b93b7173e11dbab6df3dceccad7e6015ff3626f4c5de857d042395ff0b256a181f8b68cf5fc81089bb371e928054da8ea422d9d882291b17159a5243407296aea1c7ca3090ac5621ca69b623fc6d5b72b10bbec913a8d479fba9378a411acba5d61b3007bc32713b129009f62d8a8b1c1578a1f53ed91c7b72b3c98c492983b60c481133a1ce46760c473717f4d8736c9bb152661d1d2913ebaf757855f405962dad3fc2c8dcfee2c2420543ecb05d30f22baf984ad5c119e71634120e79d6c46b3e454e6f8f3ce1ecdffcd5eef54094c94284b7eff6a118ecd30c5382c9086dd56b018e10b3a3d230b28426f5b8d9c696a3d0b0638e01996c0be9c32a2de999d15e47c8f963ed7076717e713fba7a071e2b50c953772eb9ad10559c1d1e56fdd59f16074989b0a2843c9ec0e4b87035e1e8330c6b79698f9784f4ba013f96ad5de2e961a6cd651d740c84a2667d9bacc0a4e451d903e412631f43bb3c432a275c731b1f21a4da75e5a2ebfc245618f0912414bb40b55696ea80e6031bdd42176982f38de8bf64b05cf4055495af246adddc38638e4468849d569b9841570a76deed0ccf7960d6f254c87d1e62767d8da3c4c0520875a3ca52b84a23db139af6302a3a76ffbe365bb610ceb3f4b89f3165730aeb1f6a74e62b9e7cf10e5f07b0e184e31be417a49ca5bbe8fc4c2ba720902ac032de73e4c3d5869bebd6ebb980cb5941ca7486a8565f85a56cc6b2ff12c7fe8cbc58929c99460aa401fbc693b5af71547d193dfbd52e78d60abb28128070657a1433fa0d31039a610d9e66d1b90757ecede92ed5c324c65196146289340ee74382d08eb86764e20712dab2d17c022b0cdbbec10debc0f0e9f0285f4b1410c11756d6113cd269990ccbbd4216a9908501112525bffb8e6904975b5166ae9211d05340c0e1c75188b5e1ae53830d7c4a09f826e1112e594dd616456280e89275f00ecfb29c129ea8413b77db7dfbd226a8b2ebbc80f4d9446f0532bec693a20bb6243bd17a3d3bdf76faf10b9a4a368d4a1bd1d6cd9e3e9bea9be820aa2681f748687d35682332c419a873d257104474d04ff45cb350f9256c0b6fe64fe6728a2622985987cba8470d42e321ab64de3c73941df17606daf837af3bb6e174e0c6cb90ddd3bc744dc0151fe091e77d09551aca295028f069dcb6e210d3126148bed21392556ac1c5a96fde6c61c7eb4390417ce66ec56a79595cce0706ebe54245bbfdee4b439395c38479582ef696a112cc7aa6055f283f4c7b2254fc7e08dba8a8b68eaa6f209d76da541f85f122ed295de2b1893a574f12792666b2e01053af87210af24974faf63017d040c60f65f301f59251344bab2ad4e8f52a51a59684835275d96758207cae1d532978746f4db2a0d90c16bd57506de8dcb82e9d70aaf680b1607f069da73a53613c06f7daba304a984d624be4a124d12ca04bfa13bf1016e49de7e475bef8f9e34c90387e1f477bd7f87b28e58aed7c5051847c379f9fe7c7d1385824b2d85f952c41ae8594be6ff33542c5fcec0283c5b665842d4439ba238b3248b8d3c717e5a5392021fd56cb7fd6eb118115693090f32fb98f627e82d20ce6b87c3712c1d8b6b016709e02d09438a9876259fb3953b2eafe52b08142b130fa5057ea5499d64bc932622d2eb1927393ba4a032236823fce7b8c024919e1734800a3291e9fb52aa8f48677addedc086ef10c3ea547710b8f71bdc7e3936cad75279d5a9fc8ff28bb1b692172ed89008e82faabaf59ef0cf379e74f4468db3fd59c726baad5493cea32ff1167d45ca0a0eae7ba9a9cb58c1e046651d3471a325ae6f05d509dca3a3fc93c8981c758dc3da3110a3455eabb275a9f9791797e0860741cd2a167971e4e7c16cd8d39a3c42349406f936afa94d669ce0000ddbb5d57a0a51b0d8c2cba92ba16b7ec1ae6b96962774b2afdea4721f1e4536d13f2124a13e29917cc06b25745ba7542a5c758e01e02c5eb4e478cb27ad504476827559fbe74ced118e282bfe6718349004233a00d564648ca56dfa3f7e7afb6a425c64d5a5080f95b704f6bc56f2da67a1bde0c03aa49177d5e4451ff55ad165519ca518c83b09736747a7bcdf72759bd027d6c02409bce0e62a66738d24ecb8e195e097bce8a299bd3673d58d584b7cde0f5a89d8bd9072a9b0f2414747e5efa43aa843e2e3267e8fe0ce9573dd2b3b6520407cd508ee08b2f4e444cc496cf11ff4af2dbf9c05495fffa80963b15438f7d8c4f7f337e7241e38a960a858907f879e8d73b2bdff6ccba6377738f11271aa93413dd79bce3ce08a87b464fdb5e927034f9f76138cbfda1420b0886044ad97a5be1742acc25890ba2818dacf20dfea0ae2b6c96a176879ddc9c191848508884d2c8a900f294d519c9c67d3eac01dfdc9b460671736fa0dfad9347b9216ebd385750d860e0ca57798782d24a563f63104eaa0307224a4f7f4011d421703812c05a894c7c10140b88008c04b8bc91c3e3f291003091e8134a10987402c8d99330d11e05200b37c5035260903f63408c4945f41f8b85b2fee6382801146f9737f48054794d63ff890e221f36dd4fbc8f11e3dbe2894e13ebf3bbd38af07c5d878039281a0d7605094cfc53bfda4d85582fb15c3ce11d4b3b2b239eb70004ca99f7b7b273c1fbca84a85cf0c72db799ef56e20350a401272fc5b778912e65f9954f50b37ab38ece4fe2b4e5bfaba78345524d97d1ec809a0440582025a681b4607247b33b9e904b3b2402a0f409715674c110749e5314da4cb9f5a4ee8e820e5965df00d1957d7764a11ec8e5adad2b89f1efbab3e4f5efba388ae95670c897a10a23f9069a8c4e578ea118ae5b8b6ddeb6dc4e412a8b43c338532e1f703d336c08295224a4b7c2459ef0a10bcb187b0115f8febd85ba4abf5ccccea00aaac9e68956fd9fe0ce593c7602aed6f61dbb2ea96780f87603411f66ee13974b3ccfc2e3b502395df4be6bec829b0baaf35f66f53f2ea51987a7ed760b1866431926d9c8cf694aa6bf505330f3cbe773ec90c3ad86031c0238f01e75a125649b1513cf0b6be2d36698ae291ce57045bf0de0371a90628f98ab98b01943129f2775fb0b05b016e0cea20e7187bf497b6e4ab3e35eb7b56f144338488b1cde2adc46f856948542a350125b3bf42e33393657811c3d6fb7b271c34dde65bda8896514bcc7310116c4dcfb5315f689b87ad2078c39bc41487e8855df6cf09b42dd40707827d18afcc29c607ac2f573fb933a4e43b8d0a592ff20d63207bdd3aaf0ca9318ae6578b970915a452d4df05e119a788879aa813ab5a4734ef24d07b0deb0370cf5ba5f6bfec3ac9c4e2321319463c6fb76ed5e70486a86b26ae416e0ad356c0c1eaf56211be5e00118efe3a03a7b753c8a66f2f34dec8784c9d5ecc1759efe61455f8d7dc1a5a88dacabc735f203e157509f9984f1e259d2f6ba150198cf06aa99df89fde90faff8814437541f1c3422d48f962743386102c56bdfcf18f1e39675500beb8a2e2f181de95baeeac00bc74f945cfa6ffbf5b69fd8c8a7d9dbf90e1ff5d6f4a28be00c9fbc7894717523960b0284aad3b22d0c3f2971870d852b32cdfa51d5b1846c124d824ebfca3300383bf2e053a12e8a63b1001af4344f18535c2510858fe6d2db712098ace7b1ec697230992744f055757e2784dd1e9ab6b55bf2137979a1cb9654eeaf966a788d3cd8eeb9d0f695dd5f4aab5872a2c63d3ae2db0d948ba218893f29391ea99c60b16289d9648722fc5bad38bb236a67c8f32e6f757218b8f53eb40b8304d9b54650dcdd14d07746699bfd127c0d0df715b93b80b8658160ce7bd0802db9a0b0f511ce335d9d2cf10a912a084714c4394282a390ebd3bd7f329c08b0fe88de15798928bd400b77fd8a3604b34cbd7013f5f2bc04f951aa5a99884314405cc7b86d3c9c97b6487429776461d321970e572910ba81f7c7d2ce959e77be42979539a4a1708a40caeaec04bb5fa57d92193050debe2914364346fbbd0bf997066e3ee6a33cdc959068b8787cd8dc168f416a56b3393d1986546f55f74545cf96290357f4358b79f146e722034b14dfb32a4853961989492076b569cd71e177a6ba8963edb38a1c283a03fd0aa80f671d728dbfa35b77475cd59370cf802820433a366739610cf7f6b51f88cc9cdc399e56b8afbda0111625179b097bc963f0c2d353b5018320088d0f977d2b4420a29a4802c7ab70160ed1b9d325cec2e117c468b4abbe2499985612b3fa4af56000d73899b2e4ea35fd3cd59edcd0ed3437284fb85f188d437840e9d7fd138ed301390b21e097f5265d35d56751c482be650dda09c873904349b6ecc99aed4b56068fffd282d905521a66b5f3596ec7fb335c5da2799292a777f634650a0e37abea33df4d351c891696f882ee775f45c4ecf38d5b0c50a3ab1340eb3b3e6083c1316a118cdf79f8b200c457e48c3adf94412bbbabd4af89e88b50f1d555bfe69f12e65606526b6b48cc754e29de4c39c1e06a2972045e5f8d3081608cb89467d936610fc8d307a5b0ec3d04ad80b9b8c4ecc2d023ef2ee0925e36e6acb7a9177b558a3b800fc02d737043332348b9b6300b7f7900e8c6e43d49c6145dd3b8c2e61e49973f0679b62168b70ca6fd602af53bfad76a586aa7d8911f5231f4410e52d02e027995f46b9185dc039a2ba3c13148a5ddc01bf3fc23999d7192adb5fab1adb50dccd68f6482d3047461d4f946ea89743f36a5099b3b0bf7ec9828a37b4dae579f435747c21f7b94633fd7058453472ef80ca5b72e6230a6d132ab73703a7c464f5175538ecf94005979afdc218726558d86eb298ac4999bc00958ae29be35ff7b71903fb68907a8476cdc5b6a06cf9b180dbea925c0b3206593ff4cecba74afed4c270f8b1efda36f82986aedf13edcc3207492b92a0a1896f0a9a26d3372be6ae08d2762a5a2955ebef234b747be20201ab436025fd953eceec51fa1ec107ecd0468c3eb28445db598d8943a426bdcc821a7abbc8fc111a1e9f182cb4785a09330ee72a9c301f2b0ad96a313867fcc1234b4928411153f9a4aa2437bcc020f84a98930abce6ad554ed84ceccd0529d2b84eef63bdc50a28d0ee5788df460685c432e14f5b93026fe0d4e928bb75274ded636f60117d1819f29d646028aee3543f74af04bff946be69fa0bbad0a513a40c4b7b194f4b3e64583676f1b2c421e25946af9a4ec48a1b4c40106a7758577e1a929353ecfd44699f871b96ada12c384652ad9b319c9b3185146b44e149684f59f5a32424838463d435049eb04c571238eb7eab9418cea7ed086d667286405b175a41a480a1ad0586e3160c5fa1e39700a531ffb4e64dbbf79768966232a2b33cbc1198a09f5f75b41026a7562d747cfdb1c1d789bfebb66f09e61bb68d849cf32687be017945aedd9ac5b90c152baf84a7e87a454fed76f677bbb6949305dea35ba267b1deb0e74545792c9b10199038dd34b4bf2f78315def239c8174439f909fb066b28e5284fbef9c10063c9283a9ae85d0318466e109acd06bfe22c39a2dd486bdad6bbbe3a0072afd8f1d44c646a6ffebe26f175ad65be69ec255f87d03123a564a6097872d7c45a096dfcb1197408e5484444107d0cb7f0b691fe22caffb053de4b2afc7e01b77e4414ce2bd286b44d78cf0d9d5715e0d8fb7e58ea1329e0fa481398f87189206071af5ec54e5f604b99fc43272e28a2b4d0ff063157a84a49121e004fb4c952a3a0160ec0e46f7ad91d3bf189542e425251717041b9ee932a6a14df38a526407c06aec5b89d75db153a2ae575b0750a6e370992d4a547ec07e1348f0e55f7e7fc3b5748fe4d2374cc7dd856cdfe637e58a251233e5f4ebad279115fca9a59d9ba9f64335a3a326bbead8ca8c204ea60ee764270e05045828ce4ed001512ca982ced54c8b53677161cced4db004add00600f736dd64f9e2396def65b2ed4fe51476649e67e1f3c8ca625eef6d697c948b41f038d1352a7ac616187615eeab12e3a79064fb3fc1cf939e6a3bcc7445c7d2855f66fd1a307d00d9b93021698807b549b00760462dd4bfb08a05685e9ad6371d9e167436e7f4d182fb47a751fb4f284ec8044dc4d7043a5f058278515ba8043197ba5511566039db0d1abd0259c1a5370b5c500a5695f8136fb84105495d8200f64f1c946424a21b99c8d0c042b43c2b22b129f15cb96d073fc508a47517a366ae62d657bca5135921b11c1545c5f507299129788041d3082667ff9924768b23ee35cf02cec4431e7b6299158f6bae9f09a6e67a2f9f9e03ea6eb893c34410a7389c5a9184be4b3753a9828fb9c4a4d23d84491612ee8fdd9a36e3e8be967c35899dd6b2833c9beb70ccf4e30d23226c0a26ca0583811eab6cfee702bd28189b7b85527cdd48a4def95f39d0ba4c92420795e993e4f56468c7336362176692ca94df00ac2cd523f4dde735f615596f5e8c0cda37f731d9b2407b509afd58407bb4a03e7a101e19748f07c27fc41ec323a488c582cdc6482a9c4ad73b6982c8c3942d2ed450a4dbbfca89689605ec22a50cfd192a8f2f555840d02a4ca6d167381c8e6d184ced95d4c3f38fd1e6f6d7fb835f1590be8547930865acb9ef237f5ff9dfc27c76e20e3f6d87e7cc65d1f75e4d4fe8e0cc5f5ccb0e62021bec245d18a652478b516ec405275bf48edd854d1e2e3595f650e0084aa149d9f91b4955dca90030429b5988a9a41b39fdbdb8f883dd64112d97111888e81d683c09a950ab1ac4778bc90fc88956c7ab07409b0aebd5f980e762c6aa86b478de986a05ea4eb58f44934f165b5e419c946577b2e2d647f5bf0dbef8c456ccc53958d34aed34e1bd9a11a4e7e2256dd86327fa6f29cd590754420ea328b94a5d248359cc6c851e63aec1e21c7ffc28a172d82be6ead7d9256e23cf8a0d9a8bf31c7d02bf854c99a6fc07c1129bb81a37ae483331d95db167888dadbb27018fa9b144e6ec9d0f063a0f6d08d9455a6a3bb01b5d1387fea1f42ec97cd7144cbb0f570b2ee78852638f1f9b2143608a6f7927afe2bd7e6feeb22c0d0d135531176eb40f60d4015a2ba811af000d6c8595d409daa9100c5120e2e0b0281cf8627820204574492be191b1ad9db24dfa0449d4192c272789a2500b83c515bd2576f1f07aa5c1b916a78c6e8686bf8f555dbcbf3508905c08e3179d18fbb2ee6d52e6900056c0b52e2f864b544611470c6a81756ffefbeb7c9d35b7442c85116e7b883948ded276d81fabea9f4d54c86451be502a21d885a4a9156ff75020d83c78566ad9a0848c1e2656ee97038da58db9955842e60cda322a689915a6f47684d8c0a093f4c81f1bd044021c36f031a8d4e1c2e79dd4d57921f23aaff771ba9efb3f4397dda1fb15929a5d92fb9da30c935babc78d58f57fc117a8a3b5796ede2cab78e081a6ed319eca3a4a6b87e618463d437e7825c5a1ca8496c550e6863fbe4a093de72557246496908582d795861a777683c669c3b94505e3bf208a40f933cd96a60ead4eff8fc22501ed5cebe0ed3e91952bfd35678ff6007ebb5fef7183cefe3da8d8ba0ac26ef8c7c73133279671f47b38b2ed5ac6ba45bd16fddd5861324e498dbcc4f198a45ede1414683b233adae93cba355e75ed82ffd90b3b785075eb1aaa76689040b97b17b26830ca00b7c8745200b10abf4e1289b1c07b3b51aa5f03714cf0c3069502ca5404c5444fccafe365cee81b13f479e886299bbdd0b4b364db2f6e240f122708be9ec1209b85c4cecae61ecde190da835f795cd6296e3a624b29649d7c00ee181bb7fe126d89d984c5963c3bbbd60676da50be5db2b85b2b913eac8b1c0689b02b1cdd4dc54ec9fcca0c1cfe4621e366b5512f836ecb750fb6c782a9addf083334759371aa5e9355bfb601235791be596eaf22cfa848db2803068b12635d3c6225b944b209207b4cf398ed1059130065fdc29eb504d0e293110a11f70eddc8a3a28e6ad6184889745282237b011b9bb40ca18cf7b5ecb02e7c2c69e6d44fcf0f4b62ca353270a838b7155b42f257ce85c212250589aac320621c9771863f50c5a6cf5be24647f8fd23ed0a4c5dcc25d65903a8dad0920d594ff48f9a202f7329be4f518092c121985079f42900af6494617476c09437d57440458126b1da94d78e1c036f952987b01403879e444a2354ccc526c193bbe11461ac7c83a4c9e5fd11a8e17460f46baaba67a69e58eba555d6b43229e36b1506adc0f3288e72eee780bccd5996ca1bbed070614d6571797bd8dee835e53d0dd55465a95a536bd7673e1d0f662af232622ffecc9b8fefa4439c9b6c54c9c0c24749ba1b017bc15b54b71f37291c7264813e368faf07af42dc0bd9375a03cfc8d73545623ae3299b823d4cbcf52be973baf62768a6ea1f271c09d44a6ec94d6e196072b38fac80f9d5fcde1efb08b6a6a694dfac4d0691dce3a428f9cbd21b6759228b3cdebdb9810d2d7766d44218ca53bfb1e3a36cc09dae3ccd9ccb815f1aeaee22e1fa5dec0942ac0c3a0d9c28394c07c62744eb9722ee89b2513315fa79491052adb1414ad13661abcd71af0aaf558cabc70873e7127b041dc6edc9d82631b5545851a3ccff124f359c15512febb237f46f949b2bf82b3f0af316ac26abaa5fa07abeee269cc05450c9a2575b8e2df0a3f0799cb935ed4f38cbe7ea1ef9eae351bc7ea9a172c9432c45c24ad554808b52efa897a207860229908c8730964240c6c8b195699786565eaef7e9975e53320e330e6927555bf0ca61d54a51990912e0d817a58989e03417ac967d7e6bf958d6ff94b5612ffc3eded6d5e1f2d2e7a2e13144101c6ee5b6208e810f047e766e3beef0d18c1c7ed8ef49695814b575527409c27eac8f3bd43e991d1060baa30f3157ffea00d2e58dee08cfceec268b52f0c08e39b975445530cddd2f2ad34e940ac4f494923c8e1ffa54dcc8df8987c2bedabc92ecfbe0e31c8659c0ccdd09cf6779e9bf290e6b048972b945c89c6c57ee6673289ab39a3939b68751c063c922ed378f6d7c3e7986e584b7f32ccf34d3e26f8b471c615715eae8a6655cb174c446913be1d87b83bb6ad203c5308cb66d22e619bf24fa74ac2353fc2225edebc54ce54f55212cb71f511492a1b31621fd9efe6ca9e2ea95c2d103249534f892b5ea91a18de0bd9fbc563e7e12263719eaf9d734ac1134302c050aa5c56004081b8244f8996788a790b2b3ba7579bd5a913e0569ed0371ea0ae897e0a608e4def86854c750ec1577ac6a38787b079f5adcf23271e98520f9e144060adde7ebf732cd2fe06af8f7cf14180bc80492ffc982a96c84da9193352aa475724cbe5d24787582d5d2e22588ad8d1e7f6d58b29ca2fdab9e39a8683ffa0ef188b9ace1fecbfb01207164442353eb6bd02ba43a0535d9fe64f42f7b0206240d76fe37557b910ee0f651d3961601d2119b4ac0941244a8bafdc5f18ef510a0bb244be532038fc6b3adbdae578b767941d31c9e18c5caf344e348ed6fcdd0cad169a50f5bba5b613d7b55fcef699623794814c8246f7c77f21e418edd75dd7d273b718bb8960b1993b18a7c782894ee44599c047233bafd48b87294cdc28ec889519dc55199f5a8cdba289cc5a89f85a89d751d9d3317c5ad73e39f8df6d89c9e724ebec9f54655f7f4fc80d28ad615d32202616981688d65eff59d7fb989de139b9a06045ceb5e52be773c098e361446e18118383c67fefec0565f941a973ad1c4d249b6d1173a2c93594372ec33a44e11b1a94e72f19bb56c35443e032ad055b3702a40e3eb995f0325a79e9b7bb816b0da7fb77fb1cf16f6b1a2676f85bef454588df0618029774fbb78c3a73ab5c3d300b6b6fd1aa9bcf9aa1650547fe8dc7c4137e2171366c455617bef9af20de25c264d66c6ffbbabd505418c114ca9717de3694a3eaa6a6192dc02b165fe4376ed6bcb89aaeb5d895843e9754a08aba315ae48a4891b2d4fd44798a63a79ce5a7d97f0675307e4f49b88e14fc6ab7314694c486eb544ee2a098e7a4bfb6f48f48fbdc057700f97918439541b0ff07702cc5315ffd83b312451236c3bf30f590eaa34c2e2ffa5944a923cbddd26d18664f840131382330a45effbce1bcce53c34f5cf11658a8ee951dc92013cb35f110be3cbfe869ad331febc79058587f3531f7293bed1538cab1e10653f2e9c9e40398ed302d22db0d49991698669cce7360fcf5ca5ce3cef00c994b95d45d97d9222f1eba1457924253df0dd8973a56b234ed5c5f95d28907f9793062980bd5e11b3dbf1357c388b5b2f653e3f5efc01c1dc8bcb9479f1649f0645cc62ca70df6b3006b617f33b77b20f74086312d8a644182a2885fcbe4bbb9983ccd3712c2740252799cdd0fa19d7ba9ac71e61f8e6ea16cffd4b088a5008a9d9809b24436a724a3ad32ade98c8475b674be355c17d191a5a389e331238117bd48e4b281e1c4333d520f687335074fedf218216315aa80485724c542c315bb450c5f89cc0d5e238a96d44d49a3683875c535bbe27550dded8209b866964cd83a2126a6018868109da909060a5eb34fa7e68596878af9a2284fc5c187713683cca8a664d7be0729f469bba9f216b82c69bf89d0a86245e6525deef9177a3dea919df1994a3c68c5ac6f234650ea79ae89f949a4eff2602a3fbc1695823df60a8bd449e58b33372d6009a4e5533b097eb98a8bf832430bf912c0109c20ae2c80d22ef5b3492a03dfa508bd02e364f22919e93a4e679e953f662e156883147255384dc628ed6190d110bca3ee6ff43b9315f3df503697ce195a986d9417450eee553ae3c3ec70a368c5964c79ba281b65e37c3bfd1f485eead1f47de46819060e57a8cfe4586232418acd42b855e3444c95b06134bb87002e41d5c106bd29c1882ecfe52a94689218413e410092cdc6fa725c7194adfaa2c5eb61127c9e05944d9e309c8bbd8267a0b2e811be1d2b1af914ec128cb08a2aa07f5b25777238ff89aaf00f1b13ff3232c24c2dfcd1577e264eb6bb911f81e4a32250d9d6c14c71e28c31ec20b75c58e5a63151b8a4213621bb94a6938bbd55423e277b8b9b35a5ebc3939b73aa7df57c02def32bb590e615bfa144103ed78ef54f215a667a4d8800c0e2a7b11acb2a63e3f2c088a468238dd70e007733b61bf9ed3139368d39afd0e6006422f73749bc60e3318e0913043dd115462a478b7e5febaad7d5328cfb941529f0f5d9de462f4f5542a76e39b40efc605ce8612b5cb57a621025db5a44585b73e2564cdaa6b72ce02b684118351e3767bf9bf03612b2cc5a3a54d3483b8f184b081e48144370761c196e735f7ef0a5943fc32fbb52fafaad4a55fda54a71c1175492abaaa32479cd7ab1f1044b05b79f5e8d597209877953dc35db4849383bcbf1646ec0384f78eae1917bfaa10c61ef6f1073b6e858d1f0a44dc7235b6c3c61004e7939f1fc3135861e1386838b2557f9bde39e7964016dc89069a9594f3e959a4de6e6d202c25a923394f96cc2a0597b4d1167cfa9609597eb9cd3717c56f850cabf1d370322a2088763cff3a51a82d66f9d05788f2df9fbf2d41d3aa3f1cb6f073602bdd8c80e7b53a7294dd27b583fdfdf30d40b289f3bc5866cbb4e05cb20f5a71f1da1ec77fc435383c2098b4e32d865fd774ef81a57a5ee997672df4ab825a15916a4a5a7580e7b871d54b1c1c243a8a536dcbf90b8fd66a297d208f01fd757315ea7777e56cff263a4f79a8de73b0d708f92ba0e9cae5ecfc07321ba1f5920161e0ba2f2456eeda2b496fe7ab29e28a19819bb39394d183136195ecbb93550b74c3614f6c8b344a9ae74853c189d31a340886eef9ad7b472c423238ca6e93f81a7e152da2a92e0068154c1f00da8b71524002dc751a2dd523e5cfef51f58f390c4ba58ae1d7176c88c9d31bf650369da5c183f92f00dd3371844fc060a050d79357ee3f127a195772c6776d116790d67070609b333a41a74d9de72a754896eeaea8565484b994aceb47113fe74c6c6942a3dab3b6374688fe50c0ad2d3ef37ca41d774301780c6cbe458889488743102f28fa4b16088cc67814c6c6c573156a123be172138498f8595da004b9fbc6bdffb153da25e9317a977ec44e9fd9760677759dc5582593374be299d4176f0185caafb866d32bd40a888c4ca4d97288bf9e9e5b93542fc66d99229e405e4f7b9213c201f44feb4e564e0b5d0e724bf0007af94249ee32657059a41523c586920817d19f636dfc926025d642aacfec3de4c317f583e4d65ad11185c54dff32dbae0970761ab5d45de1da1455765fe15f65c02e78e7597c392449189f23b9b24d9dc39a033e0aa2b2f276f9d1e761f6d2a1159d10053bb32475f2f3993d7bcde65e7ddf3c60803cdf2aeec9660776ed493056090bae94a95477c902a5f381c0cccea55d7222453a25a570ba9a609ebc217a373508a3f162c134aad15c880f0abc4dbc1edd5e966f9f73aa34c16dc388e182885130483a61c9504dce22321e876096ff76544de37e2aed3ee32307347e5853ec1869398ce4cbd087a991146eb638387a3168c7cf20b4fdd20492855c7f7a3b19c86de60bf108e40ca755d8a2b16fc22101a63edba7f755090cdec8c7e434438201651c09ddfb2f5d7833d4bbc394262fab4290eaa295e2acbd7204e4bedf8cee3f4fe9c74493e626925dec15be876d5b79a86aa96070ea7e579434515d78f034188692f6d08c658eb3ab6f5065cd4e25e014da6fe5feba05a07d6304983da72bf07c12d93907e45272bb034326c6aa0cdf12150b0d0449d5009098165c1ec185c4698c28a65094c74054fcd773f7055434272ec7c472ebba6302ed36c992b26dc7476f6017b21c58ced29eef1f6b67b7b13d581ad4bf6f5878eeffbc9edf9c9f1df84a168a1ef8c9d73c5810910e3c6c3931610622e4306b93f584d7ee734efd06a8bdacaeddca4cdae0f99d65f1ac533931f98062d2da5dfadb7aba8e5a2c721e10104d84777f2db1227dc415adcecfbc398e0dc1c0e3971db26f6cf3b71bb428d44f1d138247ffb929a59b9d4cbdc3d0fa2dfa8033f19b39f1acea9e7bc93235f5d3a8fdacb45a69deee8385888213f0d270e4d9cc339cb7f053cc0e593c0a8af6eff9297d13e8f3bf94a228d6d5618813481e1d0d2dc74a2d0e792e6d1523ab0dda58d090925f7532025ac37381475d49977c7a7b97abd5ab36eeda75c5ef66521e45225d01d8ec246448b96c7b63a23412f58f3e31f52f4cf503fb57ba7dc86388e654db87d75dfd03a63cef9883d371028b3a4f2d4111c4a9e3f0b90db9c283cc5aa7992a602bcd677b9f2fc8ca6657d92a61f87f22c50d937c85cece46b433f42f9b593ad16788fcf7b9ee7b220abdbcfe083a2e03ca969a2b1c762f4f3b85f865b3ec93376e421338acae8d0b9df940f1e65407b57b9e669d19b17f2199890a6af3f9581214e133e503dc9fc1e87e2d6716ce312facdb7dd0c1550afd974ef7fbb865b5872faa704dc72740fa9e929d0068359a45b78b3a1910b7d4604e9a8a79795b62aac1d7789dfdc1f7406f6d61bf311dd8729f4a09ca8e0cbbf68dcec65a81e36f9348f5ba417786a6d8cf97c442f5fe56965cc442944e8fa644550ec7ac9752585869df21017df39b2e8590a1e842f92a0013e0c619c4306c0fd68699964f66751c19ef17cbbacad6caeed6f7e568b55e42c9150e997648db8af31cc9f46bd05c17890e993b4ce9e372de63bed3a9df18269391ddde81d4a5718a07bb510244a2b286c07df865a63c8a8dfb14206388ccb30a8e2825e8c5f2a2b6c934993fd3c3fe1327d9330404980908b3c22bff72b6c610642de5dfb53b33449462d12a7e6e3531e962dd5aef428cdd9ee8785990d0df641d86c840afb81e78348129e0bbb6c8326a0882284bc0370aa031afebec41f70d6085140940bda09bfa0def992b7d39e711a3266e3a71fd3a3253c5a439271c53e7095d2cba8e489e49437191442ec5caa8b64c2b0e1ba001376e37614f30a4c963b34e19b45f7dfd47e26451814d456469b11b132e0394e38613a74dbd4960a186fded771ba449e1795ef2a0b687fda50b1043e9b303403bafbbfe2fd70a792d4239c6240add797fd8fb36e2d628a86f6aabae9662b3758bd6e4ede97f1faf2495b67d9e464ad637277fcaa6d5f25e12a4a4f0260ebb8425ff17c7f5af7999a9df1607b003a01a64c656823bacc4626865d578be76d83da2fcf2b49f0c07850f1abdafc00dee54e55c7e4acb2d3215c2cca6d2d82dc04b1f8c69120f988ee1a5501dae9bb9dcaa558dbbb0a6aa724f0dc98c80cb8a97c7ca7366067ecdc51cb45787ff01a6851e76994d7b99a6005b823284f041621acc3a2b48739d555f9759430b9616003ec63cdee19499ed19705df9818c7f4d6a7731a3317ef707655544445773fac11bf7f4b80041b02c1d8fd08d41eeaff5e8a86da6a0b8198e82c71c537e0cb8ee0d7a7f4ad8735200df348bc5eddde8e7aec1805863493d4e4aa0bc542efbc38837ea55d14be82bb248c90e58c6435c720a308eb7b95b6a6b64f585eaeb5854ab63be6c69c94f3355722e20fb6fe255938c9674ae7591c40e2cf9d5cd07c84f46340108e26ccea0be11d192054d55e053929edfff88bba593bb09b6e0186fe8b43558e86714a4f5f9a1b295de67ae4be84255c60b479ba4ba3d6a6cf8972b6b190a59c53a79090a3215ae129aad656ccee99b52fa827b003d7fb910c3e43d5eafe343338d557c6b0fe029b97c325171b041b2061e34f6cc35b9c4cacc1861d3449404188b06e1449a5052ae7aa7bf7a6b864008a828b88bb04f63b4624390ea100f63e8c46b5db92672654075f2fc2dc6bf8a319f90fff51b5c0ef19d7f9e00b2251fdd6db342f7a8577a699f4d6d54f92ef308562eb04b8cdc7c69ec4f663ae7fab8b2b40ae7c60219b952f36c50cd94aa4739c9aceaf27e98660d6494e601763560d014e7276f73b764d2c672d581c2a2e850bff9443b0dbcebe76203e5eb99a8c97793444c0103b6873b4cd835c9076667014eaeecb9dd8b3f1d0dea86932b8815b3f4d66ae9997d5733791c274d1790ee3dc495360591190afead67aff0c5b927c6c939597419f7afcdad576d20d794ca4b3c73f6a0fe263844d878107a116239c4f95c2b4070820ed36a5105d120b15138d2dd107b399651a4bfd4046891ca18d122506dacb83a3d8ce58118472a3dd5bdccb38756c3e53e816fe3ec42f1b67eb58e24e15426380c98ec8d1b3dafc3386551296a7176c48d07fa046059951b7f058b2b862894315a3c450981cab87c26b930983f678f6047047c46e0151ccc1464557e18b8f8c8facbf8f8e7982650981010bb4901f1c9c0e61ccc9b10eb9321d248143f7dc487d9703735ab4805df31e95cd878c67da8b1eb062be001543715aa4d3c3b2ba6cab6e4cde4e309e038d9c3df278c1e4fee86c966341b35ae1663d029a10ac62c06b0699e4e273e891fb4b4eb41f4635a8ecfd9bb2d78445e5ab42620bcdd01391b73078b1a7d38b8bd8090ef39ac764b04291bfe30365992ae3089ffd9194a703208b40399037c12f1f68bed3f9bc2a135f1e377e670e69c8b5caf30fb054df086b77a635502ab46444d57a4014449206c22a284dfa75fa1cf5349aa1bbe392f3f808ba1f402ef10ff2683b62782f400e72e41d10ae2a0413a7aaa95f5bb903b527e48a86cb6d267114003549032eb296ad2bb8ba1fdf27c3df8b9b08a515880ad65f8a835713ce5cb0c2be0e0884a069e05fe9951516224669a9fcd364e63b1a91af5ecfdd80285ba0e590ba83a21af4e074e32de33e43661773e26c0ab233d4a1aeab73882dbc354d9d379fccf63f80fd848bed6be0c1724f214d28cd2eb283f600c0f7a5cb85c17ce33b214d9f482b3608cbe9d09fff2e3559991955299ec769445d8a41d691b908f28fc286d503e6962af48adf59f08a8f1e35df8b1ced3d11eca46c1c4b0913ee422dfed09e801721151811fb17d428bbf445ed31cb455d4176f1a223b6a4272709a6e822c09c977564e6c1fa54c836c9b2b2a4c3f194d63a749b567814ef741c795b851b60f6d1a1c87a753150141a6237bcd7e0a31865986c640f57046081aaa4855228beeb61dbb8cb7535433dcdab9041c76112ad98f48211f7b061051172732385e0017ec332b7e4407d3a7c29652592b361a7f4b18502ec7b88fcccf483b3f91d6023b079ba46ee7406a2de710ad4d242b577e118c16626cff8434a4b58915ca50415a4b37b24d73fed1da1cac72d03c5a9b8429744743fa699bdfd1da7512e11e06e4365a534c9843e6abd11aa4b4f217bc9ad1daac55448d458dd988ae3e268f395ac3d2502153ed23e511caf7851504096c34f39fd6ee679933eb0b8b2b95111d4bb446c90c5173f3980a750a8f150cea03ca1b78db358f217473b1f26cfc2f4787d6a096a8326530c1eb686a6a3db6469ae82e0b1b5a2bb044f25becb8ca36ad6d605705ff8aa26a57ab50fe94bbf4d6b0143ea149085cc40625c90f156b09e24a0fb4e6d92509c7144b219f35beddc58908c149d01a79a5464c979f0e0cad15c45a6c63614e165a4b61897e2afd23912631165a238e22635600e039e39d5ba22acb58e4a3b58e869727db9e179171dae4f49622610f19aa426b126ceb489fc8da70f89b13b6b28e73f339047c19dfb953cedefaff081427b4a672bb4da6d8e36cb36ee786fb03aa6a021b6f1b5a5bf8c283519b8bd3a854a2008306686d663e05ca365ca9e21cab37df0aedadc69b34252d005fce4ae0b3666ed44cabc9934ecf4d5c19cf5ae35e69aa85ae542d5508b92caeaff0df56a7e6d725a88267cddb9c673feaadf1ae7793aaf2152166f4aca46731c1f4b91814010fde79d65a185261f5ac0d6d91ffd1b33686b8496c928f676d5fb3ceab724977ecc893d0035a94f40db06fd4ed74ed5a3b1f67993a3139f84f269f85c7516f18aa2c9c51153350f481dc893c024e8e440babc9e7d05a7931668a2e06bc5498ad68c51b30bf3a4ed562520be4799fdd1b6003637a18777b04f72d510fdc5afd2107096672fcc8de0f14a4221f26a3804b17ba0fbc65b937169b37cad5ed8ed3013c1692a73dd26a2078c26d51a915579a96428d0b457a4229c3f5c14194c59822a469d85ee81b37935f6842d945bc8a3baa086e60ccdb08118339273bd25868757f71d47d901cce6495f62a430d6f4f198b9d01e0d2345f284c495318473b3c0314589ef1955691502ea2210b1d1151c92d73d9826c26922d3da465c52614ed4566827aaec164e266ebbfa33f0af55644f54b9bc76bebac3a1c209eabb691dfa58345f2490b0330aeeb3e294b8fd36cb476d39b0e7e9a18d0f86c54a232cd5b98b12fb99f73b319a6582cd97a542df95aa8e8363f31b2647591ddfed90c8b44b264530ea93a9cf64f2ae8bc4a7a330c475625c642112392489b60da1ed7f4826124ff9273a5dd5bb2b6e943560d5530b41898faf2baf1d37475854d90e14920aac08b0db0f14002cb8ad5c60627775a50b189204f92354d292c117e43086a36587e36877ab5e76a2bcd2f5f58ad42731c5aac4e9305128bba8f2e43b36a8f5cf4095a81a91f42fd6e6cbb2842b863cd480b658f4e1120846bb1e3578433acf270b02e0630d4af50cbdf1b4c216b84bd3e37718202e60eb0422596c8815c42f4742be11bc77a0baf991db9691b8a6bece9ac107b8267a26c872e19348e322be375f70e577757e8bd1dac629fc8e91e9d05b086415b1982fe6f58c3878a9c18ca8f88543d4151c132ed2df57e2b6746c70e89e3102775cd99e5e804ef7924ba9b0f0995292c2667cb89d72b069b1a18d92f9dfbe5481bc9245053efd31fd1f37508dae36d6f7cb113fba55962fb451af4b4cefccea985ebae40a40295bf54e018daf5a9e2a687601cad5aa578bc90c9c1b04ae877ebe84f56f78f818f4153bc86b2a15ff664975ec4c4aef73ba9c79fe9a3ce69f065867c4d25063dd754d46cdb620bf0e1dbfa88ebaf59bf2a2760fab0a918927cd061ec761bbf312ec132aa2e323b19c336d49b70f563a21ef4bc4da46cf2442ea4cdd59457f167a75232aef08b61dba0a1740ee18b8d17b637019ad7b110a1c8c7c491cd5e9709aa99b32bdb83d30888040615974c7a3865e4a2296da316cfb0278f6e015a7b73bf04b0ce43313fb70afbd9f8ec28d24c169acda6e4040db6eb85706b8e9eff73d8eefa9a68a33c276772aea99729bfeb904927f63e75d04adc9a41e4d658e91b04461a86a7ef58722ed54dfa1378fcedec08f60a136b1845a39a258fb732e0cec8c3cd1cc19d051b4ea6af2e4cce1530be1cafe2ef738deb4d6e1cc24491c2786c2ad76e8d6349f36e1eb8c9c7a705ddfe28cec1fd0a063d92ed2bd3fc8091017b5d061eb7c9bf7206db04c7aee77bb6bc3043a4d6b7c08de129a204896c314c4b9cf08027bbcb54e77b70e850f6da1796320645d6bf4844809ca56ccb489027214f0829579f5031452b669f2a4d99b822d450787022d313c2d0f379e4f995856677703390aef2a4865a757cd20cd74785c4f5197c19c9d11cadbfc6601070f59cdd9d6048bbfeeee0cddc73d9759468e0f14ce6b8acfb0b19ca390a362d012f3998a97bd582ce77d8f5635620196b12c07332918cf219f5189e278849a31c83bf56c0c97f893f608e211c13211c10601bc428cb91b0d608228117dc25513a420302864c143bc43d9c66d1d039fc0ef7845b2d41dbc23df3c16c91ba3cb827b05e13f6c20c9dbd316366a40ffd51b06668641181316b6ecff61c782784fecdfbd49f5e96107fb9f1d5b9aa25eaeef0d71a35fe96a9891813baa5f9bb6e180f1152b20cbbc418879b550a90583ef138496f5636c3d06c3065254c21178766ebfd665512917e6a48d21d4a104fdc198d5aea7bba60bf2a387d12731770ad3bd600b4e8d7081426d7f5ae6a59c05388301e5567fb1bdff1d6b2fe793d3840a2464171369a8c618a32138e4b585b2680eadfdd16b7498d914e50ea3a48380b0dbde00849683af14f220a410f991bc3314d4224db9e7aee16cff75b9f10d816113553e6d5aa754923e83bcc24a8f809584c00963239ffa63230887774cb1ae38a00c907801670dbbf73bea337b8a409bb885dc8c190778afd4715c5e696456e1cf084511a82275d1f0235c0d83bb6d32a3da992c62574213f6aae394917023c6e34bd1889193cc2ffaef1d309d1e1e3c6034206da7fe0adfaf752238cba2c21e6a94f401f05dd646fe059f2e11d748da9b27faefff1ccaf5c1e604960b66f68774b64370a2af69e6257172150efa9e16925b8a14b5736849cdaaed1c5325831108c302f8f2eea5bf5bc10746144bdaa6d284e5dfc1d9a892665b4b708d9a8491878092c79088a72a8642e4e1cfb2ed978660ac68730950cdb4e5de1821750d058005a9d37c6e3fe9f286b569d89391250a857eb3efef7156611bb9764803d44bfd4d505bc399d44953e2ff5efbbaf629532eae8b822a930f14238619cc6dc292f9aca6c408d17153afd142e6e46e9267fd1bf1656ad1bd10ff30931c38d94ecdcaa9d8fdcb9a3e01248e341bbb60473d24deedc485a6efe5934596bb45ac9194ab00bf60cc4c3ca088c88a1e53120f05cc04d207d284f1bcbdf5c3927c162ebcd31add7d295e68d412051c722f98bc57b7e06af86dc28b5c4fd80901e5c08029a934d5a58300edd4e07487fb045d04d939ba6033c200b461718099aa4d6a342f10970c10b414916602da85208b55c4cea64412ed3007510a09bd40353f2de4f90286becacb8bc50ce06fe469b88580f53ee57b7bddbda967b6f29a50cbf0532059c0546463cae0f239bb225c51045e4ecdc9f1ec51d398a48355c1a612e8e56710a868a15ae4e520b42c5209f12491cf74512e6c791220dbca20e1f4b37770acd060dbe197554c53616ee958a38221f20d7aa4807ac48a6580449519eebf503c51b912fadd83756245ade5811f55ab87e4c45bb245e90fb83484491866866abaf889e1288bb07579cb1566c51f6e5e566b540acb7054a8d29dd24a4228ed0de8f6b37a453541b0a31a4cf0dda2b420511d143449b0b341fb122dbd2e571e51a91a25312fb906e088df8293e09e9b83142215c65df9e511062921e666d25d09ac45caea58fab2293149992a804c94fa8c8f5538c4ba1e31e4911c215d2471cb20717a418d6c3441b0f5ab18798fbb4acba2abe49b933943648452721b39f1828745c351421dc157d327b45a52045a01e4d6d6e08341749ac28b62c5a5d6d49c9286f20b948848a7e8a5d2774dca31321f8fa8a7b6e8e20ee4e0fa291b6e2126d895890a50b73e5e249157994c52c2418427d3f7f2674dc33132188f421d9d36ad3203d6dda03c82d2fadacac2804b6b6e55255b5b928ca5076243e734f0425f5bcd33768a8ef37f7defbe1101006a46603047a45a440553c1c084a070562aa398186764c209e9d0d34b3f3c3b7f343c7639a30fc23908e8e5459f1b2d1986304fb63a6b3fe78e5a83f38c1136df1ab755b684f3a1f361ed4874ce7f4c1ea3159b43e43383e3c3b3d486886c33fb21dbf3c6f47aa9cb160ef4f8e1fb31faca7f767e8fdd9797f60f27ce85e1f36950c7882157cf25132d5d767a8e7d787c7070649cf5b48e721477be06c7a6eec983d3f300b0b2f3a656cfeb889ce39e729cb8ab241990d1ebf8e6c8b373c45da3c1ebcb61dbe8e1bcfcba3d691473c156b2a2e20ba123cd9a74dc1bf76de53b08bdf9e1b1dbf3d70c59e2e8ef5d4916952c76f0f10eed9e96ac76f4f4ccfbf8ac06c08c85f922d6b66a9c786161990b9d7666dc6bc31418da01dbf3a6a48e63acc39e79cf3efe823eed84dd162bdd2911bec8ea257f6f0c47a9ba7c975caba6c1378a6ac8b46d5a0caf1d627116802c2af0e57df6c09191ff3677cc41f64f7abeafeedb2e0a0e201e660d47aeda6310495a13e6615e3abd384225521b268edd6b467925c6b1ab96c2db0e357c744776bb4c18e5f1d111d694ce9f8cdd1cba1ebc8e1ded339cd38d5e73651b03ea220d7de66774d3936eeb0b0f02fb3ce794c1330c605cad1f0794c4dcc08748fc50db287c1aef897db8e5f9cb3217089137683b3a4c1c1b239e9f8c5519eb51dd0bdcd8da63c4d795412f148cb3b7e7188da76f8c38e5f1c9a7ea643c529ed062dca90735ec4da1c3aec1bbd9fd6668a737e9a5bb7edf06fe03af2f8f4e7b6a4ce98b386131c3ee75d3fc00e2650eda4bc986cd18edf1b17473d284fcf79737363de84a0d94a6a8cd8bc367a33dc681a755e9ba9fe1dbf3636fa858e5f9bad8e54396369549d9242fac04fddd44df706dca2827e17dc8f1b003ffdd48d41fac07f79007ea3916f60346ed0f36e91e89f6b9699817e39f526a1e69cb9e11787afeb3727a96b15f8df929c57a062adc200eec5b193e992d97a65b0360080102c461ae30c96c391139b734bb209a1f7f2445457038f2808afce04281023945b71098815a739a52941ac281ea018fb81622c2e39c3337091b1e45a2493a9e69224a7e0924b923a8a72122ca42fcf2f093703a3dd14f14a96487270c09b0ca1e8e783a25cb34cc210b1c06a4afc4657231be9ad9a371fa5538a3b29ada4a48d9e11a7165a475a74489358fec36f36b14cf64812a0c826b922d952d4972631ca71f11424e5e63502241d5f345d772a5eaa8ea2794c5ec901140d8bf016a6b34b6a252f1469261bc90e8ae2c8924761c9cc46220986d97efc8dd0599156a44a6be9450a2f6d86998e15d9100c0e5448d3ce4092af1e73c92caeb197177a5e7535667d256a207514c563c26dd9cc0cd729a0082cd995744a8e30405156d267a785d850674292c444a09268918b23c097a7197743b6ce60fe72e02813c283c65f18179e8c137ad704824e8e0efb21a08f1370d3fd26e409e82308d628d6ee3fb18937c6287973606161312e5be4ca87e6e38ddbae47a858bf2da83a7e5b30bd517eaf8ba1b7859fdddbc2ed6d11a63fa778d0dd7c164a807cc1dd05fa38a762ad5de46abafc83a9d3a9b37ef2fcd4e2770aae033fb739841564dddf289e7eae67366a3dd2f4c08d626dfa48e0f9ead9e296b3d5e999ae40e2d9db13bc92b2c0cfac4b0ad04d53feea099cf30d9357c037fa26ce5b9360146bcdcf67c6196bc62de38c15f4aaba5a055df5116f41c084fc6a0b24e2de616f30815027a07854b88e5cc1787ccd7a08baab3e6e2356bbdd552525b69c8832345ee50f4cbe910788800979d5ec04703f3f3dd3471f7866530bf9bf2bc63a72359fe4baf9ad39ec66faa8cba9b316eedc79ba9ebc4cd52bdb04fe07fed2734e53e4f9e17441ac7d7f0ff575c7af4d930d04ee0dcc664a7ce99c68aedd74d537e2165be5d63815afe907d5340b5f15be4a0c0a892eb857c0345fe80adf7ca12b140a858f0a1f7da19beae3bf74f581cf1283df61bffad2814410c39ea244f345cdda0d456fe517061f77164551f47ce2078daa287aa227abe293ffc9b22e52f3f4f357d03db3acea7e78966559e2aaa2af6a6dc29e905fc8ba983cfba65ed119eb9c49c82c4a34f99465f50eab51f4eeadd63b1a886d5dae47e4cc5156a4e6c9e09eb8c816d9667745591f0575534efe7c96e50206f6d1d73b28e7ec9b98fcca9e404466f63564c8af7217e5831aa5bd00dc32219e10452076d047f357dafc3aeb1d9347ff24e66f89ea6be2197c9393bb27c63add1c4ca1ab7e481f7de10b856e0af0cfcf6fb62ccafe7316ad410c390b5ed4d7372350960435c822823e9ae819063bbfa62d27bbaed2f1eb74d59ba2ba013a7e99926ef4b5b7af59a0f769e855cabdcaa957b9f32abb56b90d67a26f56b91cd4da5d7510dfac71ab9c5e09721dd4fbd5f603b059fd5c3f0fc2e9e76b5e47aea059e2870c3f83ae3106ad3386b81c02ff8cd41a5e67780d2e5277e12ad7f305a1ca75d65a73f783f033bcce5aa341e49af741ff0cf607f5cd0a875ce976907d54b8ca75f4397739df7c73a1fb21788173ce6e9dc15d831ae5d1e75aebcfaf6ff44a906591e82357b9cebf65db0c2f6cf9c90e6b77d61a70fdfa3f045d645eb506fb7798e6c1611bd6f7eb9b9505ca40cc441da58ffb51887a4aafb46ad72b5cd7eca3c4fc18d0efb0d90bfb511336fd6c9273664dd6b8bd065fbff0f5b0afee87e0f3671f0962d88569cab2423668f2dc8405ea481d954f0cf11a07020902fb21f54e67dd02ccb420863d3f674160bf9d8eae71acbbc6e504a0bf6a0e62a86fd638391da5a7f4f1d43a6a8ddb701a0851b156b1317d24f2003d7399ae7fd5dfbe50f3bbe466b4d162ccae4293dc57dca273561a75cc77694cc3e1331430803940601fa06f13b79da1c0011158504881031ed80065a857ccfec1b0e377c945df1dbf4b2cfa998a8ea247e426010ab2a453bc5b92290a41229537f3c3edf16622ad3837a6b45cb272d35ab8424c39492291201b152274a89829fef4b94c7a6e1248b1062e6d0d4759d1889718964b26e54239551d11ff4e14eb94487429d5bc296141c41d1284288e040e17edacf802ac38228be8f702ebc9a543729d525c25f5cc24dd1495fcad3032764725e786b5d5b001f3f2d82ac28821054542c96b28eac70d29670e2906089feb4248516bae98a483b8e4c60eb1182e37a7aaa813553cb244dc2b72659470e15ad821d24208629124c514438ee85c203b70a81587c8de2e57c55571cd4571a989c668c8448ba3117747346e7e4af65cb03a17058f620cb6229a99dbf3e5a69051cc314544a2cc6b410f851b288aa2a999f5f475c24985d75c1beeca8bd251f6cc61cd20c093064a13786d5c3e6220e1809223582e905e1e3e4b9ee74a2e19502b8ed8baf08a4000c38708b4afb0175a297762d25a644c48c2b2eaa2d67c48d4488bda79334a9b5660cb8392f2233b723b6186520c10a39fb8bc2894e9379b0f15b97d8dd0667488c4d570090196e451d3676494755a4d944fecccf958536223a4040fa9caebfbf9fba1d903737161c9c882cceb85981fdfb28388f7adc889b12827302414e32de993eb889f0a09acb9c8b9536a31e43482913b311d76304a3874702fb513258d2eb9785213371f18a49a1a20da43d3fbcbd13763a2a7841cddde138d9e2f1b0440daaf578f900438fe8a2879ee7ab8e1f72cb94c6b6bb1a4c62b6582b79426dfb98b8aeca2b5ec828363274ecf4a4846969c138f0d0c56de0d5813b005a6fd069d84a51d58abbbe3199206276447030c2c272ae60f6b09c35c5aa5477c895295dc91a6af9c97242d258db63e19265044a146e632a7a3a790349939e79cef76d8a2e07d728e53b365e1c81217673c706e34e11c7424c4fca658c09475e9c84a9d73deb6ed0795b8726c5e9401c0a373a603c791177d02a8a11461048990a4aaaea7a5f33ccf353d5272a3d503ad6aea46043fa69f9b1b3523992e98fd76119970a2161b4921222d2c256073474670dd88190d31c275d7cf778984f3a1df388e326f34885405798b94a13921e0a67cecdc6c84a9091d6396540ffb6fc9cb120ae25ae0d7f12359b8d25aeb9c73d63a6bad172044be3001bb3a2e018f999580bff23a2e01eb01e9780eabe3df917ceb9bbbc26e4f1de378c365551d9399758c43c75b921c965909a058bd81b003849cf3e442a407af234c569680323d376e002a8e43024053766262004a24a88067c0c4f415f4e7ae5d640a6e982a42a40ab44e8256deba6ad7b1da0e0a912d38e33d4a404b74ec72a6e8b8e3c76c0450a034cd652799e6364d739b7b9be6dedb34cd6de63221b2ef181dbfc956c72d3901990f592d83640a6c190956f1d995d1189aab636166a777828e07d9d9cd03b682a41ca8efbdb74ef40c6a352a7eaaae91ab0a32b2bcb6527cbe5b37af5689a4d33ccdd37c6718edf5ddb21c8d79ebd188b3fcba49c7af56cd12a31019595e28b62bc11d9b446f1c96034306ced052f86531f9a0e1483bb246374494776766a2f19fe53a8cb1229d4ca530cda1699ee7e3997ebe5922b88c2432ec54ab94fb782d69d9f841a2a68bf7e8e4f0d3a369a1c486921f32dcb0ea50ce63a4e33c2949335431c65e152f5e2c201c6e901d01d93a13d2da1dbf587359dbad0d47501f1858961491201dae8496d8f584050959d905ad8c9c70126e1a3a7eb1cefadaf18bd5d5c29bf18d4dbfc6373563dc7bc5f65eb9bd56633c7a94808be0afb9d61ff47a1ae34cb916bf69be51474b8a07fde8af803f1005b704dfe4afc20cba7e733dc12dea9bfabfa74407744ee4a0e3ad9e12f15fe7c4140f2374fd238cb0b74fd513d5950ae67cc44631ee48b9888e1c765dd5d5f59b6f15968a95a975fe539bab06fd7c4d9426e10db8234d3c5095c01b70475de4495c51635aab41c727e859af1af4bc350ce38d1a54343188c490ebd8afe317c64ecf1d08cc0fcc2be8d4041d99923be894ecba401f814117c4b0e3c7bfbed1ab05e2073e4ec1cf3ff829e6eb6607e2fcc0578344e358f0f1af0f62eda9ca895b17e823f039500da604831f4cdf18a345aa60e5a88ca557b672975e09cb63f92c479dcfba48e37a9ee7b9aa7bc5a60acbb22ccbb2b9b3cf591258766d51861be89520664dfefcacf2018b08fa78be90d80102c4d4aa151283cfaeeb1a7cf5390f06553e6009021fd540e09f41adb23887ef0bbf772a4fca4865ac5a31cdad43d0fd437e8c57f223cd3f31d61b6bcd7a306160320c8c5ea24a8df5fcd8a85f29b6aeb58bb1ace5cf1fb9f6e301d03f5114a8d3d43481b91bb9b692c2eaf875d1d591c7d7c553efda44f1de3b1f8fc709e00f26eb726e0fbb7d93a7113efb994db140fb269f42eba6e0a67b838b0bdaf7e33f17137047dd02c80433ff33e79c733e41430737f8b5d65aebcdbaf0aefea6da547474db645dd0aebe6aae26918ece344fd6e53c33fef33c5d4c14453912f3f1a990b22ebaabdf019216f8c107b22eb9abaf0651347dd5453fed28aa9aac9bf5b0ba1823407599fa238ffdc8821238e8e9bb708072d05397adbb4853a333305f54bdce9c30b945cf6f46e116f3b37b72d051d81c4e18bc417f9ab6fbbfe38edc27ce305147b4363ff284d9301801393f6f3fe5916d3ad4f0f8550d440e7883fe0c4407e00dfad3ada28fda037cd4afd16ee66c12d1ee01edfb1d304203708bc9eb1701b7b427afbf05b75c78fd20e0160c28af9f036e01befe15dc127cfd0cf800b7acafdf830528e0370714b60a6e49bf2bc323f1d70819be9b68782171e986e5011ada130d8ff218882cb41df817882c0c3bd05dd2e0a67bc30966407e359f67cf40ccd0330350c88fe142fe935668d2f3b3edee2bdaf30779cf0f4c4d940bf7770d19881c834eeb54f355937801deb699bd00fae79b29a06eeaa67bc35fcfac4bee262fa6ce38156a13cc0217b5ef77514df25fc7ac8bda751e6249bb2a96244625f694c5927afe1405635d798a3fe9211a409bc6cceb67be76afdcdb860a3bc07beac8f5a893828ca9e397ec4447e68ec976cc3ce6d791e9585ec7701d69a667c7ef585bcf6fee31b69eedd40e820055270ee3051e82533583c8dc8e8f34fc56b4031395a45a6362c1340c33abbf18be22312a3086d889f9b851f103d675e16be94451144d4d1445d13357b540f98a73abc8c15f108513a30ded865d5b8714635a693855422ac28abc344099b20733ec4a602d797c7c40add059faba63a154d385de99e78ffc8264b6f396c3472c058c1532ca27b431b2be28962f9b74ea749e67eaf4db6befa2cc5584a24c1180221254d30211974f0953257888b24228b10afb3abb829cb9ea3c43503bbff8c8828c88e1fdc9806fe78e84121c12155892194bac72ea84ca68be7435785160756a2a8e98b448982599a55803a3bbb0d5b46d94b822b2646feac8006ee4c839a3e1456342a8b5b4c55e30a12f462efa9e96868492652d633a267ac01c29bbc2762e6431857175f4ad11dccb97cb60425fb9648033bd9c3520426d71332bf8d2f8443801411186a465699b651a9315f9bd6d6a6865332d50fad2110115d4f58482c564a24b2b0d5dbcf2ea0aa6c9d38b6acd4f6d2364243569dd40dae211e7276326c411e5178be255fe409d91d791dcd4d31a108c144596476a6c865e9a137c239d5acb3cc9e94e0b4993274a7a177114f52aeb68aba5341492113143c0b6aa70289d5ef8e051a3c64d9712146e951ae7f84ad8d40dfda8f1b4bd608ae959a3a90a8bbac0ab98b60aa0548e0c289abcd139c26b5a31939303821a0d0e4e1fdf5ad109d589eaed2be5f2ca2c870820377553703d6b56454abec2eed6ac65bebd508a334b26e96501c0086aeb4d085755727996f2189b73ce7711e79c73ce39e79cb766102050c6799ee79a9e49a094baa51c1a8bd179e1e3832a052c298b06168d97cd0406f61231c4c8da8811468458e044589b5f1d94ddde8bdd12a44429c5d394b49684b796e30396a2ef6f2f65d2a9172c1b5b6b55543b4a3b28e004b30c72639b81d2c1b9e4805205c083c95a7146a74411821d5b457d42e6864c2ced5817e69f3af85681a2288aa2289a9aca269f246240404a00e1a5db8bb322692d84c8328a4e136a69b9830d733acff35c532d209d1d384841c2f48062769079fdd8aac132697172218dc3c8140db023496292884c69657e6e617676613cd60cc484a2289a9a289ad7d1162b3b754f636633363f2fc8316327076d091157570c61d412fa69cc7eb94c9698f46ec0901d5146c696cee45e80d676ec25594a3af180a95de02c9c90553dba96048d80ea3be6789500437183e96bb96372a8a5bc64e32b4e140889c31194d667c3e6a54a064d792724f7da7cf10993049306a844ac580f4aa70cc208000002a3170000200c0a88e33810e4300a876bed14000954c23e524828104b45a1288c821804422006a1000001100080108661100a252a521d06decd6c3ff9791fa6a9bba3b2a478ffd7726006fa3093a99d30b318691373455ad3625567aa0d2038bbb60a56848bcff57c91574fa9394b08e1a206bbd14d392a8dc5e0cf2a5c454da2bbb609d39e91f61ee11450cac83449490daced7e7b5ff3da60132a1f73f722ca3748aa9a951a7b94e2263045b0ac49eaf03ac802067c5555d88dca2818c1ee36ae1a5539d82154c6a4009fb19e5972eff18238266b93b96dca9cd05dc90ec458016a1639fb89eb11e10b249e2e62cab6d16217144ce41eb9da0a21e7b5f9003eb18214062976726bdeb475969730025e8c40615a897852473452f5167b817d6efed22717a3d0000b744e7e34271c61a5267d655d9878ed86e897ef50c4e2f9143593c8a113c3f821652e2b9305e6cd21e5da9c5e5f8b9a74a80b65582c857549765c1e69f7ecea682fd464cd71b2f17b8d51cf33db4b7941df2401b1b869848ba894aa0a39317afe0040ff3ad331a4a3b69725a88b99876f7eb8076615318bef1fc51df027db6e9aab488394ce89bd6e3b8c48cef1bb3a4acf131e4e40c663bee191fc95d44c94c3041f5e4d35f803f6e620b6d0bfc9ff3ecf7437421c505435f9e06a95ba6459de685d23e2baa41e4f5ba314922d9e43868eb022571abf08002b8949a8be8c76bd630d53a74fa5cc99fe249db95f9dcb70a0b1e4a4ba6bf65595a4ee7f8c08dd085dc13ca16de8e809adfeefc376717fe8382af313d765aed0b3cacb2797e41fad15ca04ec44c4d23b3b5cd2573763876215ddf97a3a2137ce7973c16b331d9ae85f6b4d65329b183ea34c4703c737d6ff55b39fa4eb13fc942f75f565ea0e6e215ac3f7d642c0137fde5b6d4d7648012293634a12984cfdca684ff2adf0e9959605d6496f4eae4fea6f7f0b93ee5cd72a0ad314812287b27362d90dba29f3740be0ea4afeab38579fbd828bf24d939ea455b23a8a83dec8427263233342cca6a51b0e8e1d2be97bc9cf5c52bacd662a62176dfad09df53b3aa923e844e08d9dc05618952514a7ca0eece5fd32aa72b9307a17a5bad71e5aca36dde6cd2c4f350d4328dd0909a5895e7c51b23f86295a825d23b601c49f52a1e1911ba74081524cacdbded277f8c303b6e50c16693dfe65e462a3bfc6bfd74d3758c2899cd52a59d7a2a584b8b02542c9f2309bc3674dd900a430d1412ca4adc521814deb69194459eb29b5c899d14a3c6168f0c11822332ed5fd777fe07ea84cf7ef1e100c27c46d659a041e943853ee918a861188f0b64e464f6a4e5809f2b12250ccc4a4a21ba55f7a3b21142507d2fde961b41e0a95046978dd832acad1bc88c412f8d9cac41b8f420712f8376efcf24851955f22c36809409b4a6bc6ab4a6222c2ff5d3abb178e92ad74c00210c6b4d5ed063c4544e8e5fef147b9ff4745bdf83fdd16d24daed101e86e9714070fbbf1299ebdbc3837c417d86950628bc62a28396fe16766e0a519b4f6b1433b10f604d896a04183c8125428b0b3b92c41463383363490b29e1bad3f1b6dad102752824eb6594e36e4c932541f5c3606d340290a206382774966363d0fbd12e3af1146d1acab4958e5cd490c63217c33d1eb4abf3b127d86e45b14488db1e10423c59e9075a0ae0eab622345528124836d6f040746555ccd0604a55ee6884ac22fc93bd91052c61057329b4626ae8cfef39d30a4e437c41d02f2bb7e94dd51cf144a742952004ce7e49a9b568222859127c579bc7e8a4882cabcfb92542eff91c6296740f0d833945c9763c0184c7eb7e75712144b8ec02e48a9d70b901183295c9229d49f804fc8b4bfc5819b978cd5b6d40e6f93eb9890ac31f896b779f43884a3498f93f9042fef5c7cda922558aa8bc7a80c28c7b282ae793b95645ef2f3cb1b2cdf7a7966c0bf63ac9bc1913446d015a133e1834433345cfd0933212e897c4d31ca42a0c5e44fc0191ed67a165698d51071e40dff3228ce27ee0173d2c67a2751ec3f2cb457a00d0139e54b12435f1f4532d133abca7adc668b6ce3b79ad1dbaddc2a2bc9f0938cdf914a5ca71a4012c34f08c1b28739027c493d697ba776c7813905240160b5fa62acd9c49eaca6bd95596042482d49a11bbaf80fce12258a933095835ee57a91a46c45566c884389999df2eb7332da8291a6cf0a8727194757d9f44e4d64e11f914771009d13c614e1f424e0b874eea58a88b4813d2f7e6fe195dbe3d60889116c8218460101f010a4945f1f1989225da55c3192fec2477dc6300ee972490f85789ce595cc70517738dff0d2e783bcd052ec222699a8c9a34c65066fc3825826599ae8e4c1fc49e533cb966435171eb30202ea4bb0aa0bc7a800a8c7b082ae78bb2bc958f23765280cefa93262fc6adb4ea6e1b76aa55cdce42ce623faecbdd40e033958a6652457aa9ac6d5854671193edd6a82b703c974360c087ba68a050a82478264fada719292cf8ec285cf92b35c41558cdc953056fcecee12c3bc4d468eb52e0e30b09712aa5d6a0f017d128e4c38a4054be442981a688e6748f0bbc3fc072847d66e5390840c02466ad58568369a5cf6e10ff7a81ba8babcfc4a3568daa4c6cdf19325a36e5e38560c6fc38492c1f2bbd4e303234e276da0f1ba7b894ac823e233b4719b0e04e81fb422391bfc8e7d0621aaf2990354cdea48d016ce4c156928125369c843b4b9f018a9997009f4edfc3d18f83f9c0a16ed25ece092d913fe767c982be1ff1b5b0e5f0337cc4b151c4e81a2f10bceb86faec9e57ff811a084b5318cc09ae27384e00340641432abcb72ff895f402e7a9e59dd0bbda4d728277b0f81174e97f2816190c59d5ed341eae031ffaaae377c60f8f2c1fdaf28c2ba53cf06b8303e2091dd1fee26d0af8868ade8c23d876d752cdd7ce8d8b176ec5fe62449b64751426505b97d2faaae293e85f2037f08c4981e02b95ee2a902714c6c187929275432a36525f68bdf054aa38c8ba7f83239778a4bc4c80f69190e08baf89d4a9fd101ffe9c1b3d655b2cd2056901280268b410378cb9a164e923eb320d0403e724afeb0554a3fd868f68ec64498269f4ea81e86af014b20016653a5a57c8a4b500d85f71ec88ac686e6de3e265427e1fb9cd5aca43447bf0773fb132c35213b0d3e94b88aeb9106e422f6e2b3cd0c546f331b456ab668436281d96c91e9e0c4cd0faae9e4d67f90786fe24e14f0700f0f729adc98006ee8b5c86be636f11dd2537674ff4d0a2760e3cac2fb0b92039fc54f33320f8e045f570c8f4b0e964e8a0a21251848ab9c897aa65a1dd57a16ebf295f3279224835ef91ccfdd54f64a0f5b678d39e3890e3612fdfbfb414e73a75d140a9cf858d872c7281cc9af21a10255e6c959ab719eb595131a2f78f8f2fd2eed28a1179096fa1b4d4320099377adfa649ded02498cdc4491186731a5af906069b1987e844200c980cc9364072d5e7b5ccb01132817ba6a5251e40813c948a9b38a6ee5da5f60598d5f0226a890e493c2cacafbe266f81834ccc85007a75750d6425a861d3c1f2dc21e3059de0735663e424e6157e519231e6b28ff10b0853e48bfd1dff9a59622490474b72bb48b91278cd4df565a9a11f79dad4ad9904064d54a735c3560669394fbdcfebe846640ce24eb9eb85e288d909863fe025d4ec042196dd2dd3ef872d06717657e75120af6fcc6c0b964f33a494e3b862f675929a058af1418e6237b28041b1843e19f719239fcfb0d278057adee136aae89f5649d3ca4c71cbabf450e47765d0b9140b5902b330498c3c083d260742fc3c3ff2c8679309092344dc2165a7d4ea1e48673083cbb731eb2158a66e5cf5e52029902b82524d6eb4ee021a16c0c704e3b92c7bcb0820e9a7392b05c85cda1afa0206220729c2dd9ffbbcce660b03cf0e284e8a9a4ebcce514832e212e49e448c2c892e8afc80cc0a08cd84b90d084311e41f7180189ca659ca47d153776ab343c735439cfb6c65cfe17d2404e4672a34a6e076690930b76bee4702eedc06cfa1b1aba10b2a6040b656d80cf229481d31224a4ccbc7ff47790258213e61e41a9cec74f78606fb34a637d009fae71dec156c45620d42ed0f4c2719f9a51da63aadfb8302895de36edd7638d921be6d4082fa3d3d00ab0470a7b9e89535da773843679bf981e40beedc6071159b4bc3322fc5798003d35a8b32aeb2d3c696a4f05a3b136fb553e58f248dbefcdefbab27f13391f5d63e9e2c102f4aa3098037cdc0158529e147a04e312c774fc42d97e7a6596a8e881eb1231875e44f2f8e4064ab9b53a77645a2b08143401a9632233c1bee71fab1585373eda48e316dc2f77d5ffa2ebee67fce7f71bc34a19fc5231f90dfbe42e28859dabdc8c4f2d67e0af8fd5ec743b645996f4c6b73ca187b352bcb1dfcc7a58d9f768d058cda7ac82bc4e978eb0d66ea901a43dfcb1b09432ac9787306ca2872ce3b2db7f38837dfe3b509fe5cab51cf71fecf523518b389ff4e0f62025297653c69b92c2f90596be8285601ff8f3379d847ee179e62a0cef7cab723497482ea97c8c883d6c42554ed530dae30ea907628f4779b2f1f9dce040cde18d6874c737324fcf50bf451846059cf795eaa7175c94749a0a36053adc1d6b4ced44505f021f6878461731e6f9886b03f3117fbfa1bada380bb5750d9d2ac46e549f0a87818ba58849a5c02796bd1ee3e10548d901b1433ddd4481e0b08fdcad21a4a2fdab2749af4c9b2ee6f4487dc53c1237b306037c49a85f3f10616daa6b5cd3b0293d82f886aa7a8e58ae7802337fafa096835118f2492808e4378204abf8919ac8a22438c9bd81ebb68889531e54133f591f6677a434a7e9f27c2a92c98fa0215018ca7d84d06865bd555c18e586c777a04c67fbfb420ee111e70cfee2fb6c6d84c955419e5295b9c7a4a4c77d2b8cfc5f3f9f37b19f2560863eca0d05ff5838454c8e79f40107b60e3ace6159fc3dc3e7f0bae7ccf0177ebd6b08a08647f0421806df210c10ec28953485210c3f8ac0cbd98a379e5ca9d65aad072117b5c100b87629bdd6830aa510862ffa663322b4f60182e13ed6df86cbd10887c62e51a314c220619241e9516587daa7c4ee4960135062dae919bcf2b07017bbbba63e21f3a7eeda3529fd36fed3e6006cf5a9673033cd4372aaaafa0a91c28e9117fe5031c8b8511918e25224f64d37bcc1cb8e6d58476a0195c2a0c20ce55ea1b83168aad4839b7b9eb80a8a124848525a53dc69947214dbd9c3d8181823a1b051288f0a5bc031fc6cf72320da02765383d79690ef1b4741f419de4a63ba638f5c3467aac665caba5018c15db93aa62a4afaf5dd2ad8ea14072aac801d105f41f3c981e7f79505d254d582a7d3cd7b3f83197be696afb1471d842f5bdc32eaaafbe6fc7cc3e660ea6c4cebcf63b80101d0704df09d39eb8332795a1daed33b6c045017a74648bbe17bdc2b4c0a5250957dbbffbac451315d8cbf8c390d0bfb85309212c4d09e16591070a29606298c94cc406b53072390771e8777c54d5e6135e445782f873f9b300552c852197d8ac883ed32180f96f2801e60d79ea82b43b7ab2e1d94ac069757aa13fb357575e9c07dc4308191c9f90f68b4244de236e9791ddb0d0caddade33ff479b10e52aa2ea6d16428b2f011d464c109b2d4298fe36473b128b07c29810018b03dbbb12b97dd9f003fad5c6c4cd6b1f1088b1223a4159ad9367c7bb82944ad304322600a6188baea373f4a3ba60c014634c8a26aeed17803b12c12f8fdf460cdc35fa61274095ead46a1e07546bd800a34ad7a6cf033c4b512acf52116080c0a6c614c07ab8ec43100e59479eaf2ed88800c115bce95f17166d76b4c3c12245654378227154ee01fc1ef59b916fb4600ae60519a466a4a8f44c0f4615831a5001475f8554bbdd14b5b27786da434b274bbaacf62ea605350059e25dfc480bc51d4492d615f8f49d7581ff1fcfab9d5883baf70b1aa6eb8cced293688df06359982fddb76ab37c04b6064fade9d25bf010c9566110aa563b7aa4ad714fd3d86e9bc22e05c1099d563f3ad2b5d16e7108d067b961b936082b28e83f6b00b3ed978af2ec70b8f2c2f0ccf9db1576398c120518f4182ce2eec0f6d2e6017f10883c2da30952d1bc2f2a39b77bd0fa2c4d5aeec475b6456f9ef9e67e1c2d0297e7d5f66678091857ddd3b22d3698f6fad114fd414a80626b4463cdb54f305f0fda58a2f60d199ffb29404acf68efece3e52d2426a5ae19399e1072982b91c2164853dd3b478061717ae708bdf050f84cba4d0c808f48d547cae6f244a02a8c2a6d0520f151cd807d51b39d6d75e32d660850d466c6100f13f4164d7f80883ea003a04b42dad1f7248b7e0114f3fcea36031804a511112a62e52b04b89bc03083e343268fd4d05b61db6f06c4ae488f66ce60a3ac3f19bc6dba15f22bb4879bd1102b44043b8c70b4afe49e4741495f79299a95a2d1d44afd433c990e523f2dfb85de92125d9f451054f886149a06c8a6d529d87908151c1fdf9d9193c12f573a63cdb1124ab3b0c3cf1e8a502ebc31833e64c7623fa47ede3cfacf8480f01480c3088360a5b6a84158862478fdc35508010259eb978a63c0df0fa5e4e023ec86e5865bb62b5fbdc7abdd09ae43d77f5a098bc274a9823ef774ad27b9038dbe0240287cf481fae265471f761f834d8d9a3bb5ef1b4c2c10bb2fc19f5cbc1751901da151512758a738a8b72032963cf202d49240a72e5ccdbc059bba7a3791ce5360e6ec477b7435a823e6881522cba58b4d70cd7dc9b11762220c4c2249ae23a6a49721d5be6b96166d66420942b1337664446ebc5ab3509e66b17d137da72daf29978c9a290707ed79d61a9e76160cf4b406c17924d0eb6bbf6c8662a045ddfd5ab06c90e9a520e24b06aba565d986c02aebf67e3c77c5a80d845f386d9267e4b9497671f529237434e3a85f45f9ace27edb51e9b8edb51de0a3ea401d6558186a2f6ee73c3c1dc33f1691f2c648a6b7a3b600aa31540a72adea9f05fb9f8bd506effd5538c57202fc4bb276fdd263f272a1cd9dedfe0870a0dfc836511aee2d74839a71d752b428a83faa22c1d5b858dc9499ee084008f12aef4d9d30f89eaf8b78a5976d58b3fb84347133ea3da9eb2ac0ab46cc4f475ff9c52e0cc556be15a70d0689023d50b0716f21e85821b2168a8fd064ef079dc82eeed535714ec8929620ce92619a5c8681ff0c14cda98691585475da29a15a38ce1ea60d663a028d0c45c0cf0758e00d0ac1190ee278dc5c0083b901917569befa5c07132d7bb1d556e82a47d43b9fc46b30d8602f3b6c4505d1e1e1a412a5cb481061fdd38cd26582c386e1df9754808c03546b41e0640503d1533f75ad1e74b943a5f3f351f9cf8f46e9c343036a6a0a33d2e589fc035f0316853eb8a1dbb1173ae4b6ddfd3d82574e7f7dc3eeed2e4f03ac60de0b14ffec6eebc9197a79411002b91de88dcbc7a426aa6748cb4f2e14830229ba5bd4a505e0b7d2b0ad1fecb2d7d7057cd49ef167baca5d9e1baaeb419cc6891dc96f63d17ca4d7bbbf577f523116ca4e631cb6ee099fed5779996c6f67bb05574c3b1184010e4232e22ca99dd16c9d02fff15975078c3831b602b3de0dca623cf94e6284bbb79210e4d4a772ac2cfd98cfbe34e483efac0e061064d3878800b71ede0f5c2b98850d3e210ac1e7781fa92370c684019f8ce48cd59fdc980536d96d8c6e0e1b71a3eed8132c90cdb931b604c99193cf659a3f41901690478893cea18b2c2aa4818e91449030f3bed48ccf3f121d16a3d5039b1d20de89abcf3adf7e7e3080e83e4670e3edfbe9410d1dc1298358cb6ebb79cfbba3266284aa80cc283bd63bb3f276f5562b03f13f013922da47f4bb8e77a8aa9edead140930b32d8302d876cf0901916cf87dc5f903cd549253d2b35c4e1fdc4a4321e78ff214317b2cea3b1ee149aa23250487811e63d043a2127cb4b1a40055056282ef2a45481d0500df65b6c327f560fbf82ab0a339096380ae393f6d7addd1d2274214be33148084d340f892fd84af3d75799a54ebcc506a1944a42bc7f93453cf12b31ea912b8c754b0847c4b90b5869859805436853498d5a1f635a74e7234be9874848d305861804c84be1b7518fcc0683733611ef08d24ef465005ed38675fa1b1fb672fe96a7e52ba9e4366c4f20168017655afe9693f85c6a08e1c1553e21dfbab9864e449511c56292a6657e7ea388bcd615e97103dfe02ce0e33121634e1c4e6b496180df48a4e2a70bf96a272323ae58e771d6b97546dbcd6aa7e5cced415cb661d7d9469a186aa16dd5a5fb310020cdb079e6afb8e6dd4ce05a13e7c3282b8b9986fa10a8cc74ae7375c94f553a9680172de8308a4c13dd1e39c901de59a502985d10825471aa06182ec743a0decc03fc06ccab448d7f6f52d603fbfc0f15853d1ab42dcf8a227d51d986acf6f7cc10324cac1c3314b9088794b80b73fd91eb2828e46e195844e4c89d1107b895adc06f4ef9f620758224fb6ba30dba71660e8cb68347fd2a614ba1129b0e8b5d6314f371ae82c827b23dd697bbf7d8eda2296b050b453578625928f187ad34bad1afd68ab38e5ccdcc4f3d3708b458dbfa1f1b4e945920bb60833666c08beda7d1ce98ced4cd47dc91e0e0d9a04fcf2a51968078b5352292f16c081cc82b7bd90efc63899ee3afe35c534f9eaf78b1f0b3e1a36153ec493470fb53575da7b559059dd3a5e4dae1ec0b5c8de7922981e2c6cfe2626c664da2307013804c37c153f109c199b147df6b78382c014673e1fdf178fa4e7554294b4530223bdb70b413534c81c5470b6389be1efdaa1f8ef0bc210f84941f9026a71b5b42e026804b21ab81f39351a91c9d10ed4fba95eaa82b27b91c7e458c2979fc26e5b92bcda7a9c9611a0d2c4b69b45eeb366b8ebe27de233b4d90de0de29e83e803b6b9c6bbcd0172a8b00d7e9e07e063c4602ba9c995b90ebc09c86c7ab9d3bff16adf12ac2431928730675c6d0bd16bbc4ed4e51485fb101a5861519e591d2b322b64b2f2749744e94c27062f1f647946212bc86af6b95910e9fc89dc696207014eb496f01c287388825c8d2ffa189258a1990551f1948f77a6024e685c06ec1ed37aa75a3259a8a4880e0579f2aae4c044325838bb598516c280e50682c4c43bfa9633f5a007e1e746bb610547bdc5d69d23ce0c71510ee3a5c6c7e0ac684977cbd83b8955287db7da12f1499df3143d8548579e0f6c7cf44e4f6e4c4cb5f7f56f02299d29016f404cfc18e9423bf9bf641831556c7882bc6bd04cc57d7480ca2be045a70222094b674eae83983cf76415708f299e3ac0ac786e6401ae34d4fb13689ed68dced9059aca56e5c361ce9ffc2dc2880fa02282832a74e8b319162465e985751b0c2513d77cf8b97a0b40f8310da92581982a37af52b626951c29376f85b6b2414f2b152500b9c1704a5610833ab5fe85bda6815bfacf247b0a0b1d926b015addba8e2aece83e05d4e057409e7be38a736269d9f14c133e5eee2dee92116b6609ad4a36b753443cc403bdba6549fdb8e8bf045cbd3838f2c00153f161b3712e6f7c6a81df88a4b6462365680daff0242022c3b5d11ff3285040f66e5372f17725041f6fce1b29ebccdc60c3dd986fd32ae34d6f85e3c9aaf4cf83f866a5252f740537f317e7d5ea4ed5daf5c00b0cf1ad555a728466fe6280db3e5e53548beeb15fa395ebb4d4d075d22d488d91d0e1077743d27122922595763edd1c1bcf8c52706835b91a3a63d10ce21cd2e3e8b217fdea281590c5ed80fd0c1b0045e5e98b074cdbdcea276bacf82eb25e4f34230c56dd2ad7e7ef91940406c772b188a9efd549f5d2d16e8891f81033245931940c25617fdeed82c65a09b61fd327b84415b805a9946e68a83539c9c52840e503ca2c20fe4f6c8b46200118917cd15ac604b6c21771de0a2530cdf62213d124b5e397373f32b6611dd6a6967662d460ee20e50b2f221df4e04d53240510c53b5e296d8e4012bfdca7c3d68b24f374157c52c154eaad0ea408bd68aad00ac3944d1d3e9ad631a0c1dc78d12aad6b4fe73b561a7af226b120a3ea1faecb38bc6820922d57c8f6b35eef4fe342b702a9c20c19dc9c57ce4b8706c05d35b775c0bb6c0a9a0269802b44616b562eb037dac5a76813d7a79237979f344ba3a65bd81cad07c9b4c74195597689be6fb85984279da42e04d77aea927153010b4721fc1944e25486178020810a556dcf554c84beaacde7c4b218cac5d9f9380ed2aa39380090852eeb38bda85dc3d3029b96ed61270d66a6b08d80341efba7b366ad1f5c3aab7daaa076fc119fbe9064c0ae73e8601811ab7de82468ad78d11fae33b0daa935a0d19b3412a62760851c7bcbd5ce6b08d082e1a00b222389e81765d0c2ddd4c961e534cab9f198a69055503c4339623d83acbef86c123b50d873676595ae4f03e3cb5d57833eadb30e4586a999701475cb858d38c91fb6818d2ffa1927b82f2376a82ff25ef987f3d8a96413d650cf2606115aa7a874af433be0cecc51bea99cbef81b462d24db028a7ade516203c9a568022d0e28706ea24a9771b27674ba2efd566881648722e214b28e79423e4611a9f02708826331a2aa6124985585a8af28756e9d8e723d32920ed69ca0ef511fe32f3d75a43cb223ba72475d097d9877e6f3691f31f0b8c8fbdb8863005fa708052080a6515fa814737cfd18415f2d9939caaf00893d193fe47b22e21669865e7dce092180d9028e0019e5e024ad7ac77941fec01662bd48b3d5eb4093917464dd9f5847f7c53b2a31d83b2214f416b7ae459016a0cc35129a0fb7a7a863d47145156d04c4bfe23845052f7af54f3b1578221849bc62dd420fbe99e47b9ebf97dcd129affc7ffa9156cf08ae8a57113959acd1e20e874a915b26e1bd900356d0515f5cdf0f133cbdfd194561864ceb7125aa12d8dadb0585a010c4635764198afc98da252a185b486fdfde5cb05bddf470d0b3aae0f80de4d8104c0b0888c3ad2374033a852f6fa7279990f0ab4b54a3970a678628ddf98ce02d91160adcc4278da1bf8afb656b6843e4a70dd850d909c1f5a0ebba989ea32c5c658cc5e627a31b550d4470a8cc3659af3b108983e16744c2a96ef8aec87aa7f852da6a5958558113cb81ea80aeb5de0a8c6d24ffd341e592c39cc3b7a117ac7326dab52b2e44aa64eca64d9786a6489890f717c62f750ac4ab2c3baa1d5eb7e4c2015a66baa59c77eb22ce8eb0026d4666d8dfdc474cda70b022b2cc8a366ceddfebe4cbafc4329fb893d2f073ed53641536c6f9d84f2f5d646d87212b18cd77fa211311ba78a112ef8e1a12708f39a2c2b191f83ed6683501c9fda597d9825bac133888e5783fdf7462c23e57379d7cba597b914d351f065ebd41d8e4019504094606efa2cf36996f4301a5817d79413f9dced5153f4092b651775440a84bb8b5dc88a2a92e28c38506ec493f7d9ef6237d03680c29f434cf6e64e5619102216300708aba5ed49de04320028fc4e8720136313667aaa539144b5b016a68535d2dd145090ece8703da4efba40f7ef839ed36f74193da6032d847d1cb79bfa5a4c1612c5207589edd03178669e6257ce0800df96048fd192cbf4113d4c03ebe21a58bb9f333ffbd858bb17001861389b778f70df16c0000cc171bb66137109fbb161c1b36ca346740467fe193eb35771432a502d56092569b3cebac99a1b7bda2c0685c6efdbe76cf5f44d6cc45ee4084ba9b26fdd888c5741a4e4da413d0ca0e4d47ad5a2304a3de56a2a0893e27a86df463c2b838c944884fc2cc6d44cacd19d110ff91866e3dad932cfeb714016c76119c86ef92f17e75b3c0c07e5193ecaa18eb765b66dd4105dd2a65885b6b4397685bd7250aac6e2ba40c339d1afaa17a613412246158cf4b0c79a4037b1364c07eb958bfbd887f932ceb0fc80e045c0a00ccae1accf81b91e0f0479ed7213970924ba692e60218b4364073235803e72164eaf0352fda09b2d03dbbc10bf5256287dce189511378f5e183bd4b7dd733d2a9f38eb2d0408d98709b896060f9f5348f5c497b447769c4f129ca95a2b1c5e6556ddec87e7247b44c61f5dfc4c48d1269eb402885766afe0da0f1ebfdb54bd5c1e0532b31403ebc8c24ffe2ae385d075cb0b574e392be7dccb9ba8ca0062fe371148f6f226430af9d79a92acf7a7c1d3d6daa4ef097b5210e0ac4ae11afe44d6edf204e3e7e0cba31b9a2df3ca501a4be3d004c9e67f9680094da1f7db79b9286c257ddeaa604c241b4e8712cc8ca28939b5f167fdda79b9df02d811c54592ba87382f1d3a8a45cc81cf6c4ec41cf3426cf916e88864ed2bf11e70216f6082bd4007ef713ac0ffe31b010aa4e94a5de06e69b3889bb214b14582cfac6744af291a12de9acaac1e07a2aaabc79cd0593137c7fa393d61e476e24099d2e561c1fe8ef19847d7a2cc49aa1098dbd164e39e44188221acbb9defbcb83fbe074d7008a23f215aa7a1e052ca81e3347356d4347c887e3adf37986be6669d29e08f9e58a81fb6fa45c037f1ad428bd549348738a20752061a2fe7ba0af0b5e54121063fb7e5298910e693f8a1a5c7449ab7bf6994620787438109a6db6b657264faf1bfa4d03405811b367e18c7ad50c5fcbc1d8ae79ae6e00cd4f47a4969ed92b96267bb6b7b2d158a9066217befb4c622c0a4a2ffddc2e40a8adcd085a59b1518f30c011b7986ea18629dc78f2efa92465a83f40b2c5d8aa8e299e455c55d81af78779d82ab0fbd5dd7b6ea2d648b2d6431c454513a26db370b291ecb0a52f208769411ab70e67ce1c245d1e066a0461db3f0957fdfc31bd083e5a58c190921683ba80ac0f0e634fee4a06e0554ac495af5be633fe9278d9ff188d2869bf1e18dd33b29a5e41011b08ae21b3710ffbb1fc24c604837ca89d557f0884b524bfa093bb14f2ce95b2ac0f977962d8c550ffd3e1b943ca477ab9622bf2f42d55d855439cb1d911c3dea6184d064ae174e0d44451e462b36516dd753eef62bc4ed1038cbd55a753a6a214f6b69efdab8a1bbbdcd726ec0f43a6ef98e189520ffac0bd22335a0fc73a08aebbced8b065f2a21922602f7d93bd909800dcb49510b911e1ba9ec4683acbf107f0211247d4fc703fe3dec434cc017c14d81fd083a9e27bcc8b28f6bda40502674d0153d8e42661926e80a791c0fd921ee02b0d1cb5baca27daef83003f879321d07a88629fb63261b9d327fd80e0797dd5f829a8899a7acdc9a2fb632ee50d68e61097fb967f10f79ae3f48005303c4b053593ffd74ddea5370475997660df1d99b9713b48e96e49858c8b0c8ba5dbd8f3d3f5ff545c26c5fbc4d9416467aa72a4f970b77434a54fba472ea722dc6647aa6b59f169812e8041ddd9cc44462813268541fb91114fd3a47de6a65a488528fb262c0a8a0232d012103ec7bb96f70e8f69f16f67f8a81a11606769df53d3552b73cb7cc81c7ea5586ecff45c91603d94b0029d44152630952eaf2ecfc77310221d9925f987061018cbc4148154b90c58f936fe66f0a9f44c77c50b20ee66262d3aea8fec277ab51c3fc967153f89aa425202a43887f24d54d9fe9455fb6a9e98fecc4438b782f75d795a41ea998243d842c1d3c4d902e5abf5f64bfa4d335b7232be9a20411a5bceed109b549eb46fe91b0358013be0d7e8ad0cde564da3b2bc3c9794de68d41c2dfcca8c340b7087f8b45f14de888b9cd0da3cd0b59907b312256576c0688fd4d9da9863b1c389c89c7a7674f5c8ec2c06a81be44bf1a898c2785b57860f12661d23ccf20549cefca2aeba1faf7dd09217d1945157ea7bd36313130008941002fb2934c63fc68858209499e2a31e936509de9fdd5d5e374b2700a7b0e5d680b7dc25c1d981002098e783a3501cdddf2979fd7dceee3a962083c2f585197efe876898c4e23728cf545bc0e6d1678a65bb78e2018b0db7cb8cddba3fe52ea860b6c88c745fa698435e80aa8a8e6e0c7bbbe7a99ab0ab6d0e4f19040ce27094df4ad14a996f64271dd2b5dc57e6a171acacee74e52be5b6f1cf7fb1dc5b86894eac67eb82b4beb199b29691bf91c496a244b29be69520fa8c1966ff28610f543014ca244b57e6402663be079942bf83b767e4fb44afc00db2a9dbe62422e1320560d8bedf32a9c6aa6a1131879db25be37e40c22d1f0f0e75549e1bd21409fc8a612ff6a4fd7e4997b55c1f56e8779a874385d8b004be8288665a7dee09124c9a173340528ee73e29fb1b43f51f143db5de5e86869fba89e29c27129dbe757c1aef4a0b4aea2499664348dd455cc6f96876d0bb56e1f520ac4517c689db730059003167b47444485f4aa9c516eddbea31d6e0daa1c109086d92e160067f9089b485fcd882e8668627a8f0f376303b8a9f68630a003df4742bb353c98d880371b3d46120822e7e0ba576cd40d041d1ab572ea601e08e392d135da3a82355eafeea1f3ff31d3c1dafea82a93a315cc823e782b4cf6e8425dc3a99d77e8f02951d80f9be8d689a296303f5a56febc21feec93518e34d0dc353d6c7df01a6e00e19d026b896a96ee1e320f9260705fd4ceb1270743ef38cd9ab639644483c62acce4e0eee07b1d45d2d31425d43a4ed34b93ac52b689062d01b403eebdd3a090a070bc77e0d6217a1deaa7d3148a7a87be1c683aac3a585d07abc7d39458c892a1e1c8462d0b11a68e42ba69fa25235eb4b2652838ca8ed388132bdbc1a23a9aa924ea68d5412f87d81dee737077e8ef4083c38a83de35a4af67428edfa91d13df749f5745430ef438ace300ba0e92b1b42816a991d3641e19d9a0b51568ba5173cef6cb2371b4582023e568b9a28923d0a1e628efaabce66c7b8e0966c40186314b395683a6d9c9489396104446403482a40c8e4adf71fd1e1bb5cabe6448ca6a7071a48c864b72a363249db640ca0a6683de9d4d29fba0a570322248232859d661b68e7652103aaac53547c3a9423cbba37f743465b93e877e07a2d5aea466b965998866a1c30923e448b202475743eaf233708c91458c6b9510751d7a72601a8721d734cd2d245e6b96b6b3aa285563d25464291b4c239620cb41a48e96a685221368cab2be43bf03bdc36a0d5790c638672c346878700dba77ee5a2e244c1a54924174c8efa2490819014e2396321d741c30e6104358ac9bfe4e9ef14252c11922641d48af438d62e33f5634f4dd027a071a6a5e400ec29a0e8af1514c28cec8ce63f38feadafc106de6cb5b99a17d8dc8265b61ffc8691ede2c47ff8eb4756e66846579686f7b197373ff89a6139da89746805cddd41d204c707c55605d43ec356e58da6432d242dade7b6fb9a5942949196a063f06d1062d248ffbd427bdd32fe522e1a5dcf54b837932c2e8598b615a74aa620bbab2a8330d4b273b6708963184725011a3551192b1a04a0b396856e191e7b74c0559936c69cad314a7294d53987a8a6c8a914f299a02a35388a60cd529415370d8249cb4bea1cb0942454c9647b21c79beeca09b73869e23b11b154c919a70124d50601ff0122c252907c6ae47657994c40e7914b5d88d070894c8110394254a2ee0c2d3c3112f40aaa10a275f40d09eb8a01dfd782c9c182d32ebe5a1594db3374b2c3c51e221868820e418211c7010111292c4488e4e33fac1f183e3fe782c1c293e64f608be4f3730a1657924450794a72860a2308181e9c245142633c68f162589a669f4c763e144b1f2b2a903ee88e011d3cd488b0cb73d2ae2894c878517407894b117300c05bff88147fc62086fe1bd3d40b8f75e4d6707c6ae9f591e4101936fb23c8ad203ca531e45fa52ce49fbdea6d23f1b94b664b43aa5c2c8a3547c4617547d46eea35a7daa1955cde8ff8faad936dbc2b448e7db46d2921a29cfbf73faa55be56c5739db69defd34ef7e1b8a4b6d282ed5614fd5614ff5ad5032724e3aa59cab2933e7b3e8fc194ae7cf396773ced89c47e6d0143293104da129e79473ca69829c724e39a79cf435bb9dd6ea3dab6ca7b556ab7956bb57bbdbc6755de7791f2a95f29e29540a7ba9af0656ad54ab95f75cd17c2f1957cdf79ab1b969dddc78cf1bd55763ae3c09ca7caf99658b352438d39a69b5bc67ab86feb4763a6a3c01fca4f44e7f46f3339a1f8d4f8a916ab602ea9eb1aa6661542a6a543f1a94babbce0fec4e93c50ef3d900330d37a79647456e95d367a49af94e8b6854cdf24cf274ec49fb34920ed7f1f414260c18305fbecc9f51b01f40c396b9506b4680295934264b6fed41dde59d281f72ffcd5c9016a28a244f4a28282594129a2d3d5fb07ee99dae4c95a97ee92179fab9a0a1ee196b8fdc41dd3339214e48f78cb3471899599092aee2a8b2a48d8a8a8333ea268bcc19b90fcac977fa3b8843cd646ad8a8c8ad248f2827d44c8648f2f4a3661e24796a2b735238a31639a3165b0a6a6694fb5340dd330b8372423dcdf72b33a3a17b491bfef55ffef56fe439bbddf35ef4ab0dda547a4f8a1a6fd4af2ef76dabdfd5aeb5d63ae545f5bd365ef5032d727defce72366abc91ebf7baf47b5150f2d4ef656f22a0bdfd1d9bfd5ef546d6be1795f26e1cc775dbc6f9085af8f3e33b1b573b97ab2341b3d5dbb65a3f8539c66df4d7ae524a6bd31adeed7b49d9715f37d869d3c3d8fb46d0de7a1f095a685f32d7ffbe1064ae4e29a52fda7d36ea7f35bace03af2ed32ed7308539668ec9f495bbfbd5d872f7c9fbd550854cc3c63eba2e25bb6eac61f398174e580bbbfb734ce67ea449524a29e55694bb7fce39e7eca6dcfddddd7d879adc9d6a4694d2ea54ab1dad92dc2fc6e0fb9ba77d2268d9fbee6a7fb3a63de8fdcd5efdfe5ede77ef9f0dafdf59685c50ee9a943b1c654e0a812ca350613eb79b1b14214a7f7ec0b4eb7460a84f654c20b8983f47c42de186382515491d2277fab925b2a7c620c52ad422924a4335413d528dd42295481daa43b821f7e16075862a43155263a830d42052864aca58714335493d411fa0a5c2a919968cddaec675a8cf6b7130154ecdb064ec7635ae437dde4a8553332c19bb52e114457d5ec76d576bd1b46cde88d88498621c8c83b9cfc8156db014914b29a379f9234e09a58ca48252432d36f782dce9a701391e7287cb21450ea8c517381738205206fe7eee07e7836b41ca90f97eae0717c4f14805b94f05f21d16b81d9c0e2e0757020e07072465544f050929c0c8cd90fb39a13e40ebfb3b4cf3299a57ad687ee60338db97013fe0ca36cc8126ac59e48e152976162976ab7ea94fe4fecad404e8af4061059a016b5255923cfd3260d52279ea96ba54b9a4c0ba040aac5de8075624c9d3ef81f58ae4e9efc08a45f2f45bb066913c5556ab489efe0dac4a489efe0b562b92a75a0da5bdcd34606d81b58bdc692dac5d244eaa32f799b202c928c8225799fb705ce52a57394da3dd5a8852011057274ab4825d499eacee84e8fabb94f46f97abafd6dbe6dffe4dc32b7952393ef38d3c3f1aad1a6b7888432c79be17abfbdb060d41b0bfd7eabef63bbe9b08dcd79e84ab71e1ddc2ebdf4bf59a366638c3c63f3535c225e1863fe18fef58d46bbe7cedb33143aa7d2f4f66edab31d3dd31626c5ff8a20698fb5ee0362fa7d94a5d3687a21f5a15e6984c027aa3b6e644f752e23ccef9a2d95fca2b25751a065e3b77db795a57a9267586f0c8ca7604d9869e27b93ae747c3378f4a19946ede95f128121a58eedff2cd14fc80881653908b8b484d914a82ba826281f342619c2ac80d57305c44ee97a92972bf4c25c9fd127525f74b140bb95f7e2ec8fd52cb148411c3e30ad612923266d3e611e9a070623826ad20b97f32794d5bb882794472ffeca0e4fec989c9fd936392fbe7c625f74f9b29e8628899980bf60ea1cdb41aee0c11b9bf5de86e2ee45ec1ee10b9bf35ad86dcdf5689dcdf355310c7896b667368b509758206b9df3bf6a3cd91fb5d0b57b04a9dc8fd4e67c8fd9e29c812028f2b188ba81d096f27cbaa21f7d3990d1d89dc4fbb75c8fdd452d00b028f2b984cca985a70e47e192029a3ae603255544c9348c5f4e3d492fb6bf5b0528d7e09057095f5ab62ba52c67cba5231e519ae6051f0fc71b56d28a78165d9c18d6db66e702b9066999a9a2a7d400bf08899c0249184f444062b829dc02fe00e83673a6cb23cb26282bc657964454811429dec523de0d95ead763f0668df02fc4a9ed94e9a86dac8c94926d158ce0f393fe4fc40a783f427b8e59c1eb0d768a1501d68e514480094b1bd914968915e5a34410d4c1393f52313ac6d3441fae434bb3f41b73f95f4d29cf64bb01498a367fffb68d849420923aa0163eabbd94eabd552617b65eac0d185b85132cd591e25d33889b49ad303a6df1334a145fa394db064aaf36915c247747200b806e59b692cd3ef727ac0236d80cde38d4c5ba4d49b35c8d3f0c456324dfa26d4099b3090020181de329f4e0d76ee72c4d0bf41b56d732959def934868f6851930fb59153be39e94a6d468b68d12cfb5fa2392d535393bf26ebae2ee443fe1a903b8db5511b39657f0ba3d4674e4e4ab27fad2d9326d154d2d6c1647f2b7dca241b7730ddd3efb1ecaf4919eb9ebef3ef0cc58329fb7757ca903de02dcb0ecab883be07d80ab25c2d7a0e0bf0289bea674093c9b77dbaf7cfe901fb90fd3b973cfe394db0e7db411d986ec32b79bad2c984a5cb119323a6ade8b0023467607a29c98d9ed026b04a74c42af1438920aa5571aa12e656994dd1e5c0ea3c40fd7e17ae1ef65c2e705cc628820433a992822a4bdce6e2097f5085471522982c07153132a7195c9e68810a723401d2c4087468c18f254b6449a20a9725203b400942a905463ea42081022994f81266c9519125ed88bd957aff782c9c2a4f6022628886602b984a182a60a82c51416202e3249ca81ca1e203152646e47cd12328201df583beede4d44ede4e63c7183f70721f24a0a5a02f4260be30df53a78309a781dcd3679443a406e5b1ce5c3a45b568b206724f9c131378ac33eea9ceba87f650f9bd280a484a7713827ffd97bbbf58acf6a477d310a7e64fe956f268ee5ac5f8decd7de75aaf366adddc31aeff92d9bfbe3fadafcf467f8df992ee35ee57c31b1bdb0adc924ebab09ad6755aa775e02866cd7eed728ee056eef79c22b80b47edbbd9badfbabf61a875dd77dc7b524a29a553d6b49f73ce39db286bdfdded75286befee4e9bb2f694d25a9435ed6bb59ad6853935e4d480fabef740fe507f5fa947bd7f36526152d61ea5759f365a25591b3d296be10ab28c82bbbb5027d43e5764028ff8b6485d1ab0b197fdd43efec385f8912459ca981cb2d6badb25b2c7ad92ee7177777777b743ee63dddddddd5a77afd6d65a2bfd4ad3aaf7e975d81fa21552997288e0517421cb4181bd3dcf531170b93ccf59acfba294865e2ae786ef4559ac96b3bf176e516e360c691016cc1982e7091ed422fdd162ce103c7a50a635a945fb63cb6673ced9006d6ca25953acbb9b7e37c5dcbd299629a5b5d68f868f3289863b9863027c7f7e776bd4bfb96afe1ec895860725c0f3bf570d9ebf5a3876586326d7a056ae61cb5aa4e1fc16da883afb5d2a27091e69b6f946b6d659d2a53b97ebf7666fae3fe7e76f43a8d54a2a5fda0897046b3fb7b6dafa79083257b7d37eaffaed59cd82734cfd2ef580fa9a779b1b8a3e75800d3be986fe09e18dd94e6b37ed0eef3bd47f4f02eaf33a4dd334eda67ebd6fff8adad719f4191975777b0f0db9bbd34929ad45b3a8563b8382ac36caa4a41c2398be954fadbdbf7920dbdfb8700b67f2fd1a33337926dfb09d344ddbda4b3293754f2bcb7f6a89bb91da47ba0349190ee3ce649bf9f63d1c69501ab39d56abdd8debbc0f95c2aa950c6ba645e3aab11969d4f0c6c19b1ae2489eb982455e72ffd7d4e5869f91e4e97f61033c72432a2a94727f46b01679862aa71737c0e34a3672aba7dea9afe3ba90dccf24d71f3f21f5476ea89d5a74587ed9e0d9ede079a81f71fed195bbcfdd07b9ebc4163803b2401950fa8ed7a1de9b1985fa198d3ff5ad54dc86c35438273751e0cc9cfd723fc8f7de0ba37d5c62ff65727ddd5cde1e0226b88f2df2225b344a265be4b2455b68997474c0e30cb241f68b55f5668319cce479f3edf4bc8e033d16f0ccddbcf393be5367fe7e6cc3cdc4723db21199f0067ef209dbddc20ef6420c1eb98ea87b9c6a3b714e307aa7d6d8f2168a1dac4bc2d4b59644b441cc27d8d76add8098bbe9554dc3c9dac8ca9accbb8fcc4a15e2907b2fe4c42570ea51ffbd0f76f1382eaffefc68dcf07652a43a7cb194af38198ee3388ee3f0a7be4bedc033639427138e627ed1a842d4d7189cb9e79c42fc473fb5934c7d318f798c0bf10dc71879c4b7c268f2f61ef37b311ebd7bf458eedec2d4500e0d78b4b3d9ccb586567f865f3b4b1dc9fd9a6cb6823339ec863b5185ddb091ad60d3013187c82385848531424ca92269888603164c86846cf882021b805c6084213f4ef0821fc43449216310020c2ce040c41339a4503212c40e43d420c31020bac8190466a00208103f9ad8005300162650207d71818e235aa36200537080a4ca16237ee800827c79819b2b5da0cc40830f2223f9640224848c14e99001952e4d607c450926454909c6ae6f42e9871c2f78bc59e678c1f23b1ce7bbbe37daf3c68697d056b674995b64ed2d27b42a113184f884134dcc765aad7637aef3542b99232c376081a145e3aaa137e8726c80e77b18982e282a620640e00c4e48b628d9d24948be6568550495e1a16e09b25c159a27c5dda2d8b82e9ccd5aa2785a62ac2c8fb43041c96479a48588162058698c4a6906577ec8784858f3bb99241c2dd414341e142e8e4bcd5116249b569647599cdc1c6519b2627ec7c2c3e23493e51116265a1c61b9f2f33b108b1033591e2969e940690a38bf7be149f0e88a979ca32b4857a48828ed852e670678be97a39b43c47a00e1436de43495c8a40c6661221a892daec42f78bef738701079c4b19940787479f70896591e5d39c2816e0ae1c40819cc44e8e677bd35d899abdea5b6dcb514a1cceb966ad56b905b860fa52d5766457030060409ebc1ca0d6690832f47208c18480c358dc4c3d5a563c07243112c3004510a62338526c50d14f4000490189ec44008cc0d2150406181131eb818c1c11625417492b6c4909384c4e3c58c10b41755f02acba3a422927888a1832d412e8455b4004af1e3a3e8d1a24b0b2c94585811c5059c988f1b26426cb41089a1c6b500f9e1521ae3028dd20c845a2f9a242d41f283951444260987d06a0a175450fcc05c5a4821f5403999e1d341062fcb1018ba2c412f704cc8b061b982e58817b008d143498b0c4a53ae5041e48a97a12b4840ae48094a7aca34591e6df141e9471e638850a6ce8e51ccfdf983dc2f6fbf8b892826e08124e43eb675846817ea47a3439a023c7fa45a77adb556dadb4743c70896e148f3c4eec5881c438cc98982a1e0f9120848f22081e74bfa34749f73874e76a96e903a50ce11e168011b74d2c5d892eddddddddddddd4dab6dadbbbbefd6ddddcd755e7fdddddddddddddddddd3de79c73ce39a79c53ce29e79c724e39a79c72cef99e943d3252f41fab8e2cbfe2903db82b504d76d92f655390eb287a826577f77e927d6c0b1de9a12662d24a1ad6b1c909b5e82ffe80bda4d6622dfa670bead8a129955e922a48cae8f757254919f39daa208f5e527615136d09cd9c227037933cfe35a865644ff531d6649f2de43edabd18bb5cff2aa627d9bfa35f0d55926a49a514e4197946ee1965f7aa0fef490dea1e8f08d9d18013e284704fb820b8261c132e09774493a2c5b427bdd32348f654654a2909729f1493d45211124c8a922744410c654f3511caee7dc9ee4f412f69a977fcb50e554192c7352f898b491e7f0be40ebb59d56256d3eedd36aea35de73dbbbb715cd779ded779df8742a5525845552aefa942a53056a9562b99958c0c8bd59a69b568686a6a6c6e6e707056ac60d182b668e13d5bb824385e098ede956087b3820565c1c27bb2689c8bbab171675a2d1a1a97aba6c6c6e6e606c7cb1183b3e2a25a16c8144661b4c58480a6174658053580b1220324a2ac42dffc806315f39cfdfed51067ae33c41da819058765597b07313e4c718115295680b0b2c4458482ef959a36aded5a9d52ea5e3b67110a9e79bcf265f768777fd119a5b5bbd66aed7c2de7e868363b4a618e09ed0e2323a376ea95525b6bb5d6da27f7a391f364368bc568e43cc15f03b6fb92d5269d4cc66a222299554c3585324ea1b2fc9c17b4ac7bbe2c7f5224236a9ded23a90faba96d56b6f9b58f06adb5ce9738228cac7990b7cdbac7c2b6a7cb6b513bb985ae4b492d07dc5d4a7a937a78e527428c068273a4ee77ee79564a299d7af7cadeb6b95554ade1814763b6383f1a29cc2ac48cc92ad09fd94598c99486f6db57c383dc82e39ca3a8fadc57c3834c5ddeeeb17989993e4ebee0ed1d1bba954e5135bcc975876eced9002f42c2307d11670e9ca024a28c19c1d689a4f91fb40bf7b370b2f49e08cae3e3b8c83f598927c10b920464cccb131d2b18e6171c4de429cba326c0e42c8f9a88a2092f4c3835b1d4c415da84cc864e07ca9197264c3e6cf2c5613250972f79be045e0c9151591e75b17243888526805401f264086c00359801092735784296440b54481e3f3f28c0c1693882c869f02364e81e881f1c38948a6490054a0b4848204ad00cb0142c39e2881134a046d4e0352070380d3f382e13429eb0e18921978c7ba2bb654e964871e2048f143431e3871478a101081f3754717bc45cf35c1e667537ee229072b70b16f65c5e67d9ff4e72b7c7f2986860125277c041882342d38791a422745878c1e588e9051fda9323a6168e987474d34a2b0dec752e9c77413bea6115e8c0417429d2c588076c035e22881c5d82f0f084b4450aa01d3964892226831210f9d114030d4438ed4926c1800483199e4ae025073788782032c3e10a0e4e7c1063e4c763e1f4907643220897262e4be449002e397041c285ca6fc15c9e6c796a6579c46589621d57fb09a630c7805daea0940370ba83476b786fa6fe20507003bf771aa7c5c7c92d79fa6f72bfdf641f426749dfebee3f79461f21b318036aad4804cefc2cd664ddccf271f1392b1051f8c9b5c6d8d2752949ad8e126cc3f1c776f76ada4b6b6badd74a297dd1eafe72db9dad7834dbd6e06c87631d43c828fce47eaf425a95b556116aadd5845a43a8b5d6ba7d7d134400b78fe13b9d0029f6cf0065de065cf137208be7c0d5e3802dfe82fe316658ac16275042dffc7c391f46fbe0703fe74fd923e2c07386acf67470c0e394c54880d8627bdf8ab7f91d2cfee6518f3fd4e36f845901b9d3df6b1291627f05a4d8effa9a77fd9c35a1cd3a38e031c6cdfc157f03041bd0e65b3c0936dfe2e7b7b8f915e10a2cfee65758f1367f038418fefd9e0e134cf3feadbfdfac9749618ea1017368f12d30079c6781f349fbb4f8fe89f3333689dc87c5f74d7d06dce7e61b3da35eca47a95eca1e0c808c4370e5da0a67a414591398e14865c016e00afca0c55a7f05285d058e53490726388978edb2df0f80e6b1cf811fb4681f0784d13bf66fc0eb3b7644e1a7c518e07176c9f7bafa0ba26304077941275d883062d4bc7c1728001db9d381145d0208000f0c0078b023ea4819f767e4bc70d14108c65c3f7ee0fab1e700aeefef58322b154d6be676dce67d388572cd88b164562a9ad6ccedb8cdfb700ac592597956d1b4662e4ba6f34a65c19971cda76a1ef57935dfdde7ee6f37cca1c6a5bdad7169a1cd15a4526cf5ad42b7ab71bffd8efb1af7db93c06d57bb776a5a5bebb5524aabbb6d31d66d62b5a1943c3586ddd2e588d1c101cb975a971f591e2d219798794b302d81f4248f942e31449ed8970822dba0d3b96109ccd29725a425294b412c35594ab2746469c796279d1ab820911d7069c22587a51f5c90e4f9d76e40ced9ed4e69add6cad75ac2c812069aa6699ae6c2d968a59556ba8534bc4058660fef0be6cccf6c65ac70bc915930d58fce440529f68fae439e373f7a129c1ffdc88a1f5dc8e83f5c474f5752f3307a667e8c11769080192df063b84f07a11715197502648f47b917b30847bc22c409c70f471104fd41ef22775aba2c8aec712b45a0cd6e45f6b4bedfb3c89e2db2d5fa9befee47e346bba9594ef75a20cd2ef3e99e5d067676590b9cd9b348ef220b20e500469c99fa00069079290bb0d250373fa35b2d50cbe0df84b2b30167be069c99f99aa7a199b133343335df4d2f847bb1ebef267324d0936a404b5404b79efb1a90655403caf4d780aba61a505504e2195403a6b24e113ccaa41a1095758ae0991f5d9669fe669a70f440a6f91af0cb2e902604479125038efe65f427e40d723f76190afcc0b10ad518912c090ade6e652c3a35c59c33854b2bac3fcf1b2a518bdd5721f74185558950ee5a7443dd3a2545bec38552f26cf651a8b0ea14bc597e5eb7a639622a9420c2c4ecaf82066c2875f78237e0b07c82f6f6b5232d3627b9a17aef57f065ff3e0af55ef7a4c23adec855044a7255fd28821c720d47144dfd8cae31edefc5b8c65a64edc71655e5b5c83854397df8bb4efc9501f9fe1c57e0867018428aeb913f5045456e282cd327c43ebd2bee29f7328afbdc6b344b1e6b4c4631a3d4824191fb6bcc7dbe23b9d35f25e64a5f9bd3a8c44db62635858648008000003316000020100c090402912c89b340cd7c0f14800d66884068583618c863a124476114833114639c01860003883104196588ca800060ca2805e4112bdcbf57f8c9e468645a4ebee14164e3a00d88d866473090a756ac0482e8dab7f3c3542de536800d3e8200753b9a5b62c34cfb79b872a498712fa8ba413326f91c484690fb50948dbf73b175740ccd6434f521153fe70c891477c585cb4dc31ed30d723f4a1ee24f432e506fa8698c603f696c934ca0d94f16723f3e29f0b21c39eb48527ffb6f488f45a14ccb3d0723a521ec8deea3a47c0e2ac4c160041e00644e95f8eb3739de147bcb98d16bf7c9f9b9125b88c40f24c90eb96b24d124629b16e65f50c32246214bb1b8336ccc171fc6476991f2bf5beae347dbd88ea3966c3d6d7ba005b68ec1a71ad65d9da9044737a9af686d93974b2f5a55414c4eecb2b650b463115bc9cb0061266e8d288ae54b991df2ef9b9a206c0ebe7dc51ae10213f15f484abb464ca3886dff9b1fe1d2558936060dc6899fddb132b7ac47ab299670e620548aa13cd675d020f4b16ef77c15ee9c37eb5728632f624f051b2c40b18f17db1e92993ef3c04a241959525f44f9c802479c48780cb4153b75ddcfe51093ba308239198424aa32a5b97cea4a6f459136cb5e9dd753fded85c68c1d63fb7dcb6a5c5e6e6b927aa4ceacc042a6d62aaefacff527a7c6ac6830d7cd93a8cd09ad71bdc7ef5be3bb23b439e083d30e3dfb82eb27231804d19799a1319fbd277881354b30bb5093aea290d97a7c5419535d40f3f6631a2aecf3ef4a9191ce860ea44d86581e53a98f059d9de6563359bdd365c1eedf92c9dd241dd06213320324d6927e0c6c54945d620c3bae9e02ffe8a8a8061727e1392524f29e2a05b02fe0949787c64627a6f8d4c6362e0c711419c2d05f7abccdf5c3575ba0811fae739d0a256043c1c716f098e02f7aca805861fa35c897285772276608b30b50ef48ef1ffc07916473620965eb4ab1ec21903365550ad9bc75d6aece520d807b0ef4352e51c10fd5170bdc7df0f3978125d60c065dcc2278a28d6c669d23c97ccc997e97b415a7f558968a32a6d2609fab5b308b7c132a3e9b1ae7adba398f2cb28971c23e79f6d989da5f6cc295cd632bce5d1de96188b0f2115bbb14ef6e9a0152e34e6b38a7bd5855a567cc82d37305feafb432754f428dbe734a685d855133a2d80c5f8c30b6bee08e3df162dd2ff791f41c5b834162013a2c4d2a05ceae3a4318276b06841b0bc46f068b59b24e4b375b95b9693da445457a9429dcbb4f11486f7eb5eaba0616a106918b4df68fa82ae92cd8eaa337ec6c998b2639e079ecbf5adad84609b7fc29d3f0988e746c4835880bf0bea3e121afaca1745016df38c6c3c894b79b1a2d982a37e20ff8a9cd04b7b21ffd78427c352044a59e40942a90b2abb1cf3fcc3d2147b8945100096050afbf2182e550466821e6e220a7a0e0fabeeae8cd8125504084e6f35ca8bf7c08785ceadf88107828157417f88ba03a612e10b840a8bbca9ae0e7c4646a5e168859b6c888aad4fb35d25ff3c23c1666d4681beef2f6b7fc2d0b6dbfc11db072499dcc140db15726c3ac03341c31b1b8522539b652ea667a2ed21351e0a978732e1d03bf2d34cec22e120736fe6de21832450662987e2767544e4a154e465f42a032440977f8176ea24c61081530cceacb8af72c2ea171d3562cdda7a79b866a9424ed64a016354a7d9a6e367e21706768e60cdf8706f885efdfcf204af9db72810811c04cf5ea7d248c4dfd6d007ee14deaba4275793b3121f152e1322a03a29781886c40d4c2a05d98e37f611a446bd687d3afdbc5dbaf3ff56aa47aaf9860cc07613c5e2fde1e9a60504d019350b2e6e63d2a100d178259160d4a218148ca24b5a5263a7917424040f95146730f37d3700838061296ab76a784f35384b4e40c015f29ec320aff1df2deac5d289b6352e66033ce37790dc597fe7fa9b1de02907083cd0a939dc0bfb63b77962b860935445d37a017ec0873218291d8c113843e34e4b41765fff8a30c3f3e29ccd72f05a8f8c6605698e2ddf364abca32e776b3c26e22b92296e334b9d6cc92fdd458c78becc08c351f8b399258940b9450a2d237e5e0510a235e12522f6195b3e99858dabf8e87b7a03424dd5ea09c5e1c71f02d810d774275c09b170c08bae1122790c580688e75453543b9b863f069a9dc45dcfa865c4938218a15558fd0feb78852a6cb20604bb2ca9d0cd1febcc82c9a6db1044aaa101d3aecb8164e4a8137517eb606660753cb1cfd455bb98a19a18c27825d2942bd20da0ea74d4181838121847aec954a4ce82be256b9c4400054d52d941b515e59b4e75a7e7f474aca2d63ce48c3accfa9dab35d2c3f4d9b6a7c62c2e81b882607217df151759ae02882fc891ab0a0f8f5315dce7252bf15db21e530a1099a235e82d5de1c9bcc05c4eebc40c4a724c01316d216ddb899a907a12c9a7ae3cabe52c39eb4a037af2751ac5ea9992c56ac9ad033764cbf6886068bd559f38faeb4128f950e354f10290e94253c0c0ff0061f454f959537f54a06983dae662cc24b8c458fa8cf523465fe7234095220ef1b921bd2d8bf5604a4c98d35b6f287edeee42898fd8387583caecf0ac0e284e5ef9fbcf4b51ba1be82b40cffdfab308f0198c725e0b745b4f5d7a85b5bac36f04443b55ae6bfc7cfb00afd333b5420a4198d3345c290675b2b0d42be96a76d85b33439642d562fac7082cb46702747b92b797a21536d6e3fb05630891c3b13b9698fc65b35ff7ed62683b713b970528dd56dcbd1433d8b10d6aa77f6348849c2bbe076b92083117e3c6e454454c432269017ea95d19bd813242648d62f8585a8fd92059deeb24be44fa6fc1c2136f47d63d9c835ee4bdf53d856b433792cd719960a5880ef29e12020715c46a89f96e06dbd6932712c4a66835df537649e8e2a175c8319cf8b06c6d227c98b6a9f54f9c4105841e6d764949b84101de421e1d6341b95435e221c857358e6c0024e8a5c0628576a97d495d3dca868e30467b88b72e7e2f7772071ae817271ed87ae71c2922e2b815c493e03a4de8d7fee4c2b74a0534bfb90be6eb361285e6fdc263458b770056ae9d5734b83e47d7bb8b7c8e88ac95f7aabdcbb38947d8d706c80a918021a63f48611963cacb7c619b7a92c6b7d55d26c7704579a562260cbbde879dd27e11e4474d5b69156fdce0c8779add42549cc20db318566ca7e02c2aea5417721aa5df10a3c66bb0b1b085d685c42d6d15de38d35888e75cab2eeb26c44c477b973a6ec6a8f84c28311a10ffecb238909916859c916dbda9a86346dc2f97215af99f5c1b3622a916fa9f42dd1f205eb07bc004cc15c4a387981675173571330df3878a6a49d3dd91ed784ef3f07b2fd92e0e4c9e87e579cbad00e16273bca2ec4e40492542482178833251fca483666dd8be8fc69a69d5d196a1186b57440485d5c0a9667bc9cd2673f3ca53d27e1c1380e56515fa04ddd60c11b3320bc094730bb362b56b94488573910cdb5696c9a0eceb31a136f9acccf14aae7057bc64bfff4f238e44c0db77769c4c9365353630f7bb09edf0d2ca49a12b6571d087a09a110b629915767ef6e9c6f5a4dbe4eab70d6dd8b2638583f9353b333d2a3db67cfc93b18d14276f9cf8bbca0b93d4d2af5bfdc8a3eacbff7a650eab897a637365cf319b5e7ef555e28ddf8857f9c0763be559f46d21c6acafeaa6f7dd0c1234364e75ee459ebceda783ecd2ed106810438b56c03d15d8b19daf08cfe1e798355519ea9d08fda10b5182325c1aef1ce2070bbc1fa4ea3a0e7f769685c8b327831b198c7f7a786f204d6be3dd0449e34437094deb7e7ffc5e48abe215f8451c4b26f5b12af45046a85aa879782e89c91ba6e2255496b1205d76ad1730f2bfb5672316be0ce0d69ebd01d88481254326829501104148ad25d41de95ff4e91bc39474446bc2ef38e0f89a2db9ec8563b9869d27d669aa4ad2685bf00bd2ed7af1af28bc5660a938535c99598aaae7bdb2ad2d23c9f708a9bb81ac4c118658ee419f4f4d56e6ef89eaf6c48a8618300171e716f1f74afdea63de8f3219afc00761507e6e620abf4698bebb79aa7bb861efda4230187538a26bc8203735583b5f120060d5cc9552cff2e4b5e4695363d44c154991f7c2bbb0bf9a679b845022b133534ce40aceb2fc8a8ec7d3fd6c4b6044dc9962c24f4024426330830d041c524208e657dc2be75515fd67a49d500677ea8b4cdad10f8c4e00fd07197453fe94a50893a8dcb57879200a82a9b03bd6a4ae118072200796f2e23402bfedec7c3a1c94201779a14d3ee40914534b74c4823677b3e7e3c988789e655d7f35630c4c03fad1f0f7108ccd8c3523a6feea74e6647879c4c9e2a7aaa9554f5121fd6141f10bbb1f0bb9f55efaf5a574897f3181630b177554acf2a669dd008a15501e65f7d90cdd86a8c421349f30f73e9aa658fc2c9c84949f8f39f8275307ef834bd3113918e0048acc37acde63d52e9ba4baf177860d944d4d7dc5248c406ec900f707c7a92df0d4893a80870625ecbc474b66f41adcb864d01a1eee612569de59bfd321c4a3659a948445852ca3409cf782f03559a3a07d32ed68721aa1be375bbd8c77c0acde0e326b622259d14b9ca43de706ee43c47c4b3d0ffd153b45bdfc2ccfd46526c076f2c5aeaa3515433079d0a670596ec48460f1443595510feab00bfc2af85cb590c679a1131069ba0b1b7a3533874f6dce49b9dfab1d2f69202ce4b2fc7a58b6493c9c6396a98ff608499c8e8467f815929c232fabe621a4e39371147cb60a3992e34dad20298f014b9122f357a567a00a568fd75f1623930f0dd1a8c0de27b214200fbdb01587fd87a8ab7c8acb782069b27901bf9feaa15882e8369bdd85a470e1d0c66f98ec2dfc9a7fb1dddc9607445027a8a4c0c5fa9d19719c0ebb2bd9925ce39bc51ba1f7743121ab6f4bd1858b994c83f1588917f396d1d5fabbe66a2da29d5be99a0596ac8fc35d3800b986da0e006e2af0a48b6f8badb517cecc07e83e46c0cb692082387ea285a6c08bb0e8c3b7e483c0559d15a50c7a949b432ebd621b0af5b18220f415e30cd41c41e245b43459da8bc71b89f68f3e81547bbaeac402776b4956546594bd96dd730c9063820bd52e5840b4ba6d60c2fe8383a5b43c23bd08db0a88758192f541a8aabd46d34ec26cf02892002bd69045c32d785debc7548129495d30aa7cbb4514d6f4097369b721d7a03fa8d1a0002d0763bd02a945c181d94b55509d344b12fd22405d224812020953a589c60d17442af5192c0c8ac8115fc6dee807d1af90c2bcc0167eaa5c1f17f7cf1e5bafe2c6905b2c351f86393d034258cccca5401b5ed1822fad18da83f5ba1048737945155f078f8072057c960b777a45554a654543e0c6a9f821d28ef1fb6ab851a2c2557d0301ab1faeda578774dd644cd78f402bbe49f52b2e226da7aec539db427ce842a22f394a25e5c56248204a5c08fb1d39ce2b072c48321c3e3ba04fc062935c8a0bdad95fc83be478bf31035943b86068023e302403c7c41fb7702d21a96530bcff7106ac7c89a02b84b07fdbd80f642e78694bff054e43c6a7590addc3267ed0b3f91733b93ffe800860c63c9144b4fb6e7b53516c56e85a16aa95b019337cb6390f5c6b075fcc9f312385a1319104841bc992550baed9091459126ca61f7706ed9a824ab0c47d9205b19d4377a4596ccdda941222d8b7cff46aadae3f625cdb149b82a8264adb106c24d97ad641c8344589fe0426b2e3be760622dcc5aeba226d587b54152579b84c7e6a42b9ebd29e1f52412df92c410d99ecc00457ae97127a52294dfc79dfeb34cd1e20f5b3e2a184b226805e2173db572ade2232e353f96be06e74df2c8e7edd613ded9e252f9f62c2efae566fb9c8f79cf362bda8928eb2fd716953fcdd068b295cfc96d8ac22fa125244c1346d6e4eca4725c01a75467d90c55039498d75204a41cc86e91594980856185fd82b61e40d8dad25f899b4b6dc5666155375b8967746fcf63e9183b1b2524b1a26559463f061b7a4c0d9a5706992aa05fcefa146a5ced818932f9df63759c7ac2b234f4550ae1125a0333a8d12ce46cd62432a266c105834da3c83af606a43c45f9b5d6627774830219cee0dc10f706b27ee6d469b68560f0248692697836e2076502353f30642bc75a18aaed3b9a9d1e85a629519e4a383c45641146c21e15fb20d6f268346b3871d6205a8a8616496bbf26ff212ab072cd7bb8ac8bf0faf3e51cbdc84b808f5f2478cdf7969ffbbc751fe70a2c5fdb1da3dbe614ef596eae578e2a658d45bfca7e1b59ff7f0b65e99f13807ed25382de052b334a090e4eda2a2cdf7b5d3622db3586643706d657526fe9df489b6dc5bba7bd1a2f2a7dd66b01672ecf5111681a8b3dbe28a4223374e5b23e05f99e9e6dd944420d55aa83a03695048b69160dd8abaf544b389258b95d55870c33780d3f03d793c72079a4c0feff9bdca83d40331bb5d62da21b28badaa818d2681d026dc3a6d0dd6febcd132112a345898d1a4254424a05e0a50185ac635e426dfce2e15e5db14c9366ce9d56e9700637262d637a75cf028d1a3f40ce78a5948c2e94105faf0929b13c57af7c228b73fdb31f5e36a7d83018e50739d883b5349fd285e180a0dc4537162a9bb9a574386670b149ece19d066ba5a20e3eafb0ba8fc50d45157d458b094956e7e0869b35035a6cf822d4510403f7acd08ad0fa66f12d956234c4bbada10cd2619716ab0bedb98c0d93c82842e185d63f06efecf79a9620406c24c4c4680c95ffc3bdad37ed902504456d42fc00589c21aac450d49a652b87eec30d289969064f2f445944142a4927b3c373cd4ece203b096055dc9e37541d877960ae1c82f8d0348ba45633b5782310d3a0d5aa027897d4c3510baeca6ad79be06ccfd7b5d8e1912a46a5f05fd3a07d39f517890276429b77a5c700485e6b16057625331a4a910626d6b271c4110b4e721909a35be82bd5ab55d825a70fcb6e128c870596b0592cef20d3bc12d9f01582acf2b4f342005a5e784c4d57ce084cc5c9562275a545a5e50fc5a7438d23c96d034fd614a89362815eeb4b13f8c74e8c5b8622b0601aee9460b4dda85fac4c13e8afd68c2d3a2b217d8a2e3cb2b92b46f9074b63c74cbc6a7214aca8ed8e09bacc8b25b272b752b35a380ef60213b2564df5248c572c6792d0bc23d0fc6268bf05e65415a337909cd15f706869abbda9033c1473991b99b7db79e14b12d5c50f9698f1cb9f8ddeb637dafedafe17a85cc94340123c1f86975e254d59c2ca910366032eb5f2a323f20ffeb6b21c5edc7c405ee0546d53ad557f97ad11e6c4071254337f9440504d97a9c64c2d8d6c5aa384e9937d8577071a6560a8e5775e2a669a14d8afc0738a7d7491791ba5117ed1e8e7bb56e80e300aad80379fadc749f0861c499e8eb2e27020e4cf406c8f06bcb602016a67c10eed90146fa06d0ac88409ce9ddf1d244ef554d59a81cf751c7f5b04240fd10ed855b132722fc4b9d91b6dcf03828ffd7a197d4cc2bab2030aca6f95bf5eb4393d272407c161858c108c107b9cd85d3337bf43f2a1ae9e350e3a71163a1005a6717a0baac829659be34b354bb718607e4f4d17ae542c0343042793e22dd5ad7b438a913b9aa8ae1503a40013392ba3c5be7d19b596e6318837aac860d8421cd0be9a108f01273c7576dbc526dbca825638a16436551738e1e578b983d297b87789588bdb405a9b7959009d379df6b5fc27ffb0c3f91096a31ced8ddf949541df476f00751983912114705f2504338d56a681a7deafbe4e6a914728e8d681f0ddc8e8d58039dc3489908a6036b324bb7c54985bae8007028b63a788febd15ba83eba3d32be380cf0e4289fc129abe116f299c803b37c04174a2f1e5c17ada2d4b679c4466457a171b880f07870f3a38ea4082fa60781f3d6450bee8382b1391e46ce2a3c0b8c41a54f53f48e759c9785705a125bccefcc8c016d7b51778d765503ca99b0577762b35809240cba8c5d22b0ccaf8b7f933fe6fc5457eb6609450f823c112219a51b041d64d9ab0ab612ae3d6a8525b275b57d8ad808bdef09d1b7b400ae3e3b5480a5ebd662f1f86a32c0f6bb2470006d0c9916a34c02973ea250d6072c3bcdfe39fa51e069015616b23ec98ec9c989f01ca88c32a5cdfd1520b8487c69d9cca554351a3dd376ac1df314e031261b2195424704cad9a5beaa65b59b98210b71266074fe5f8a4e0e7fffc40c2b61c62aa8aa8cefa8fe9e8ca9a9f39eb9982cd5f7ef13335b2655a6a86d0a9f17f8129395098e44770868d9e8e8dcf5552ef0960a864ba8883f1bcdb36682e2b3704e8fa598fdefc3005e09d7b7064649d1f50ef15b51f309babb8b9878556ba961d7c25cc3787fedde98727943e95450d3023f230524911a25538cc9a4e8f6818a814eece236e1875e6696c65ad92bb91ef66b163a670ae3224084077b1ac9195a0466b121c7fc47496a0fe3329c869c3e78a330bba28eb716a1aa67dbd491999b7c86df7261bb3c57bfda69655405b341a37329974b8cb9b377189dc9b38fe1049e9800edaf030562ea5ecde545336ed67ed481b3773f1be58cf62cfc2dd22bd05ea1525aae60c4739202a700d5dea2228ac081dff7ff09e86d3eb20e38801779e15d77a10c16d4e2b5ffb60ae66e2f372d2de55e3e896489db62223b96debd97a357dae1735bbd87d9d65aaa999a91e8d60e0d8bad7348570110c1c81311acf661d489f3277f4b8d7a27cb2b76599104236ebc978a17c2aef97345cf296107edf341e559615d620fdc089b73ee2d317119089b8e2cc96865f9eca2620bf75fd968d8814cdac34f8f3b42c01f2afdbef6c47a4989895165fbbaa7f203342e8ebc9540289192f82869cb287d09e82082ae6b392934e652771a4a1880127bd3f1efdd2f3b94d628bba13314df9d5f5176c26ba4831a5e38be73208d47f5dbf631331459a291d7eef542681e7a78edfb18998e23db53cb1aabe7335b8947a21ff6d0ec8ecae0696aa2fca54da90de4680e6d047be78927123ef0bff5ea0abfe2f6c1a111545241eb9e57b905d21be8c134e9de3a8f4481475aaaafc249ef88851dba52e12a170a644fd7d3813417ec02b471f12e2a56ab882f5a71ed4872c8e35b704367c72ef3bd66c424c0c3eeff45c7ff0ea36f9772a5d9fd4ec53529fba40fc8c9c553460d2652cbbbefb7210362fc441905a31b4e5e108d1ce2e4337b2e7ec2d5ff5e14d531a7039226ad3ae2f7721454b3aa6dc319e052a94b6c8514dfd5be6fb6ec3f600b8bf7bf9e9eff31bf69daa8b7d9fc039989dd2c7ed6a80ec207e37767ca61b23207463a59e36d84d940fa74851e52a1cec19dc5f0d3e79b1118486a985f07aa17eb2d7d49bc897ba753c9a1cd6a61baccf9f10540da614eee0634023b4f9551399f63e912972d2d6837ff866b904232e1771c4eb1a4875f1d7640b0b7d3d01879bfe16f571d80ed7107955c1c9e6a693d5f0629aef5a7163e9eb1729bd334fd5df9552131f80d9e2686b5fa891a3104dbdd7b39d3fb4bc88ad42a57a3554aa5721a57a152ad557a392b312292d68a554185fe9d0c2255467847a3a557df3e3d49946b96db5bf50af40251ffa538d9f5ecf2035d97beb8568052a735780520ff5f4472498b6ceb8da6bb41a29d7ab51897b059510dc9352d519bebec66a140757fba5f82a2aafafa2f27a052b7357400906793aa50af3cbe99a44096bb5dc3b0c6e52c038a4a934c31caadafd19ccdaa7ae2af2b0821635c957516c2a2ff838d02e95f11c52adfdc810265c3bbdf49e04f58d421832d92bb4d8143e621021bc8362be7b26a0ad7b1dc8b4b456336dc28e7d496f2f630f5e9a69ff3dd4f9f0b58d07cbfd6a69601575aa817791d9931e52801f298a656164fef3aa503f3d4d3ecfba32a89e202257ae8c18268808dd56867b1344b4b856c690cd050ec332e2b070335efdbeee6428e2bcb71cbfe8f433df4a7a21843b5e136f7298c2b740a7a0bf00407e70888cf985a4bb1c1eea868398a0ac4061ad6d3652c19b2e01eb97d9894f19dc3d0f8370e99a53c35d83bab486aadbc38bfb0989b6f1400e4b5860f3f1d2090753247db4a6c7fde21b2b16926d4bf099c1063863d79621c74fa3c2c53190906fadd8b08f9740640e037961c546507ebe9c886322c30ed811862a4f8197f4e340326e5af80a85bfe3f3f0e9e672aba1ce947a5de018e31c3d369b8a814f8d28d004801f102916b67f3138fd21ef237354ff5bf9d1a27cccb19be92db9ebc2aed649607a2431c63e473fba9b5df9a286c76e5a03df1e7de8e3cff05329862200f207e52404b0cdd2b9f2a10f268b1af33f3ebde74240e4806571c1392ec7a765b505bfb2801a58c62a26c17b84a1d99d6033795a3a64fe00fdba017e5cc8bb0b023fc3f1cae3b893fd60b613c4bb29c4bc0e0f47c8e6895cd6d88122e881e25cc98b1a2b4ac05feab897185ba3a152716922115f7dab8c58b28242e8b74e3414a3c6d538d7dfe79217a2ecabb74392db331caaf11ca9a4f067b618609f9416f6501a3f3a8643809c38f5f6e519d9084289f6614d7da7f709cb7e32be85e86b3f165fffe3b3eca2e737a8453ef5f812c3591f36461fbca4f85e0fa2155d9ebfccbe48bd4a5f4d5fa3d4cb494fd43df9e22c2657cf8fed0ab0f3ac33b3ed4f9d1a2ec903775156f95900a23d38d47ba13228e4c6dcfec1f45b45b03962896265bbd544b7588e360dcc0159de917f0c59b1d53dc32d727b77c35b8ad24a46cea266e05106b9459d33ffbcd20d8214984ac9780f24d86809e2868b229d5a431c0a114a534f0d1e95be03cb8dbdcba2f5250b00860ce21d08a25c1c5ff0c172efbd9a1f97851ebcefbcfb1dd546389f65a7ff30e9c1b0d7a9687b2b9ae9790b039e10777f68212d4c8e1d3a96ee1f13dea1e01735fd7182367731f849b2c41c20f0bc5146c75352a1468390fb4cefea930a49b37f3617410744608cc37b6bcd2b1ac29061db1a316edb8a6445f87ef75458451efef8538bbd293b21df778c1c3f7325a1be87fa0e0084fb32f452d58e72510b5b6e79fcc454a2c0202d86942bc1261c6b5dbc738b2807def6cf0c963377eb7b5c6a87bbd36a036402e17853830991812a7ac005b351a160a9c9c00e4d1f6f2a73cccc6c5813744fbe2295932fa6c85d0bd349b0160937fa24e0c883117985a76b0da7f26a941e5cc7785e3bbd127a11f3adaa6f7989be12d908c760a0706533a0752f6eb00cf7876c4a57f1166b79445548ba3e9d58e5838eead1d87bec329315b70dcfcd75a6dfd4a59d98773257b8ac245adfd4efaf8cabf4571644fec226c1e4c274020ef4ba180771087062558117f4490814d4716bc7dcbc89a92069d846158e494875c238efec61d875daf571511f3a5ab597ade159d25d504988d0670cafc6282ee2a56d4f189639b2da89bb37a9cc8c62b6d5813f8f64af12bafd48c64daebbc44294a168f5d43ba6809de26236220ec637ca768fba7854f6f1eb72e58071bcdbae1944be2fbbd5d985f909a03a0c4ef51c6cbad78df43ae0e4c3c3a4d2e8189295d5ecd5a447bb49a3ba2b89199b8e4aecd62c3f3310059336b0f57fce5bfdc8d73c55207f94a88672ed024abafc524a041817d3e49ddb1f27f9d660801b5b7844a30aa42ce2f614352df815ccc14cbea1efd0d4811f4b745d11b7b97424d316a03a5ca586af4473b3bfe50a2be93aa5674a0cd5318251f9a4e498fd5b45caa236e11801d907a006e6e82c2f8d86a8ac2d247b0b0cc74a6e845034041a0264c409b8fdc45d3462865bf13284420b2fea8851b9da835382231f7817f6ad458a8270e615ed39611cf39f69fafbb6ff925cf3e2338332c7bf706bc3c602d5ef35b51ca5028a0255736c063685b9f39b0bf8080bd19a8ace99626ae1af0e4d1f095bd94cd753f3cea2514fdf7ea12bee72d35926419554149735dbb6bb9147050fb10a0c60789ad466dfc302128c7e2b9ca5e1b9bda7b1dee47e24ac3b7a296bf98ed5682c14de6c5cbb86405d41efe66b1d8e4bc501f4ed98a0f79fbd3da5311afb266c32dbfa7e567713e8ed2dab354537875cd934e7e05d98b10077e0184e094e848e7ad7d089f9257e11da5dc2d363031d462131cb57414feb2f24b727b7b8a263e8e3c6ddfa40e0a72b4cc0d3e0dd9ea6932c756fd4225a10b5fca2848c778412949233736beb3f6b50dc571b86d49ba4ab0ae588ed390d37a3f1779c7776c48f9bc438c50291544fb02fdf3c4eae66f416bee6d59579c5ab98809ba7440c15c36238aa0315d232d4ca2d78dcb76effda081063eb712a08dc87029f385e04c5a50317b14e6bcf5099e93ffa846b251b52ebff839db862252d0aab4112cb24dd8449bc0cc6f97f123b5a0daa0099b01390039d71331d0ad72818e3a56db3fffc332171668383ceb91a81888cb7f7dadc76c89a7188d3fe72a04c1d4fd178606fb99265af9b43bf9c096f12d0410f61e0ec7ba26b5252986824a8a5fe91cbabe4e2b5a752569951b4dfcce5b5843087026fb934d2a582a0920e7a84f2430a8da7990b9c059a5811e654a30702454b3957e05c31268e70733df2b67163a6e59bb471357cea7d4a71a10d7f8941a1424b0de024b8461689c055a3bb2dc4221e0789d2fecd59ad6d8bc8a23dc741272451bdad84b26f233a7abb50cb73119075bada038f63a167d8f3821f5cc6895a1eae1a997c8e10290ae8667303f33124709b74cc19879c9d80d3b0ca98b65354d28ded24fa996199047d9c2ae0e95c1d08fe286fbb62f7ae34e94d77881488de2839f1d6f4955cd22d7a426ac0162883a5e6a0493c9caedd3785df74d2e3a21ad822f37868eb9eac03bfa819fac61dd4af9b03a6a03b582d520fbe4587bd6cc512e317cc288457b1a1b2689217170a32ac472212e82d6ef3e20d5a89047af3b4f308f44604983b925b089b94315c1edd220e3aabcd45c0341d94cf1da059f0c7827aefd15f9d8ed868dc18327754231a0dfe8aeba9a62987721549116bcbcccf665603e5b43cca7279ce6a83bf660eb7fbdb0d4a9303d1885d2573571ed8c7057ffdc46d37744d113f369070722181d36305fe5a1c45fe3f0e3bda99a59788459005029617564b10fc357ffc591224f19e642c1ab1998c5292413dfe1a572fb4f00483be1a21157f7a4e676efcd5ed29d3c92a6a6e1dbdf9ab2c4b9803d3084600df8a52d6335cb9a8dc1fad8d243f7aa7ae2bd55cdef8365ee7f5b37c3474862fa6882a5ee3bab067cfb2842c8f54d674f824d28036f48282af00e0cfc2399785f3c41142008f29db2f1d0e2cb2c74ad930e783328e0564046254e6fbdd7952782f3a75605a10d3a52d27938033990fc1702dbd069bc8b21cfae0d48c0742a9395b4b2374b271e0076e3f28d418e16556ad49fd455339936682e67b8d660eb804892ac33538c3f005920859920d290191e881c37d35649511cf4f92a62b97127f6ccc247cf938b83e504b8476423e822035c1a018da90cb906d97dac061c706d4b0fa75c42a73ad7dcb4236a4661ea65bbf2ef41864252c3900e80a47865c465894d3a09ac5044c3e1991ba601303f9c4e02b27558224613b11bc8531708de288d6d739829da6f2b5a0c564a887a00d24e40fb9fd23c0d6a8592892a8453a2c225f1964c3eeba098f1950547ac1cb17cb5806dc62c0c41259e252d187d5f4d5c09684d965dbf56d46b7f302f3a49f99164ea04e01b9eccbffa30891ffed7894ae8a50ade354a3b8e5ff7973a520161d249c1ddcb0e5b2495c59d6ebceb477b6b59dcc714f0a8f3c30aff10e82a1a25c1fb7b9dd9c5c7d8f5b3359f1c5c1a7140056bc2a1eb6f0493fda425896559473acd45817a8d4452cb705516659a9354111afe0e9b052830b57b9e0d7e3b4c8443343c31d714779ee599668e25a8fab27ca2cf6f9a2be6df10824118dd799a89830f31820631847fb28adec3c9260167250a9fc60e452ce924ad920d6a4f093f7713538b123d6da307a9609a2067815ca5eb0eb4fe3e94fe3938461be22ca973860ef856e9c725a4e79b99892fa13970ed17902a59a8da722e6d9b2e15d27572bed868247eb2529b7ed98552c8044d6035376aa92d534faae6dc3d3092798fc2232903421df51bbff827e0b4f78f4a8e1e963d064cdc2da875ffb45280bc4829c9e287199574fcbe648e12377217b25b3028557b9810d6461888285c149be703e936588eef99ed0b6dd2b325829b5615f35858497e6318fabf34f18a9ad4b097bb6ee9bc22657d28cbb914bf66904c11fafe75896e98957d3d2c931c86035743a51e0cc33fc37bc76fccb6ac1a4ba8f31b2c2231e1311f1563b16df6e9e7294a4fa7328eb864fa46ac15e5dce233ff22161b361d66886b6789479dc302bc3ffd9b129e5d398f6a4498182f9a5246493d1d18c51fadcc6dda2ed93811db5c684b9a04f1f688c5e498b62d1e638fd0f42d22dc9d8ad6151061a6750f5c349889e8dd3d26884812eb5859a95d4e3c3e359fba43cf0ae145ead09ad59d8d2b30f21df62ed82517dece5b676338632ac576abacff077a50596358915cecb8d9b8843d4e13a9ba53b5c1523a97553412f127cce469f8cfb2227d3e40a0a7a7e2ebf224e242cf100714607449cde5c0b08cc1035e33e1e16af4b28f275194698530f9c9a1937bea03952a25cad9a605b0457a25ab5483f71037396a2614a79948c2199e26486135934aa1134235088494e671a5a49f44174f647a56e93675c96a71d29719bb42212d4d2749155e05c78cbbdb2cb2527e13f6105ce95ab1cd5610af94daf76e54b266e43e7b597bbb96e16624e51a1741f5276892ba283010c125a6596585738755d1c64308940a65d8541207908000b6bb3006d0e50174d14086a263dce8dbd4469379daac2273a29542dc32577f0cbd919a4dc6ab6cd151fc45f0c82f30afc3bb12ec428636679bec8adbff57edc375f5d8211fa15274ee45066f325253d41577cccbf648c3fb60f7d53e8cc3b70dfbdf2edfdea11bd11b96e217a6bd91621f4aa6678986b948e9ffe7de5557d4ef6b6eb8470c1f0ef43db1b6c955a992d77168bf2d292a512144fa59e1c6ad3b2f81e129270d4bc4ab6705dda736df89654380c2af1a586be23dcf5bdbc8ee81d47a7b4af092046fb9c854e815b412ae625101ac9629b480b7597022ea8903d7454e724e332b0d7099741f10f23fa6ee5296a3962b00b0b1211654942ddbf289198a32c71d8d8c8b4ab4a404b1b5280015bff56dd588fff50416a5f45704b648abffd541644e6cdeb5959b462045ce38c1e94693b9dd2fc60a5420e16c85332ff7234993aeb3ee47a1cc2a95cf416500764f520060b9e91f477310b3c0a3da56b474943bf4d3f98fc68faae5732daf19228140000d48f38948fa21a05c36cbfc5555455eba7ab02ad792fee9e48b3ad3db6c2a2c8d6f0d405310c1f49fcfd22b2ebeef533fc0b0589bc30767fae83a835fa09a281adc072578cda1ce38ed0c1b9193750c33278b202f0236a61c13277b01cdabbf1655ee9e063b14f2282f885a2fd47fee865f266a939df1e75f64868235bb4661d2c6c5f48b7aecd565953cf45bfa737738daae583f1a90b6be50c4369e85b25175cc6bf4dc620471de0823d85ca1c5d1d6c1f5602f79ddd49d1864b1c48498b672ef32720942120942e9adadf06a70eb127eb28a141d78bc9de524ef0cdade3e264912b968bec88cf77a373da423537479af0605325b0d76022e51a26e6dfb988ac96e7454b3bc4eb347ceafc13a46196e616d0131711131da20d2eeacf47f08dca2d130c120395711b488a3290fb7c013b890e61cb18563172482e34923047ec48ce8d07ad79fef15bf3b2069ea00edbe74ae95674b8afcc48dd4516b967be5b372f1ca3e1db2f9917f977301eb6a7e3f9ba255b066bed53c1dc7aa66bb1ee3213e27bdaf8889f24a3753000f7a654c2ec8169f643dabfbe76651bbf211cd4ff82a2cfb4796e21c07a1efae07cb527ccad21a814fc935c9b0f6bb76ee2fd67681803440f05f9ec00af2cefdba9dd0cf108ac6c96833aa531f3e54c1e964e9519081b4931ed73094aac6eb31ab46e27525561813060da18ca9bb066b1bd28246a772c4d659c846903c1dfc88e1d610c508e751cd6fc11d38e99b4db6e740418a98d54660822a60d7b7012d11d6c1f2220a79630edc3cadbcb86024fd7b6ad73900c0e3cfe20a6fd5c363bb0a427ebc969176ee5a38b6b91076e0f2671936b05d5f5ef464e586e6647df90befca0c22f2f0b9d3641cdd6768a11fc399ed1e075d058e874c64d551b6f55c074a18c1cb5f76a32633883d83ab6789d9bdeaabda75fcb221db670d506169b08d8b841ee41936111ccd5c280e5202bed4bff64630205e4e0d3d9dc238c2d76a31347efcb3fcfb569e5051670dc29b8c3b9b013096bcb34c0aa39e039fac5832ded03435122034e64a184cae662315777e005c4abe03984426981bde9c4ce21b8dffbf678dfed7bff3eefd3f7dc88837d5a440190f4e7105996bd562668c7adb9a190717b92884f0302d4b265d2d8b31fd1fa094f4066d6a94a516071d9c023e50429d157ec47cc00a350c584445dd773d4d7808343bd8be1d6a9ab7b9f72185c1db20698f70fb176bb2b52ad427d9ed0fa4d43461c538ed3892b517a1470dc735e7c159244aad567670e6a08f7c809b0c775496b6dd186d36b30a838bc5925bb61bff9a7561840bdced404dd2e335759c6a28007ba23aa157f2100dcfa9281deca0a4dff29080407e1d4fe227c41e5741befc58e28b3a819882bb7d7e484bf3a63e9fc46b5ba5bb3e2170c9b5a3568e411d7af8bdfbdc3e3e320a78b58c25926c8b02c0b8f6ad3964e93e92ad69b38708d345bf9f0c5c0920ee04859ab2008985ff939268949490fd345cd22cf232a697744dd712681d6c6a59d70087cf9c3fb67a984ab4d77dcbcbeca48de7fb5165ab045296aadff891ac65d81ef334e6a939c3ff32d5abb9a93222e692a4149dd0c2e462db44492b8e708e983a12f8ca97a9f6119344c84fdd12d91c0a62387f2ad7b0fa88baa8c756b9b3896a4146198cc2022c7ed5d234cae197ad45e5828b751039bf5d21de1aa0fd64e3d38f6af836b17273eb2826cf8299254e4aaf65a21b8d31bdbc0dd5c0eef07f6edd3cfb879088461887e0d2aef4cc7aa7cf6935b0f5515e8528f7ea7bbc00c6b7fefa8144cf126458f7281e9c48423c6f42f5d3cdfc3193d2a23ea422cf67ae2c3898db75c4f742dcc9f91c8fe0fba0b468f488e59406d2a7d995b3219b03ce6bffe04117ef90b4f473cdbe87b8ac364332ffd44d2ea57b06aac0022f7ba3489470efb8152b40f5e075952a9de4fb944b874ee6b65e89f5a5702cffcc476192b726aa406def7714e10bf4cfb009d7592dd887866dd9ef2ce21a72aba49ebec231e70aa0d5f09b4128173443da5ff407424ae5dda1b2dc70e5de12ac1b76d0433da77531dc4e694905764c16574b916aae06e4f40cbd1d9c24c9435d169ab75d2ea9ef35cd676804e47f938da6b69daed9b38f1b18d3b3ea3ce4e3fd4ce16e0a818a6ac13651dbcafbb63c92d2c3173834415dc88e785b7a11ec7ed1745449134cffab7a2352154f3ccd2abf30072c36f434936f324d5e90265c8f7f08337a42962ceea3c0f6308aed94a7db1ca8f5625264b17e80947bdc9023354bd1696214a76f03e64d7055cb3b65900acb75e16d5f01063469b4eb66e2c4c5539f7f6f7ebe621961974811621c9bafc1f4d884b4b04bccdd8a4ceadff3c91f52dd833650dd927cf0e14e4d22193fa634dab0a494b4806873cf5de994e8fb22f935db2b028e239de01874096753ce3c8c4d66194609aa646f5a901bac95607bf36346102c555354e2e337b4412a5d4ce2fc51a069bd254c32a7fdda7ead57ed57898440d6aeb8003eb5653a91afd7eaa8c5827780d0299abbac7fa01ee0d5bd67775d6dc09e9c9633f93b59df5c84a178a55e5c1a0dc93b5cbc70b09025cd9d6b1e9aae188c59634b15bc49ac7cb6fd90b5f6b1caeadafac17ddffc0b196fef5c50d3b1284df26c1999db97599ad77a75a6c380094b3b2ccf70647a5c3821e4c28b5bb3d75fbd8b53495cfe83e5fa267a1ee13f9e4d64d40281f9f7e109b27bf8e79f65ad16bee810349f2479d035215684877bb42038107b88c08001964ec64b52ca1111a276643c0ef2217e9552d77c6aba34be04a06c2a6c6978303470e1d1f7474fcd8b1c3a30e0e1e7574f0d8a1c3a30ec859c824e76a28dd108cf4c390ecffa972604d596bdccadc0603cdc309f9f3f71fd78889d36a16f81f796d0e409faf176c7392b7c77cf28c159cc7fde02d4139a2073a4b1d1b4b11d3201ae7bb7c3d354700a938e8f8c804d469fef96971f573d8f9b54806af1b7b95bbbf003880db8b64b7ba3921072a9838aab50588b425c37473ec8e6d68f29b7a0c685b70033ed3a30cb6c6d4fc160eb0cd1d6829764e86d5b681edcb850fd1c37302ff96418c126155e51ec37eb6f3a940b23dedb7c167564d55d70615167f6283b5c4b6f36da41570bba7360bdef64b4cde8abaa0d6d3eb44cbde8971303ff09f67fe9f660f4c890733249fdd0f46b34bed505307549df091d7b908edc1a94f418b55053d3add81738a443df08f7904e138b66153e79a17400799048d3ddcd1e019afcce43b2fc1628c57509cce3fedf8504924104b3236f2e0d5d0381056fc2cc6b8f554a27f686877e5ad9434af1836c5817013dfb0c36edb9cefde21dd3ac78620ac66a273410813d3041dfb2475be2859a01ceecaf559d7a08833a20f23a5c7dcc9fe2242045eb06275fd6f6e5ec11a5e2fb7c19e29c6c0c9c30e9fe94a9ba4dec96acf1e87104dc54c8fc8e5d61cf13a7c2c95a4f4900cf0a951d0cab657a0fd2d67d153b2135ce0c018cc496ffcd4559b35e2f78884800055a6ee0efa1c268727db93c32e86d5549341da6212289bb800cd0850a6158a5b71d80712c8947b5419768e87d45b4af4ce690361e2ca029c2112dd3547cd125970c751c1202d1eb2726019b27e8979948800fb0b8280996039c61d6d3782135a0995f6fc19059ca84834acedf7d43bd0932edadcf660a31a4b8789d85568c81222098d0d6e4a4e8a6efafc290000b8cd40ca8e33c76a7c6e25f70433e4836120a2c90f1965adf8bac4c05a0ef95f68ec336097263ad0125b26ef3a2ccf881ce1eea2feb3825157eb0dcc1539c31557dfc2c4bee53b4087b5d9680a2decf713d59abfbe7a3bbc856861ad1b5213eea09a81b19a6ab1c9466b4ed4454de0e135b486d4913262f32c5fdf93ae101fc2fc4ab3e6227c1eaf8684141a2595303003ec6d2c517b7480e6117c3bfd3a2492be2412ed94fe9c1ae334deef1e632057ac017ab2393b8c405a3f9ed6c0fab66578a014ea4b9a3bd6da2a3a3bd3cbeebdded32b13f47733ad5bf448bf922094c9f94e743737d95352c5c8f2ec3bcb48292453579689ba34bfc418ebb6ec4fe348ab75d9af9d7e53620a32d637a7fa0376ce26ba3f2b72e3ce40531d5b8ed4e0464c42ed261f81e4cb26f450f4c52a35317b51ddeb0b81f814fb6179449c0a9ed91873ee9dd41f6ee2b7d326d4b058b59c513ba9127464591b3b61191a67d845e32c051c08d85b0224e96f0398a22fca6fa3579af550f5219bd044cf29d37f863f99f96c759c6a4cd347b220a34a1e9f8a1e14349846096af41ae52c344db2400124584300d0da3198b3ef5f73d6d4751c6aa544722590f2aead95e1b06da56a624e9761e6f05ad769b0c4916935fdcd679b4e0e5ee7bc2b46826bd7a067c74cf2016aa1a52b2898d550f17a5e4333073c09a4b662b598e1a320fe444f15d530508f15a5e6f33584345d1a5ebce2c5296b86cc5d1a6631d028eb16ab71165d2dbe0c727d4b45261534a4aa4db68a63d8f5d4a500a94bc332c8671e535c85fc886c1aec50f48ae69b8aa21e254139d93f83284a528a4205321083d2a561790a4fcd6d52a22520c198ce059e895643b84f40144c11db08c067d448f2c2b4cf8cd097fe77629be2ee734b8715822d0e883ddb171d5bff72e82415f6aae58bad854dfcb5138956e0c4b07e86f741fdfb88a47855a7452a35126195fb83223a7681f8bbfcae2085e1f751591006c1c21fc1807230253ab628732901ce883278acfc23a2f4678ed4771db0eabc93da4f824eb9c9c8454bc34a513271841df63d51b8a0ce060a664da38f68a713a44b6e55192e03d72821293f1954054163094905b8beeb0972ec6d1b05f10f71ea62eb7a5cf4e0608827a404a9214ad41973ad480a16ab2d6e0fb1d806719ddd99fdd8f2f103379f14fa7dce35e810b6587b4dd4cf560a5984660e70ce052b2e995392bd2030c15708828a9a4c8dcace7d66ef4d933dc08c1152d2c3201e9ce19d957f29b795981c54a5d2c34883a803e259cb6068f45cb4c963b27f4b0f0fe4341fcaa8047f439872d93874a1689b6e31966b8bfff182d07ab594327e2a4597e84efac093d268e457c65a57ece89f5289e25e468f64d62a2a62564894f86bec576272918eee50f975363c6f93fed081766d9e37a5f4f3bab6a7e4b50a442f84471b336546fd801383c210a236e3fd040b5563d1ae129b98b8f9a74cde545a3938d318460202e89ebbc17f6c401007c7a50850c82b9ecd221232e4ffe6cd92b7b4840a9a401822af9b93a728389f988940e21470b10d7e3a2d40d4c7d4e0f4d1af44d966f757f3a4ea06606859b21b9589fbed608bf37aaea8b007cb9958c87a38053ccb6c171a9da752c836af5674bd72e194773bb0b05239d0716b2fe49dea0ef0de3492618d18d65aa248cfc5f4100b1f8a0a8a00dc436fe7c24a676a740ddbdb0092c5c9ac8ba0ea3b84196f94b5d6c9cdbafc73200f36c4a4620137f9b99386b3ea6773055d66b127e42943705231ff535e0e3e7950e285a2af601cd56b7f7466d0f58905b0d334a93cf20984a3897ace509ff257c6c5870efecf869f95ed8f73889de3539db9a1cec552ce60aa7f90c7c6319200c6e473560e99d57d35abb05d796926464ddaaceba201b6b12e9c21e6208674b4db4de5c2635bd0860e6362b34540ddccc5d2fc8d0dc9e5d5998b4b293d1092f1536d79f64e47b3f5b5b8c46431b6fa4f33a699f3e8a293dee177a7ca44c5a415b35883855b1b02e9e1486234dfc793e7886775b6d1c35db1b4237b8f23b18587c61c997e0bfa4f183266de02d58235acc884523f06f5ffce13151650970ada9f493b8b212b745e19b2d45da9c1910958fb66e462f3d00ecf699b767f10251e2b702098a71b6b7a00cf8e30a3d4ced30c824d761d2eb758a9337759236fa774c30da95c88194fd51bbcaadb782b77c53a6b519eee6beae266cd3b327c9f0696db5c216065ac081cda9aeee84c524cd762b7b85b9d3bc7872b77070eb8893c8866cce8c8738a7287644b62650b7619db4c06f48d940e30c9b428945965e233310937dd2c6c2019b8d57262503543ac9f9142a25e143c5f219cc56eb70d80bf63b08b3cbf9b79001dc9b7abe9e4f51dfb08da07ec99404daa5a4f70b963b745d96dddcfeff4aa6250d6181eb835c7b88c734de58896cb2f124db0e27d45c62c4ba33a9010b1d2e10979a141aa5cd04cffe1513a7d9411ba00cc3308927485b7bf760cd99fa964afcf0d0244521f21780001c14613d2b45c8cc3a6969e235d510693bfbd972676d3f10d817b9f587c1be8214f96402861d9237a4937d4b33cb5dedf76ebe2c16fd599063f718c1d8f9ef3745671489acf8b24f4a66098cd10e9509a4aec897f1471019466a974d2d85a08f47ced0748af8cbf03569280f5d8727c42caa858189aa71c32b67eb4fd9cf78c4301276623ae83045d6cd1f3c3b1e07b5496b319c90cab20b6a01f22426958bd5d44e14eae3c88b797d872f57a5647d2b98760a7fb55d5613637b6aa16f1415f4b4006737c997cec8b04067e00ea03b4aac50e76a55123f2d55609d8db39b6397c9f5cb8bba9a7001ed6b55c7f3d24c87af8ec46f70a38a8eab70421b52643528c3637af232d612dacbca6b9640f9ae63357ecf9152b2c88d330dc6101ea7be5c898dd0bb703e8bf8bb4304446da411e912b33fbf3a939b7a61ce12474a161201102ed5c0304a3b15f64217fdf66dfbc0b0513702658e83f0f17672ca5455756745776c99d3177169cbfe6a107ea12850573da982a2b5da50acac2a1429c659f07b11cb76b62dc1e92cb2849fe0e261f7acb8e25aad23bccd32c7b869870a021871498504fa428cce4151ab2b851a61866a9e36abe82c092bce38e0be9bd9a4485225c510792c96262b6ba84594d5be3c4b8a24c60b1f8b9a62a08e9135a18c1b4a56ae01b31813841010360349040ee060db388d5f45cbeac32437c4c3b19257a2826d7121ffedda13697ea577e73bb8ef0ab98f286d4add176347422b057dbd9fb4db3e460131fec163711949f091679d94ec0854d78c8adb90e33150184a35043f73357f16d636503e81b8c9aab2b3db09f29e41f3f081f6268f8dc2f60b5ce457840f5d10f627fe06a163283ab59e4421c2b6c5f5e4566ab828b952aac2b1579ac6274ee822d9643440e9586a22ca1f8bc76102556b05311f628450449406777fd89982217041d44abac287a627d71eb44a97144215426b8cf7f59c8d1ea842d8722f21a4ee16d371eef36d2aa0ecf6c1e47d2517ad9fd8fa3a658e6ceb76d5a33a4b73c4ab8c6c91482b42101faf9d984036c5570e291e42fd0353a75ed3c1a44edc45c788d993e4fe747d51ff2f858bc6c610bf275e63774295f208019ded05182c570f6ba115b0cf011b93dee452fa96005e3d57135d72efcdc021264c19a53008b3887bacbce2c49cea39d70ca6a08dfb8bf48589d6bca93a5b12bb4b9eaeba8ea3b05aa45578569a6bccd6f66ba7455de61c69962faf947dd69273f9f1c69b14ffa6c93269b7d21190415d161fa2047c15072a16929e3535d6afeccc9ee6d773e95afb0d79da1220614abb0675df431ac72bb4cf37b5eac2c779c0646cc3b8957bca10185ddab63180a8798b64f3b817e2ff2df92ddd8cd9a7bb0506901ddfa887668c5191578d675256f73395991225126b651fd587f99c061e41dc2b7231377c37f076a8c0374102a1b78d4d882607d1ed45f8d4509fc3602004b724dc273b47c03d71c6ac9b8de203db4890e822238e372847fbec42c7f149e9ddb0e5bbaa76e92f1cc2318636b2a9c2da69c00bfc0ab2cdbc8c2849f59f03e6104c3f7b80ca6395039d652b840eb69d2af613268d91252085596393d122dda7b3ce167401f8380ed18aff8da6aeb0c748b5ee75a1bda1e14104791347d0abef308ea850ed1ef5b1b2190c1f05d61acb62421f43b040dd3770fa569acb472034a82967f5c40941da70d0e7da867cd8e79cca79c348fe1448b2d069ba0008b1fe8fc5ac0f5b381b323d8478de064b8c37d8598477110c91c446dbbe726179605d6aab2022eea1c4e35b1d6ceda2d3639d3f283fe9b99d94d24721389eceeeede01370823089f073487e6bc9eb335a5e0d9ba66ebce969dad3a5b06c80638c001103004012e12c07a9180172f64842fe84f7c61baf107f57f3aed1de3c79cebb9cb6d593fa7c7c72007c1e4a8909873a914132323f3c3511b7f240c9a24a6e22f51d2044888c6d6954bd3d09d6ef4a709ddab4d065af8c5788374f835e20b9ac783e4075a1774f834aa6ea625c3c2850c3933d8950a90cee5528c0c0b17b92587584137737ec01595c14bc103aa31e891d2ea8fca8a5351b1e9dc9e52c59d62a5c31fab33c506e67b3ae55c3f12de57da44c88025e2747ad414156c2076141f4851440a2152fc90e226ca14517c446945018af213a58b1284283ea0484841c66006077cc41286c832c413569d068ad091810c6c6670031d4ea7474521a28909a0184187214bf41cd18a22074620c2942b186912854da784472f0786608207258c50c509ddcb0ca8f460052e10f20108c94d72bd2a3c47e8f6e70c0eb7852397b4d6566b6d10d65ef32ffe6230ac5a5b6bc6e3b2b108fc17dee27603dc3123c4d27a39c717718b1140912d1f15644b89653b589dcbf17e3a64b2bdcf5c003adb7e7d39a7d470521a55d7dcc7146e54d7c87f0ff5eec3877bdbafc59d41e5f3d7e23eae796a09dcf0e0a169c0d7423e7c2dee0c7c61185f1746c38c4cc99fc1ae0b6a1ab29ec09329081fc36aeedfd7e272aa7e1f630d2f1ae0639de3d67a0257a6ee475d0361bcd905ff7a8cf1f597a631b16b31ec311d1d63d8c38e6d9c8e7e6df7b5b01b8ef9f32f9fefbd7865757bf4318d9c9bc4a8809f0c175b5da282ec1e28aef4d703058f01faeb8162d57b2210bca3d78ddbb1e247fce545fcf49bbbbcc65b3ee3a6d7bc645bfa7cfd1cece9f2358f41e614000f764e0c72b70864a84b2e02812912bc18b8546fa137ce42a07212898ffe858f465cf42f5cf4272e12f1fdfb5df8fe16ae6daefd0bd7fec4b577e1dab7704d7b4cf30a9efd0bcffec4b377e1d9b7f0ec4d3ccb1ed7bc82eb7fe1fa4f5cbf0bd7dfc2f59bb8fe12d73f39b637cb7ffd0a1eff85c73ff1f82e3c7e0b8f6fe2f14b3c9278fc118f2fe2f1378faff1f8198faff98687b8abb37a3cc5179493b1e1b588dddaaf5fb7d7630c42790492f35a482034326607c02c9db3300ea89dbbdb7300c551b75b04ccbf0eb3cd2db6ee14f04305d9f0317d4ae964a192ec5a6badb5d65a6badb556fa04c6a820fb955041a20e1251188912248a28ae441145141f9789095080de2823adb51ab153b0af9ef844c783adbbd6255bc8c601c8095b89dd832d53c5be74ca080548e7503f4e600ef688511953f7b5e0a81f36fc1db4c7b8edd782f38e838afa9c74f6de120200f4baa2d753af2ebd65011d5f177ed1e9764d9d9b7326e20ea1ae0142d95fe858c618a1c419fcad3f4d23da9b09d0af00fa95e937f200747ce5c8b78eb11cf9f3e9184b29e3c338938f44b79a06e61ddf1cef7db65e74de1e77cc58abb8f6b2f6dafb89101ef7c950eb5efb640538d7dd68224ac6131c20ee131c76b055702a141f6cadb5d62b586b6dbd4e96586b6de54e43d84e88b841e5309fc40093f264054f709ee0e0a48a13284eaee06489139e798b38b901bc9c10d1e1632f93c40ab3db29a262ae4d093e5c4e743e5c9ddd821e55082a9df0b1c3bcf7de5b7bc4a85badf114515dad486c177a6cee843921c5c90f4e6e3aa7c22c7808a305a6e273282cfffb990dc2d8d75f8c6bbc2ea573523a679d3493dfe3c6c08ff11b9c9918d8cfe48bf94d8f9918796ff92f543411c2003f73157ea1c23a8639628927a619db2be516ffda12830f67f3e3c9d431bfc9417dfc2d104685a9b7f231e62f06ae10c68329aec214e6d8f750c5c0af5f4688ba1f5f755dfad210c0e9397156b7d356f9c4a37e71fe5a60fe17e657a676aed7b535b8cbedbe16bc06f7893bd54491e3783f9b08fcc570ffbd6a2642055cff70a9677e63ffba9f318731eec52dc53cc690a59b1e1fc3f410f0341f75cb9f3f18e2571e80878aaf3387f1529101f7de7925dfddf2f95abc1830bf62dc183606be19f31a035f19731a036319f31903e38cb98c81b3cd5856455df32b535b7319036359f3180363f8229ed9d44a3115a36768199f9e59d635c731f249eb0c6b78c317f7cce663be652adedcab86d98bd46fbc3350125f0a52caf85290194096240690a505107490d849402544278014e1840c6a0ba8104ef870a2475015579094fe7a829a042511f4b3b11954a4c39f49e0010c3d61e2c9139efcf4d7f3a47b3284273e78c27a62832742a840c1e95c7e62e889a026fdf53c91a467c627ba278cf4d7f30491cef1fe9e20e28920b3e7091fb9bf9e276c2eed116a01c12af42397908fb04eb0a811ee6646ccdca04e88487f3d4241fa13fa21c483ca0912219b0e3fcf1ae001484101fc0a1cc1086bed1164708bb044cce090841ed6367144ca0a360f7b2341060bed41a7a78921b909204df8904c5ce95cce9309a1cee54c6f0e3cfba7634287891decb0f7e999a8020f1157b07131050f5700f2039ccdbd0e08bb87dd84c80d145408eaf03354d2b9dbd3e4486fa2d3b9d984959b10e1018fcded28ec17ec9cbdc40f8fbd4fdf444814f04928e5841d6e2b5802099c258a007197c8c10e76891ae0d4255ce0035d02053ccc2570789041d8dc89091437354e8f5ac2a773aa1f3cc445026645a2cd81bc3598d805140575091c3b24c5bd9840d1e107713101ea21677250b960a20427e652dea51f5bc564083ac8e430af995c8a61c244e764980041264a60708449c764085307263ae820a512567890c94109272b991c9468d2432687c9c40826423aa7fab136413f8284f4b7e988f09f0b2ff41bfbb83e9e2fc6964c3eac09800fb71c5190457fa82e75cd8a2ee78c2f52fdcde7808cb20818b02b16814989e1d333bc23badc31156501fac5b10c43af5bdefc1453eb43082194158818f22dc1a21d6c265b088d279b921131278f81524a0971806928689c1c04ec04bf80520e23a594d0ca8521b9b05c465c2bd4ba024a2965ecb977658fa851ace52f502691406ca541927b1f6ca2521ed7e32e239b2ea56c61bd163c6cae85e562d3e58f6cdee5a70520fc64b8979f1e3f4339a9a4d5766f567b2f5a5bd4de0baba4959d93d4d90bc359d7b47408217cf04d086bbdaebf6c707a7c7ce3a3c7cf38fac768155171dae89a964e593a458ce8b0783a564445ac6b5af42868d46424340a8223206e2465d41a01c115901f4180ac84e4ace0cfb8ba233c488e74939484f443ea48432429a42ba42152ebc686d482314a59f135e17efdf58bbf79b3bd5cb2ee51be254a00bd18300e240afa7bb97a3160ef63d703a368c27b98f362b0ef25f062a8ff7e70da810b1317215a784c534c4494a0f056cee75e6ce076da418f5f5d98f4f854ba08d1e35f2d98698a3411d1e3c7aaba2528f4f873cab971f187c562b1582c1b1b1b1b1b9b2d1a8d48a492c9c65caf4eca42df418f9bc91642e3c9a66444c4a738e68876b0996c217afcaaf1c46c4a8f0f332268cc19c2d2e98e745dd775dd8ac562b158ac6e6a792ef9d5d7e1eb138382c6c941c04ef00b20d6d250e8f1a5c6e9f1630e02ec143bc12f680d459c5504d2b174464023a011d00868043402ca301796cb082b402b3dfe053b0c498fbf2f2c3dbe7619d1e3c36b85eec88f12a0a0aeebbaae5bad56abd58a85953da246893d17ae32fb9c3da2c77fb15b8dd2235c412030070e61e9745dd7751d005e3a0d7a4c02ed03aa34a83449d06deda193cda3482d0963e672a416a9c5915aa4568fa552a9542a954aa5cb4336513955a73c240c95cce95847744a5d7f4a8e7497fe62f3831effbdd8bc16382f362f3f162022cbf60128065530c2922152a8c26eb293d8568cb022858a1fa8b8992c0814e54ac66eb682a03027882036f77e8208620a2b4c5ad1850c8f07090b5aa00e9f664686850b192490952aa0543132568e58292287583273a4d0a232b0a224c7ca112b3c3f568a58e1811523ac11308a09d5d62a63ada86085552b3df870d52d8b0fd7891e75097a97ec304f5f6b95246aad35d62a4c389d1e65a506476c803d0410f6eb5e9373ef47da802a55979a86193a43d77bd1c898aea181d5ed3d94a5a1c6e27c71a7095c33301de1bb99bdead38828b9ebb6b19c75d635d7c62570c38307ce72d63466d84b84618c31d6b49daf199686ac6b6cd77ac6484f406730cbb22cdbbae6befd7d4539db39c3d2a0e91adbed6352defa224d4ed788a0482412895ebc225d63ffb29b0d7094354d03f7b7358df89ce85a116c51e957986f301b37aa288434db37739fc518c6d9f21afb18d6ba66e62cb3f7b1d7b48dbb61363466ad66b1b53b3aceb2c62ed6b4ae81dc853dd6a2a6699a065fa66bf463f75e36748dc63086bdc52eb616db70cded36c02c7673b063fd980d8d695d53f7ab41b4335d4345f32a6198c90449259dffaa91589bf6ceb0c759cbe9d4b2b7a66b36dc7beffde2cdb0a789bae67acca4f36bad35766d3470c7362cd23551943551c6788bae61941f8f3286bd0b865d74b6e5c725582a954aa5174dba06fb9cbf45eb17692c5ff9adde68e09e47ba066253b6bc067b8ca44bd796f48661e6ef18c18089c9924072d55fec72de4f24c211ec639c05c708a58c394f4cb58d6b397e7a06be3e4e5d03b517d8fbb2183f4d9458d6dc1fbf3fd23e7b1ea2df6f350c0dda67bf6b106d4fa644f56a1b10565db3ed65b509689f3da66bd0b62753d9cce535da46be2e5d932fa66b4cda0c797baf05de6a4abce675fc99aef13ade2e8b3a625f7f1f6b1ca17e12cbf45b1b60961932ec78e374746c83b181751dbdbe9c6f39cc42e7664362111b614505567aa892a5ca1455a0a8e2449525aa2ca99244152654e1e9afa74a912a43aacc40a84a0b5045180a1a3a32c4d35fcf5091a19ca120fdf50cf918ead1a7109ae2a783952589d8295653f4c05d2a429dcb41fdf5505142a5099d4a11d4109c1852326483a19c29ae4c013445478429727ea0724508547858a04ea51d2353639f9eca902a43439d43fd4881e5440cc8911703fc4d81c4eeb58826b9e71681503f983c3047ec225c0005203c58c1ce8f246c296c21f6142b5462107444147a8c00bac18e4f0a499418f0a0040f780881406670b877269762a620e92d2c59c20f8c39b125bb0957f401a182362a68a382f6a342d806c629279d94c628e7a474d6a129859b57e610bda136d89cb4da2924e7ba8ef0203972716e26f9b1f6ce0ba31363fc18736e02993f824c20dc14327330ce5367746a414d8482a4b4565dab6f3d634ed14d44451f3530ea1ac8c91f3d7e49ae4cd36492119af2bffc38c31c753af658e63cb1974038ce7a61ad8d4a2d2f30bc249aa8d84448fe534522aa584465441579dec79cf802c3b87125de441f3ffa030ac21361c0d50a2259c1242bb864d5040af5f7b0833f220cd8c12050089122fd19e9ef371061c0964cc5bf425bcae9ca4d7f0f5737e28bfa4b3ec909c97e3c2c5ee614b35d44183107c3ab9c73cef989b8dd4fff504e167ed1d6322df165ea78baaeeb4aab1fabd56a458a2f6a130d248130d0057431168bc5a2363637363636362c5ab80104d4ad72ce396795aad4552a15964097966959ba2426753c9dec70d78d56125f57c462c9222c9664499664b510800ca9c3d56ab55a7518638cf10dee6ea04baeae96e92c45f7da1d4fd7755787573f56abd56a2581641309248124900492400578215561d7755d775dd7755dabd56ab5bab1b33a4bacd97b753c5dd775ddeac76ab55aad6068015bad39e79c1376dced70d7d9567d9d25beb27a61cec43a9eaeebba4e9561c8a822dbe6989393b38160ce39e79cb0d56aedde92c2c156154273021873767b17fbcffe73f91964ffb93c8d74913f2de7647781efbd8bcb5b2b7fbafcdce47cefed5b5b5ddedad3cc4c6df9fad265e3a0c9daef16656d7679ec5970f91897877171f917973ff1962dc74c376ddc8e0eefee769fba3d3dc6ec35a194724bbc8448fb4fcaadee38817c388364afd05ed892564a7925e6f2b2e54ba3df9fc5f02761f87de14fde1b4d1b77491bb7e529ca8fa82e519153f58dcb31232f39a14c28138a295a1bec20b4034f9c57d61d8af401f4e7b3838f2b1d01fdf9f438d2f1c3f3c9279f7cf2ce2be943fa66ecee8cfb4fbffb77e3f4cb7ff9afdf51f4f6476fbfb4713adebea66d1c34d2b5ad6ddcc5566f3a66b7cfd59779cb91bbc51ed71a6e879c7f3d9783bec401dfda806fe14fb995b458794cb1153b1229c622305e9e833a39eec0cf7ec61731fe7d3d2366773fe418c3e26357fb1b7abca418e167f7331e9dc59d58fa98695f8a3cc64d478cd88643bff6d8bdf672368b39c6a80315101f8b3cf6e62c5aecede3e774448e8b5db7ff39e6fbf7deec332ec7cbc7871a02da76a37e0c6e30dadbc7628c51e7665967b1c7a6871b0e3843dcc1e2cd6132c1bd372ec7ddb0b883c596a6336b37c88aa8b8d92ff1fd2fbce52577f9c94f4fe2793f4d14bd08c2b8331f6ef873e49e618f6139722612dd1c39ff89bb704ec7dcbc85cf1c39eec49db81377762429b9f1d1bf3f9f1c3a1dae74557f3e3a20e93e39f8e020a4d573a71faf4f0f1d9f1e363e3c7e7c780cf1e101d471d777922a8434325e4f35bcae3a2f1d71e366ab5f2fb3e1a0b5d65a6bb5119fbb6810a4412e80582891f9e933c298d9b44cd7705a21cf30ce3f00bcfecc3f005878987f2bfec5e54d2f358dab67ffbe859815a716ba698c3e26441f9b5770d8bfa1d300c0d7f13537a5740e6201d870e8b7320f0340f5dbf8d4851964362e76fb2e70b3c5b970798e850c17d369a9d319659e6e18e7e05bb8c1b8f0d7d78f1b4d844304f05943a55ef4c28f08f01ba53304c517953ec631ca3feb19ea6f4f808d53bd909afb51031000fceb6bbc281669dcb810ecc01b8b4e7f445fb435ba5d9f7f064d28399c431758d753fa57d7f0c768a40ccf3e005c3fe7af6f9c73ce39e7af074514fd99df0030d3c2c6c2a9cf978d7b978d4399364e557a4a6189f419697e9fa739efddfb74fa47a15417e67a6ca6939e44fa80840187c8d4fc39da38154ab471a8df1bf7a79d376e4f289abf70b373cc74fdf4e91afd382cd533e4ae1f0e9130a612f8d3a78c4eee70e372cce8096506e119180e814486c0211b44c1182718a7a2908a820f0e3f7a01faf3b169f291bbbbca5aebbd353aad99d1a3bff5b19c6d4a5fb437fdeff4a2b61a9d3e8d3b56335a9c588650507ab204f9d824c1e9c962e4c5f4a4e76e3ffda9f42bbe050687f6f8b5cf8f352efacc2ff85916223d7b9720597c54b1eab20a215d4a39fa4bf4d76b35304be6dcccbd7b9f4eff2814a7ea7983116dedafd7fefaec479cdbffbe01a517693cfba723007be96962b665d1e3cf78d510c87f6999966999966999f69caa67da06b31f6f1c4c0dccd235fcd7c6c1ec8d06ccd2b58dabd1afe76ad4e8d773fb6bf654d780bdf49873da8bb6176eb6b6cd50fa6cc3517ad186c365f31c339cb6bd883a7d76f9f7309c9bbde5a57600b4e1b2cd883a6dbfa2a5256793c9943f7b9c99b4cf707eed33537e0d679fb3979797972ccbb22c4341d11e76ed737ef8396b3e363c3e36407aaab8b2376e47e65c8d9eaf4cc9cf2b5a4a2316fe4a182d7ccc8a534be99f54f51cb3c16cdcdd3dfb978ddba78d3bf5ec5d36ee94bd69e3beb471a89e3d49b471aa2cd39f39577f7311c0aefdd43662d71e6a0740edefc6e1d77806758dc663d7360e766dbfccbf376ea6e7a7915aa641433f37d3673adeee7e33356660e236c2c5e39180f7f0be1d64d09843a6d344148432e4b06764f04829a594503ef7786a3ca067f3708fe7069b7b3c3ed8dc7621079b7b3c58966091d26f7f3d5884961840139b9bfdc919213721949dce2f017d518f334e6a821bf7d2e59670c2b9d180fd42de616c70546e9c0bfd71b05b0b6f2f62c61720b001669918f51e4fdb2fdce363ccc404405a710ea4ac177f312ecf7c83a181a3ec72ebb2c7fe3acd98bf6ef2dc2fbfc1308c09905b0ea4084edd3e0874dcfe1e0c3a4e1dbe17f6e7ecf2512fc6c5a98deff37f5efdbab74b2a379aa78cd7567855dcbfc62a05dca491686b99ce18bb202f82ad744a58fa01ee00df60a4773deaf437a39c3f6917258536d65a6bada5d6da3869b5b3da7bdd0bc3139b386b2ccfa927076f26cf6eb2241007c99c70d5e34b12735e922844490c12893c2099968c1494d0953e5f47a783870446907c40468f51c68f3e5fd6803ee65309d0c01fb1c90e204b9ed08e1c6ec7aac78751ca39298c52ce49699db4566befbdecbd2e0cc3386335575d334d574dcb75ef5c45a25c4773724e354d1c8b963c5da8ad9cbba149af9c4472d1e797822ae758f4f92653ae9c93a1cf6fb9a1d5637dd8abb5dc0d405c0ef93492ab9078267802188b112c3afdf5601982050610cb0f2c37fdcad0945782ae2c9957905c3132e9152243e69520a57ac5c7951edca562e864a908a2a2497f3d5424a142095474fb52c1a2a20849058c9ac2dfe981edf2e494524a29a512a614948c409242aac12889d10ed3a4842905252390a4906ad0e5c351125d3e1cedd0e54329390b504a197f39b73bf77986fcfab63d22a583940e606cc4975f13b5f8f85a6b6bad94d239a79432c668447cf1099cfa6163b172df89d8f5ed033d808646c2b81153f2bb1b2f490df145842183a6befce73bca215651b551dc8e34f5c3e666ba4c15b1b977a40212c6fe217992115fccc09b9987f18098922f6bf460d3ed49b9575330bd9ebd36e76e545965d541458f453232918c4c242313c9c8443232918c4c242313891ef6ecb1d1939e54a38b7e74a38bbe728ea68b361ab961cfe4bf165e1faba692a8d78bf31951d6a5cb9f5c0795a25879e0f8385e6b6ba5744e29896c08e50e09a30f7c30b5bc7e89f9d3bbacc81e5fbcf419cf316f9c0e1af50cda5f1ba7835e5996e1fcd9e37ba1b5b15649e99c934a5963c4f9caf7b5b87e6ff7b5c8d78f3887ff7d03626238edafed859b7d5dbf02c5c2850ac5c24597a5d7fee2dc0d0864dced17beb0a67db60166b9d1ef85d6c65a25a5734e2a658d314564e7df40d7b0e8f7630e7b44917e6f2fa246a2df22d2b6471b77a36b9fa5e32be6b5d20c317f6d38627e6f3856fcf598eba09cb6e9a03d265be1b2629b11f5b23d7571696979fd34b145b768ad758bd6da6432f9f0377a2e91467f491bb70130128958d041ff533decedeef73780ba8eb92292900481038492540f288452384b8038f2c3159f1f58dbe7879c95cf0f36393e3e80ecd74200bb28d563b4b7f6fbf75f38b42dd56373356af4fd6fb4477ba77080d001c2878f8fd68c548f548f540f2d0bb6f17b5e1100a06b3034d4a75f43dd6a484d4d83d637658434e3766c2dd6c8786fb456d63a29a574ce2a25d535d2c6d86bca880dfb8d3b64a43bec3d1a8d46db7e1c5f4229eaa29a39ef88d7bc3e7aaa6bbc3e9a5f754decf7f1cca396273d0fd397be6a181a5a9ef435b46c4fa6485f8369137de9eff5afdf1f71d8457f1fc3d1e37ba1b5b15649e99c934a5963ec7424fa4bdb5e44895e7bfb3471c46187b78b36d7ff748dd4648e99ae69d07e1f731a928695a22eff051d86e57c2fb436d62a299d7352296b8c7d669e22821fe6ebc24faf5ced5f4f235fcf91bbadf7b590bfe382d2c9bd7b9f4eaf52c9305ba49717d3a251ceb07cb13b338526940a74e08a36ee06a1fe05659950b2c81def11f093c19104b18b281bbb88b213bf9c8fe5fa62ceadd6d979dd381875fadc68549ce72faa8a1d9f2b9de20e4400c4906faf520e69155bd70b548120e7bbef7ad853c9f03aa9d9ed662956a4ed561adadc03da2829360eeda9e8b5a78937e20bbd472f82223ed2ffb4c83ed4569370aba10218a821bea019061eb552a89d52a6784018882f38889262c31b1be8408e0fe820417c914d2810066dd3e477e0ca8d0d084d28da14d2d90cca28a7b70965e624ede78d5361273b7382d80d96e94fa6a41603ecd7945360bea82a9b53a1a26c1cd7d3cea97afeebef8cd4f7ef3f9d59178db49b0ce62816322816f1457dd44ea1164565d7879d53b188a8281baa248d8cb80361807fd364463e9f9b255dbe0b19e20ec576a3989671d7876047b7a5b4f51282523a5b10ba40b1789953cc2e411c99aa6ff3db6d524a21a594525ae9d35e29acb5ce09a5d2c426a921b64dd160db44548543a921b6ddf256712e0cc29130607742a96ade222ba2ea77b2c22199aa5faf4b6edcd3cd6ef7ee7d3afda350a99d5d9f8343bd72b148afdb0b375be6ec1c08218494e28bc6a967502aa59e412184f8aa15524a131f920fe46c9ba2c1e660912e7f3e8dfc402a67db6d421b45ba9a64739075c11f30e7c92f02e3ed033225ffde5d21abcb4f19d9f22fdf5eb8d9f046cc8361ee76ba9d6ea7dbe976206b3e86cdd0ea5a39c355d71d08216ba76bb1e20e8d3a2f76718752c8a27147731731253f731aedc9449171e7a584524a29e38eb438c5b3ab0868c7511fc346803715cc29249b53a594b065c6af4eafebcaf88c298bb27993aaf802671fef49dbc70f39ea7bc5e85b5e1fd2c0b02e59a064ece28e2e02314a8a8da33eedf07db8e92ffdf9f410d471cc759b50e03fbba1a0d8901b019621c5b3e7d34829e5cfa079efdea7d33f0a35c3cc4fc2b87f5954b7f0b52aa3f063a76b4f2894cea02934832694d9a272064da1d98202619841f36ba47c367cee1d99506c68c43cc8a24e4633fa59cd322ccbb2a8836559962bfd9c5fdbaebe32c3f7b5be1f2f1677e24edc615d754429a5238ee3860ea985a391ad17f6d88e138c4623ece23bdaeec53edfcf581e8d9e26eafc18ff1de59b3129da99bea3fc388ae28e5acca28e19fb1bb1eb6e5c7f3251042d156cd25a6b6ac2b4a66f32ddd776b6666b42796d4d26d34f2193b6d6647a6bf56b5a3399346bd27ee3f4af2841280ac10e6ea697461b77e27e6fd7ea8ced8432a1dc234a7c7a20e2d343101f1e703ad69e4cfe4b428ca9458283c3befe0ddd62183f84f4bea5f557bc70835db46bfb186b8fb1488445222c1261912822f98731d6f06b5624ca44af23c66e526dae1061ed491a765d2adab81060fc131ae978d3f0d3cfb8ced7566ddc8130d42defb8238b37076b5f4a3b31aeb83ec616bff71ebfc7f8a3ce93cfda9f3c18e3d8598c71ce183f97d9cbfe9530486f37aef43adb76d44fe2b76bfd26ad7d66f5efb77b97445bdbeea6f98ba88b34c2361db3ef94f22502ce908bcb4b595d7082703a1c563f407f3e3b5c996fdf9b41cf4e5baf0bb741f6773fa36fa854cd374631646a4600000000d316000028140a07c462491025519c16dd0114000f6e8c4c5e523c14ca834990a3400c42c630840c20040888888cccd046088626532e739645693f3d45ec3175f561dd32dec92c20b81a86fad1ad0bb4acda3585dd40b23e8ecdc49a88dd12fe656b8a2dbe2edbb2a4dedf200bb12c28840d1080e55d3e0c79ce045b1a5ee639c1a60690cb9f60d321ca4d146c7b48ab530aa2ede407a8751fded31e37daa2de87557e6187f1e64f3ac55a2602021fa0dc744fafa03d4daa7579a057f0602f4461c6a7ce09d33bbfe076c82ae314bf97cae95de17125892c2092badbc1ae410f45e34a7d7ef973503b470c8f759d5aecfc78ae1b743271adfdb79e8b6750d483009d5f433f02e6787503fff541756c562a71dc0f414e5afd9d520f7e0f2f3d80454243284935996a1fcda2509a671c1e9bca1813ab9327554b5186d0eaf60c89897688cc73ae8577307eb6ca755e4d481e01ad3ca66a90f11fb1b961c4b3ef3eef8b96dfa9a5036fbd5a7255eb17d988793ddc190fe56e4d68b7b10dd6a4ca1b06ec95557666199c770c0bf06741346e2daee31e1cc8a4ab083f1d3975d9fe2965f1638653e6087e2da6fb29365e22d50a7e9d561431dfbc0133942fe2e8b5ea26c6cacf994f3fc3472465e691d82eb3e9827c217ffaa36700cdd17ebfd09d011296a71d3fb7b9a714bd7f332b574f9cf3e272982ef7041a9688b7ec10bc075f25d166f97b624da8d9a04966d2b3c1264698433437d8954668a609e87d55f188ace997f8e9bc312524eb0d6b8a339d06bcb59caa0588e2915c7124dbaabff556c477bb6d61f259b0d9b4a20acc289b9025e1174e232ac05080c3adbd7e646cf2f3b7f3d9e6d9473e75f36effa8cefa34c7e0edbcefd57c530a477238b029596cfd33c699d164de815a7e0d7b8c68a08bbcf787ee5440ac0fd6cc8a68f43b9688b36060917d955599beae9ee1418df8405e58ca61bc9df2d703a5a8e0b5c3861cf9f7ef3868d3f5f7cb3f98aab4f2bcdcb3895f47536e526f119425f06861b34e82de93f382fbd93bec8d196a3e1cacf2c96694cf0bcb5d036ce15a039ddf0b425423b57808af361abd79c7827d8fbaea2abf3b622b21c41fbb8117c40d23a8778b01f70b2268312de1075fc1228203600fe119160134b9899b48f3cd567de088eb8b6e6b435fe12b6ad615352d8d8744d0d1543eff3f4ee8fb3bfb13080f9963febd54ddc2e2ff98843a67c4372591b84b8ce701f013cb80e49cbc0f8288937df241f30de9d2598b4d83901f23904217751c36b27b8d23704f61fc8ca4516dc85f00798de995256246f4b8a79a0e075e24eb34d6818ee737efd65e5df3e2fb003f6320d8518928f322e889024b92bd00251c9c961cefcc90df4574a8212cde1841d3b14932f1d6a40807fc05609476219b2ad06791926b5a66057dca5d43a2d1852ebf4df0c0a5e6f182d206749d13e55ea516a60b9db6240f71a599c8a143f485f174e1b830a7284c6d349ed2a308ccf9bdbc6974a14f53c1094bb66d375dc4e919cca9e75046173a0e2519bbed66e6feab3b696fb69cc39953047586ab950c77eaea05cdab86f54606b468bf29a834441231ed51248ab291b4cd054a3f4850bb96a6462aef61157f4efa41938eb481a118725a659fa82b8eb78934dc76045b131be78c17dfd1fb4287a0217d7ff26f8cad62f0fb02f88940e7417f24006b898864a4ec7fb41aa32e8bac42bcd4a9f343d1027effe85bba2765fc9e6e0b1c283466dddf513a2ef297d6e3750cda8a7aa4787818dbebf8fcb40706be643f63dfc855b422f0999f78e033b77bc1e740eaf9e0f3b57d4f46b32a463137c8ed591b3b20c084b6026aa9ed6ead03e9fd5484bdd84d82219c00a93de454bd968e8cdb0af21886a2a7cb9dcf3a581d7412bc16a38e31e4e59787e223564b20e557533afe2a89925b391d3c53ea9bf8d55c953e80760f15f6a4959c44b6f983d16e38f695d52282c6b9ca97b1188706f8a5964451b25829148b6ff47c35379fd7d0d581ae41b59b8ff23f7586fd18f7319dc63af64cac7cbecef3edf3f2f414e0ffa32d0ff5b58956da5841be467448c4f4c3b8e1e1e2b02ed9bd0670b0095fdf59978640e2eab9062cce5149f06bb5cbf56b1014d3809889807ad73faa83d73d3682af4ef5e4f781a78a87f751078c9dc6450c6cd04f96f691787ff0338950ef5d8680de36766022002763af2377653da1a8c3fc6d7a040028e8a67f5b3d0bd1f459d8cb34379b68ff979a7e6d21c93e982c24cf9a14d374979cf39d8235d9fcf22cc761c177c745b89fca412b127efd2d9e0806e732809bd1c73026f8dc4b7d20ed46f7ee049a46bad20c32d141a8c7e644124e2d3080cd5abeda3d530e5a635b9818362d9d324097c42b91731a3673981584a29aff601d08b4bbe8c224ecca23422fe0c3016512655ecb94d6e0a8953fb8ff0749f82ac37534971cb062fdc0ae32096663d767ee387eff125a6579d3275d07009fd993b785358926cc190f5c7574bab36195f84e7ea6b18421b8889822ed40ed7253d7eda1ae049b5b47f37acaa2db4f907e4a35ef886c7b043504a56195ed97905cb1253f96214ea8524a6b194ff49fb1380dbe3876406c203f2044a9ec2c9f3b3b007a69c7776aca285abb3cd53ee50888c0f92121f4733228a4e46c885be4095f8cec6dfd1ac023d848faf43d5cf6868447cf3ee147abdd3e023e947db49c6164f3862e471fe75c20a6cf79c94721b4482f683b294611445d009bde5e188cf341c146a933bc67d240ed3c5b9bf5bf8df23cab6cb9a66f0131fa4056875c246b18663576107d55c53fc463a8907d6d86bc9fd31d5e97d7f5ee56c85f86a610b89a50e1d871d2e42294eda86bf64d50fc5915060747b0b4642d61a44ae30d2897a657f7447c901e264768baa6d975ff7b4fd1c1f007921019255cada98968263c03d4e2ea3561da1f8906e735be0e4cc7dc08a5818fc5ee2474d0119964c3d20b4efe8338b1635ba375f9e64c856461e19cd438adadde6cde3e6fb1cda5cf969a019a63376811a5f69b20055c7a49b1a7e4fc836666bdbde2481a53f6689f1179a4b4aff6d8387bc4b3c40a85c8ba766632c087351b587d3deff28c86c2b1d0673cfd676392007b2bee97d60a5bd7752f76f3bd2f9e407686e4c7bb7abc7ca41aee76683a557581e2620536cb775cee25753bbf8b2845c7540f91c3e37e61bb700ec21184780db4268a10718cb3f414910899f4fa7889a4365089d8fc0073b5c11f86fbade6a166d636d1978e80dc19088f83900d3ed4c38e62d3a40ba531f8d3e2ee23cc807491fd16de23701806b5266fbea77110845e179c05570a87d86fe8d04b93f419fbfecb6ff1831f1296822aec3977d83ff3ddc148726be67dd93334f5c49966b52aca66d6b9b04abb80369d4f07ccc83b3bf60c51866f564608999d7cdf728c11d44dde3564d7211d002f52528864c6c0eaffa88d3106128b1b743045318531dd178a7dbf017790699366424c5e99d5000cf9c4c81b927bd047760a4b5ab4a86e55f7ecee3d0b41d8e155676a36f60e6e0544e6c38669dcd70e85774dbcd8b5c60d9455c6926a1018f4ac560d70bf606ce40e925a5ef8a8ac8805c01784b839b38f85e01482204a9854e00e39622c7c14cdf5033ab5f190868e7c437f2b688c9b73e1a3850b1969349501e36ad425433ebc45fd19593b9829b688b235bc70241717f9a94d2bae89efc7d40788ff54308e4b77b8f7ca4aa052e1a3d9680dbd7bc247ada49080ac375746a57fcd859fc1760c1df16df34096b080cf447172178a2d056a64cc65d94776ad909bc15067a0960500087dc4278ade938a146b577af9c231d47785bf147f7f6e01e5e82643dcd388dea1ab49ee33f87642f8280f867e2e7b835ab8e3d8e6d2631f1bf34b558d8188259116edd9b01e7c9483f781fdc6dca4f74bc3e64aea0c60f0d1718a16739bd88fa2e9961a1db1577689a08f524c4b3a4580695f85f7267654ee61eacc89deae96a57c6e32301f3be7896070a523c82ea29a22304c7119af973a881e2202e5aed078c53606e3f806500a39ebbd09ff6624968dc70f99ba4c29ea4e76dcd686a9915fcf554b0352aa7462ef2831a35e5428848229dae4d2e3c2a3dc7d40f6ed2bce24b12f7437242f63e2a70a4042514850c882b62872fe4d330b5823064d1e606d623c23be9c872e5c2a1a9af95952f191b31f8000b271c2896441bdb2cea41d111978d064d643b122aaab52a7dd90e68629db716441096cae562f98e8efd2e5328e5117af9896bce71232e0b22917e972e24ee7f03d5206bb02911a4b3ab83915b62f72990d8a9250a99531b56ea3a3d5f3a49df0becb8be6889c3a17695e175f63f3d346c36e5404a974097f6cf7520a1410b09d3cb4afd1f8e4e8609d1e1d458a54ec8b12db0eb77679c79428b5aa7cdf68d450701c20a4025a73de316fe58fbb6ddcd63aba2cffa23f7537306122c69afde008842e545412c6a29311de30088671fa65f3982a991772d8c7758010af2ab956477558e9a7369f81e81cf63b40baa4c815b66db78f30824e32db5c2ffe4dc18a24bfe797d0f26b1d9b4d11d85d3bda46e9b1eb1c88fe21fe173a94d566114a0d2b99eaa8736051c44b042f88fc093707a3e99873b81ae1e9ec2c0d36c3c9e4233586812adbdf52d61d8e0625f5aad209f7dd367c0c72522c74568cd88e9e0897d785a82dfbcac1255a4ac0c760275be002650bc073ac407a74839cedfb8077c90143c491dbd38c261e213e27f51f233f48b4a7a9d657e4e9ab40a7171e7875d1355718610b0ba82e23f44fa657a1cd5f704f1ad1a318a58efeae3114b270d7e4b52cefbfacb187ef9479885e01d053defbb254827a9cdfd29640143511bb170c7d1b3c3a560b3d63e302fced3e932ce4ec94617ec234520053ab8b0c259c75fa4a81141117ee1b73a699d68aa597ad42c27fb6805d9ee1226e6a59c47b568dadc877164aac4bc347389bff7d2178fd9ccd6e51e32309eafc7e840f5ab571f5fb095fea491367bbc8cc4c99862c98d78b580b3e9de9abfe9d6f7818de0757cd70711d45268ce1f1b3727837e8aa74d9bef4a777261aea44d685664bc74c11717a9085d5159632d0eca70b293bd2ceb3afb32b95bc64b0790e73bf38fa46291a55075f9f8044e4d5f4b915776393a184101fe8397b65715f7b40fec480cf94d3c899d091fab8dff8d5649033dd635d23d57f39c035c028b14b10ece2fc7541d5fd7d5dc3c6fa4577519cbd71412fc2802e6df1f75182a2ff70497972c28da851cdb12b1827b047a7cdc066acb179136f55f96d6def4bbc1474036dee6933c579491acfff87c64b1fbf5a5d4dc4775a1393de8214b969999411c265b4c25ef36c0408b3c3409f47f4ac94b3d0bdc067b7265eff4d9a6a22082bae04b93c9f496199b8848b4922c02523e6ab71b431eee27992882167cfd7b75a38f18f916887be28eb61a48406d6df8950d18374e0029cde8ec3cfc0285239c8cbf2f792889cf503230a0dcbb3161c56ce99e37445c8880a6bfe1dbdf7dda660b010aba5f14b13d1225a51056b3b809fae885b570c9870f152a44545b0957cab7d3716a99bb62fbc9435f7137cc0b3b842cd6f3c65d46c7811d323679d06cf24756f9c74eaaea078d65ca0b9f78bdfeaa5e2e2506549f13155445cc2f7e18a728e74f641bb58234269767a78205ce81fde2677b2d368cb1a9166e8188638854279f53c47b2464483978d18dd25bd705771ae86e501b01e6f8b07bab5ced84f5b34786c33d65aa7ecec152dcd0fb43e10b48835c52bcc04602a8cd16f36cfd2c2ce6fc5b64212b1d946b1ba324666d8da3bd2392870eb3591227b92834f39dca497b2209015db0322b16fc1fd1a45c5bb591736c907b7b5e518aed13f3cfb1c5fba7a911294c3144fecc4348e5cc6b9a68f04b5dd682a0295581fd4a42baa079ea5b2a4233b5ebb4f4da98099fbc4878e03eafd08fe7c7f4cbdc2e742960dabe2fc979b7884eb47196df7cc0fef745a0d4097ad87879113393e48ecdd7357bd61d9f6318de47f1ebc95fa52a216ac08ae4904838bf99c9364b838ee45e27f4107bcfb9d5549fa653f66e9bad07824dd04ef0011f4331928ff467cd22fa427d0330c115ff85b794655b39e9717acf5a6508e65a240f54780e0d507b586c8260915b93411a9f1bd38d6925a95133ac20723f2f71160d63ad6a20f9af5ea835227a63a8c71b496a2e6cf1bd105ee08b6c5580a431ab4dd62788b699003e98296824eb17e8d4fd8233c75f4f53725bb7114df4c1da181afa04192a80406c2cb2921f58d1aa6a2004d080f48c2b63913fe02a1a10de875432a678f7b7b7ffc3351d02466621b89a9768c695de26cf1aa196f7ccbdb5b08e5b52ee01c9447cde52132317168fce2d07f2e1d7857857ecc7581be90139a89ef5e002016875a6f31ac523ce1b43b1d8cc5aed75e76fc98102a42d29e00a72521708db3415de275aa47ed08f17390c6157d0f672dbbf95524a046fdbd5941d6536ec3655c1cacd3f99a46ba5e87a8ef8f1709d52f42a8be6877eb7e9045a58e490d3cadd332a093f69139b0a15eaafd60f0c02fe59650fd48aaecb3042b20449a815fc2202d8197089e30a420d8460a03fc11a70ee6d294604b03c780c904d8a1e238d042e475c21720576780e66d077f81d23056ee3ee1bd409b26123249f0fc79480c87399cbc58f7fd85d81b30ae02503ce9059af503508e3201ba8ed3c9d215d170cc5390c63f9446927a42b6362e7c07b963082af51d3e3f3cc8ce5cc0066e4e22249af711328cec47594ab688d1f751a6301b4a3a96d7fc1bad5028571d564ff7a61b15865047ed592e1dcdc08845f389d707f1e1a9af5c4e8e2b75819e84452f7ae70867e8e1e95d783cdcf856094f94f33b7ba348a257201791191cda6debf75ebe323f3527fd915e98a1ce1a06c04a7c8dcf6f92cf23411afaf734485d1521961f966014078a9d6ebc2a2bda02d2c8026aec4b851a74b568705f34cbf6e800089d97fb6826cab5bdffb13497c2e2c00a54bb4acfa3a5ff863053e13b986e146558510e3ed9a7403a479d7714225b9ddcce013f4498e6a8502203200325cb675b8998292b1029d531749d3e756c4b0e9d892840c03f04448b09a664abda0312717621c79ee08d2fcf23e7db4b43564a556e47dca582618a08f79e086476ee732f0f7638f815aca920c2d6498192bf740a571af47666f2b61d021187db18d83af646a67b252e414f8c726af5b45d45601c82f5b99ccdd95da497ca5708bd7297956088d42beaafb117ad230870287a95b8befa83d2ba7d7b6105d231e320d4593791c0b59dfd0627dd2c3ea1e06c98111c2459e5d623de560bffcb96675e238a594abaa1f243e3506197f8688edae88a88cea9fccc836f2a01ee8ced0a159123f83ed2482f392188909c9bbb15e647a552f011f89c0e40372179632e74faa29ca752aaebeb52a146ea65e7ae33615d9a20286060a55f01deac18afafa69cfcde202caf4fef33480899c291d237a582b1e644f43a6a7b11f489427d5aab896a953f0baf1f51cc82dfbad589e33db08cf55c573b3074f6fc59e6ad7f323151d17d8c10340feac1a039ede15eecaacc4c882b886e0bc9c15bda395040c48c84765684422cdec1fbf78d67d74af569797178f5516198d11d1e66699daec98e8bb3b06bc5d08f90b4442c099b3c2323297c88df7116bc5638707cdc50fa650bdd69b902c9bee77a1720cbf9dd9e74c1c50c88c6d68460497dcfed9506efe358fbb5c64ea792dba1146e7d1891708b6d1857ae47c2af7e6aaf913bb1be00d96f278c221de4196a9b6b4f87a6ec7b85610170340e68e0490cad157fc596e08ea275e613cf680cfc67c9c2f112e7d9c5ca811fb2b2555c565628bfd75548efb65c2fb82e3839891b74d1f2915ba06d9ce98eaa0ecb45157993948623745a6d50ed6b6237071efe1fee5eb557592b2993c01a85433e733ec05f999e4c67bcbb9a628cee91b3fdc48bc21e570f8040ec4cafe3de2a1096a9731cef2d59dbd18820bffcfc11bfd03a5ec0f54bea3103c3aa9d1245933cb5af60be8fd2ad706d66362a75b875d993f16b959bc009f4ff1ca5ba31f65e423177b94b5ae0e6b31197da43f8ca7ebd1b5d32ed878211ccfb7aac512222815b34dd753403a3cd673bfad2f8ed53c19e6772e7599606f2dce25a19554a3bf624369fa7dcf4984642836dbdf2912cc81e4701e99307e3f1237d05600e369a4708e0c10c6d969fff6c204cbf9e24154f15403ed6d23bdb18094722f8038a9a06234d2c6dc7c0660757077f53648d49874cce1b6f7249a5ba4ee85d9459e5e89089183c8cb9037bee453d42b44f16aca30c0375f1e14d87bc389e552d2b18c19cbe7e0bba58b8850093e602ac3fcfb9e3a9e74b24c8aaeb6a0ed5ba974c2e73a302f8c54c519c46e81123b1f796206681fdd0ebfba53b41a71040adc31face03a7c747dc3f5596c1ee1b2762ab92d89de6fab402b081eae9b0e2d9fc7e2556d8a688850ca56b43e82deb49170751447e53c494985084229c10803b8f8e0dcf71a4d0c7f69b1de251be75ef6f4eb87d2f5999442880dae6bcb89c2978ee62b77eda21315a6ed043357c7855c01641eab6482baca5dad0a5b1a846998362945ea803e211dc29304d3eea73a405d0a4d280690ae0b5139145d985eb778d4e3026f084c3e52e8b30d617b56f202c0f10b906c143b15bc21822a8a17cb0adf97a7ea642e06bc82a8f28958d1eccea55242a8594744e001a7f5e5993a7365974a09fe1d45547014192c2074fb6961dec4c85ee248ca194036494bfc8115ef35068ed22d1c044124bd318063dd4d02b0228af7972c9fd84cb7f177cb3b39376de1e471b604bcd49a4b47d22e2359044ee893631128de6d55caed94ea12bd356d692d3ad9f079ec75ea904d62834f2297b3463c142cadfb23590bcc5c88710d651f942ac2d2bab2b9b9ea00cd140d96ab07b3c42db50ab5eed460fa1ba42de5cd7644e6310db34f8f5689ff604a103984881cf0bb0ecac86e42c590b62503fc997b25f29c82fdea08551719b9637d056bbf0c7c9d976195d6ac04b5663d1419a8b5255c9973bef4cb5c6b4f5aeb94d1bd3ad9c49e545bbfab2361b50ea441185bd2b7801413b7950fdf59d7f1f151e98f081d8a19fe0b8a381e432cb485a3f550e10363ada10b48a892ecf00f8d7c1307cd722542e3845a17b24de917086390bcef1aadf7b8d3a755e662d01ad08d95188fd7cb652029e80578bb1c7167bd1aa701c0992b2ad9d92d37acd0da8c0717a94e1be5f43285a228629318f064fd4bd93798ea465b2d397151a1b5f93cae42e36679d8355e82da3e96c3e1503655a6bfa624c6fc93dd91e879b814aeb9c13c61fbe0099fc2e49ce86e8ffc4c12144cf713fc93604e6232df3d0e906282124914280a7a3761d5f8e30d0268a2647fd3cd1384661781f6c6772120c42556a18588f0ca539085466d17b98ee36591e47e48e559d1be7b11a456d65b131aac246c611688c23797dfd00872bd8bef382c5214404ea867870909a59caf8b7642b2f32b0b093899b4177650779ceba5e04fd66f52c976fc4608a6e92b947a9e45603364fac4bdebaf274bb0b96288600f93bd9b0af10a8f9c371b6d59378bb6351f0d127663d0c8d9ab08cd0c5eae7fbe2b3e6b7668b55190bc289664c0349254bd891f58385a745431bb96dad01577fd929dcb7636e318602d5ff1152a4149a2087d5b179033d9366a7af3c3eb3fd718ec9ca276fcf0c1158d7cdf8e2ece482f78465aba5a04b55d49de5d68cf8f54d38e615efe9909c1126dc1cba1a584e2b334d924a9ff8c71043584a0b29fcf8fc809cecfcd48d75b4747634ed3bd73a29a4daeb1027d5d3be85613cae2635f9a15f64f18d7eee91fecd335c0ca4b624252a9323654b6d721df8cd02abb7f9e48c1311f63aa329484ba001bfdbe177e0ec15f6e2f4e8fe28ba8de88836eef5914b22882069a7490f96220e3819a09586beb41f87c3913b5ed9296eed7da14ddec110d9b9a04dc95bd471e4938650918611a7b914a980641950ff31eb9cc17aef7998dbed609212b032e2de302bae5187a7e9156f248a6ccd43b127f115922138b06f0beb1165f7ba0251cb858ca0974245912cb0527e70ccce96526ce82ee04e01115ce3772cc85f93499288d91cad6bd97be49d9ef88be4a0debe777e43aef293460b60ec746200e98ca0b2fa06c2152f9e83797417a4a0598fbbb040ccefe8136494d8d8f6fa25eb7dc50cd59961b615153eed0316f34ea29f1ae1829e0b4a976236a979e6123d3b39f757852c89ee2d35c618bf2e6dc21b77caee950c74811836e1305e846c652eaca3c21c4755d0f4fe41b51695da1314ec6b36e5e22f8300b7b6d2c3167771c5ac2793b3e9632094068e03a9018fa2b886532ccba3ce6ffb4232b7646a2be99dd567d394766bea4d69cb5fcbe4d4d4d63db5380e9f4626537213cb91a79181e3e1772ef8063879578d563a037c3bbfaa872223147a0bf9c909c8d676f10df19b17dd7c6b4d7f93982c193199d91c0b10740d2658a0743817a37fa17cdeed91d54ee14d8259cdcd2242e3c6dfda3f78e0b3364cced4dff5b292338fd13190b3edf9a4ee97932e96df046be9491dd4819d93fcac816d965208932a26a28233b6b06ef74ed808ceab19b2bbaaae3dfde7743980c3139a449319343a3c908ee9003db52f01e792b88e86cc7f1720b0108c8c8f3967eb4bc07f4049561604597929523aa2494064a045f731d12680d7091c80281bbc2fbaf1bd5004bbd3e04446f65bca7c63e98dd7d84b51f99b4b759feb4b74684bd65d402eda93234b76136050787b8eb21f64e69a4b1b89e5e881d6d839fa5de2a09a0f53f6a05f559c8bab92d643420fa6df91e19abc731cbe4a3a71d295c00292ac8e7ec397eef4c820d0ff8c1da13470d8fc9a21732a4e5d80540daef06732473dc6b0987e5427f577790eb3d0ac955d999a36b92b0e5cbccc6f87288f59319b03797f0bcf048cf69eb859530685a2b978aff9294a1e51099fe2ed0bcfd786f42a5a311c4088543082150609b4a563b062d356c6546a75e19712ecd394c94e6dc2e68b1e6b33a87be940763d76e7fd892234c1b19def524f48dc14e4e472f57963b2d32ffdaad9b7c660a33a044c4218cdce81c5bef55df19d629f8ade648383df5a19b247f038f4f4813b53cd0feb13a894d945fc23c0665848e8f9dad5adaaad3a9055148d1d7c08efd817037bbde59507346a0ed1a0b138c599583a01ed0d75c8dcb3cefca8e829df1f50cf4831ee0c965b3da1a19705148b94ff23af81095e2c43784aa9c4f731af72f4d6b2c2ba5c163cfa69d512b82d37e2c7de7635bc8cca688a00bab7d34f0e56afbe856b54afaf6e9c48826f7414f09e7524ef7b90f02a3e3e8e587843e1d2067af0a46b3f4fc1a939a95799971267e67d0efdf43871e90de3e69e306d43a9d7c00a19ca9d75c233d36a2f00321bd1ff9573363e638072312e45744a2d4290257c46a8f06f8ce903e106e9cafe86e7abbc9e61d5be734903dbee8eea82b65f1a9882965e8010884c886107104f92267a6842ae2b05480b48335a4de755e0438ebc28e21c3cfaf5a0ded840c8cb66640d5a051dfc9829bea565ce0116539f1248ea510baaa10fc8624c3baf093f8a72b201cdf52108bf3ee754beca54888d9dc113c4cf51fc47f7f3345c57d3d33e22da8764ba16a705095f460f7652881a65ba7e68c3ef0d07f3c4059cbcc376e7560412b5311f9f3bc387bf54e85329739df9858d818b1345e960e59004c2ce0885927e49695b316d497af4817b8e071c623e569baf04cff59cd766db2630d3c13550a16bd45f16894b5d4fae46e6151a580c2289bf356859d3a468db05dfd0a38849af410a717989f191239f1e4bc3299b91b605d39e1c5f9d7bace3650d0fc75069caf412bc744ac3be03d5f0cd8b977e10751ff5495de0549ef8daa731c37579699f6b7220eeb7b50492e29b80cd141c1301481bc1498f04c32c0800ee6e660dec452e70cce6ea5c26b1568cce4abfcc9581dd2306e4b1e002d5235cb12efc32d834775ae7f97a095867594f5fe2fe62f18aaa77e87c8d75f74b3ced29d05cb8f9cf82ae1d0df521c001d42832b7dce4f7c8a5aa032d4b1e0c0e37ad8931da0d627145e44cfb12629661ca1df83ab87a07e973da801ebe4cc1b49df5089a2130cf7890b79fe88a684fcfcd83062b2e426e5211c52fe21fdba135c389e4e1b74c97540ad8ddc599d62e6f3c455983b76a51328bd656044232259f70b8d034ec89e49185e07bb39c500ca495e52f117bb9e227a8d74dcba6bf56d57746bf90fe2521375e8a0d86d7e108f77b533c60b6637ee529cac1c798b638398cc397ee34b641e3388da3b5bee528cc3ed443d8ed15224394717466fccc8651fd1c35f2041ee9e397094400bf1853b2aaa5cc223d96d945597376fc641037a6856b112f0328f83b283412c053bfb95132893606bf36a72f6f663ec3e2de4f13dc78286f94f02f053ef00a0eb9d4c0f35a07309c2388f970aecac146194ef31ca07606db100deef373b742511c7bc33c958504d8f5122788826c7087a98457f9b16d84c03e80274ed513f51d4069bad380ee84cbaf19cc70f426402825ab0c7eabf5a558393ff7b753baa29e4b2092a9be1d2d705acc54427e8acd9e83b48c8ab92bb6a7a20584a3d3294767fce9e0b3c10cd9b94c35e3181fb80f27423efc41d1318a58a667d446895724ba5dddd1e743d0d5586cabf7e3809619993c93acd96054379451504b916f08fb8b98f0cb03a8f358920899e7a0e6a10ebade903d53989fce966f4c75d4443c271516f6a8f5ad7b5ca7faf6d0c2d6089339c5c3551df0e91d3e0cc34527ab356d29af8ee8d2cdcc6d245890d65aa63fb0474ec9c98b214f1715ef20839df7d20d74c6625fd705b705ed96009c5a208fcc15bae122b9aa6d493a73fb6220b431ab27332901b5c35687d95a2874f96c642daa3cfca498a169d8e3483e64c8d742cb460d4c024cbbcc1afa8f6d0df4cd58a8316d78419aa9306693b81145a4ccb726b5dee1e01450612320379b03c3f08b02bfaafacf976d06adc937350806e317228f3e5b3d1352643c46751900686199cbff6e7ffae8200a54c2384cf41c691c699156c99d704d1949fa6f343e34e0b0085ac7b136ff6c0ecbab65454f29c2b3d648c6461fb44bdda28127815d69271568920daa48b2543647cb95e3029bee8144f9a3ae2927179a94963610dad0c19e75ed8302019d28f1cb2eac21cf6685937239f6a4ac76d89f378ad5a4b7d90ae6b5e54815f0cb6c2aac210606603cb4ab13013497444cb69238bfd9a018dcf72973bb0075e2e37b67829f930f9cde0b588011088cf892b4fefb17bbff82783eebd12d362f7148bb001cd182780c0307fc9a081756f3420dcee247d461fbf05f292fc197dfea93e2d9cbfdab9dbc1b3d72274e581898b0face09f103a8965f46ee88e8eb8a2b86aed3cc66882e59d23861b211643c3395ac263409cb792b8b0ecb43014798acb40240e4c18a6a768ff31b14b340d88962c47640ad35758de28f6c2538ec74b80bd8ba9fc74ea61f69553b425bd7ae7d6d919cabcc7332419684946e61a5bb1bc8fc9aa98dd65ea08df055de0d8d8f43f2d1e0ca08fe32c919a7471c9c3718b50e717806a62071f158a23b26b145782baf44b9a6d824e988f9d6065426dea5103cde1ed265424f573871a9a5f49f6c24452cb186b22e2147b1c5b1032887426e08f2a6c196a41c02a02e3cfeea055eed6d6324513f822353e1580c877553046a058fd5c1c629e6e96aeff9496f4e564b8e1d0824f74bd30d7840d1c7ef3d104a3b38eb584d8c91228a9d060b6dd90b2487f38a5dabc71c8f4431f5ea187ab61b29a87188e1fb4a6c703aa5945e3c2f1ccf3da6d41a611541b4b6f804bbf08e21283cf186311b62f86a14a572fc6b2e48b1b8568c4a9a69dc4b4445b900b61a854cd203a11e26472319e03b3d7f48f6151e2cc733505d90a33a5e698f5429d9c28e54fa463ab5257ac3a4e913b7e48c26a210bf18d73b1984ca44e289af872c5f40129d6e446a29b0c967a0a333a3c49c45b9143bb484e63659047d4751c32b612826c8b1de3c194b124ee4b74253724bae4b0dcf359cfe149d21f6619afec7d6b9010512f2f7e0e2d167321543851ec923b817e32598c664d09fb453590e2e849266da5a38c65793a4c244bf14166ca62c62742ac60b238d3d9cb2989a7d7048fbe37d7565eae023c23a1a7d18817c6c8beb020abf1fdff893ef891ef074d343b93533481a8182357de8741aa312e6dc942d0c77fd271a61d4acf7a4d2bb563a45f719c1d0bec0481fe346975051c13396d6fd787680ebd2647b0891e0823a66e07e3af3c92cbc2c842a7daa84a57e31aff28902bc58b23de5a4dbd4164ac441ae800f8e2b527c9a7a500b32c5cccd689c7585196a6dcdfbdd3c2b4fc5201dd498c05c7d8b7b20301cd07905a8281bb595db09e37d152d37587e619c2096ec1b7156de9f6f6afd3680b68d7e879e1c70155f7ecf78e6563c06c0f86ffc619de6406009dc307a306336e66738887af3ab0e28ead1a612ecb5279af9f402af6d6ba1621de764760fd6957a69b8b3f47c273770d477de1540611b8ccd64168171c0371abe2d96fafec79338b1ef5134805fb5b72b83ffbda9d339208b5a9cabe5e64b1bdbd6cb5b27a5b63ea0efd10954f0137b3398cc67b2da488abd11acc980be4c5a9e115cc114b1c75e3bb5ff4399a42ed0c7b9b118466657b233bb0fd0ee9c9844e6189a3f468a023179d9fb91812221c5e934f84ac6c08cbae4ea19a5af7d1690f97775769eddaf105b33892e92ba407397f8ff12f93d6aca08b8278fd06ade1d803455c30069bee2bfe78ef3009ca9900b4c0ca25170b367dd797c3656e143fda7105eed9ff1552ed8a753118fd8d4bf07c94111b4e8c83d77cd60925f10498de31650d899f2dcef45b5355c72e864fcb8e8b032f2596498db71632759fddbe7ddf65780d3cf67671ea5d3463e9f0e6dc3321d12fc83ef71be043c3e3cf3308946e889f84c3cab7bccb01fd0a99c2a3a176ccd1c33f93696933d55a5b5ae9fb4d03ea5660019e748e747eec046fd416c85d8a3011ceb1d589bfc1eb446168b9fb524b6c9f9183cf3bfe837abdd787295612fab6584d2beb97ed4cff2e1f39325ece4c11ea7499d7a7eb15aa1c40804853446918b32b58f10d7cd29cb0818304eb4800eb6696b69ae2572be199f16d8a4c72fb3e32ce52e874729bb6434de1d6b862073fadd74b09fa38f4392ae703b5503b069764d925f3aa4bebed48f4b5f9db30aae48cc850d52bcc6e975652ae8e8a47747e7e61a8f4af6ebf77f44f10b69a9b2cd1b2a91156661ee8df53e036fc241eecddd9ec8c69746ed6a33690b77f1e9f34150df7b7c08dffc6b7874bfa007014d2d3aa6d3dc20692ad20b2f61f16b9428f49ebede116774f64883f269c6a8c614876156c877e0dc67c4e03d958165ac6de439a886a62930ae80db9063f1f01e79ba30df92b7d887f42adfd15cd39597488bedeff78ad309e30d609caf50af3274250f200b764514b627135e2d18337c082df38578506355ae113f17594b12637974fd6575a5ae52d756de1263c852412ef6cee6ac054b2b63d8e34b9b978b036a5d56a1d4ea8f72d6d19708145ab82771b6656deaba3579ca4e0a36131fa67b84d814ac435b104131de643964e30ed45f870b2cf68bd3b55a3c65df3d31fcdcf919d5a8f7e92f65d900c39d67b64b114ed80cc464c1130b109c874441ebf390df58f11c9cff82ab1d4d560fb792d1b6e3cdf53debb5ad9fce39c0f62522668472a0d5bf683b457fcf1762486e2a3d48050c51f8d5561286a5624c9b0bd3213862d073b4dae0ce5f4d592af1e63a6f5c0a562e9755529fd1e15af008d3647acbadc295c3e9650c2ea3786b59eec94505c8fbe426de566d435d6c085f00b50babf58623b1ad3fdd1086c2d7093fbe9dcf40199c23cbbbb12b0b94c64c7b60a97e40c302aa427959a1c2afdfd0f100c05f8180de9d06fe808d861214b8759e19d48a914ed482ae11fa5844bd2c08b34fb6b30e9d3adb6a00bf0efb5b6ff8ac29ca5c426694e7d4d717afb9b11e5eb86f622555133a993ae56c93683be914d728ac46badbd8cfa896dd7ff347bd659f6c7a166aaa264b486acb248cede20c9a1e7c16588f9b0831783ca17f0656493ef03613e0dd6315e202cea5be726b1398c284c3d9a12110f78c0a72e640876f8891781ac35d106c30e8d80714c4c1657b99dd640c4033e107beb40b00fbfe24db3e481914b695357c183a2fae78c85b687fd6f6de8dd3e284e2b8308d37766c3a3e7b48376dc0da3adc09876b0e30862a1c0ba8692f359ed1ff366e8a8061624588bc079babc0665fb54ace8ee6babe374309adc4c13218d2e6eca37fc9ac69efb03c121ff779cfafe83b05dbff61e088e9378e836cf5d8be9d000cde194e8e2a38f617dff093b65eb62fc0f943581eef14d9a35bb794f5d0ce638aa85c4bb7e4e48c09ddc0a869b64251f8658b32e864edc7001dad3d5b34ec65b902842031e7aa8e811e1fe27f559bce3909762d8444e4a5f7bfe24b66a62ce07ed9ced1f93db5f78db1c0677063d71825bb18587247ebb996b583aa1f4bbf26fadd19d0bb307e7bb7239ebefbd628c09aa2eaefb7ac68223626f35e426dd889b3952b41f28c1daff50f6a123bd703483cba177b512fca26f57ce0c00efcacbc540c9c175943b24338f8164e4c866d472f05fe8bd8e48f8fd3eedc6371b7f12ca27e3bd698fc4935dc6bc6d94b1df0997f129b8fa7983b090bb5a80c2b84a52308a297edcd55ecda6ed9976f51e42ea29828adcfc83a9abe9344dbcc159efdc8ec83da1436ad0f2a6719d8356dd832c00e5c1ca941a9f3f8e3af7edcedb4cbab94a506ca224af4f2210ee97f47ebf430edd5965378d03daa7a79b9f262a7f1bf4b34d2482dbbdb50e20114e2590ab4b8b59e3aa551825b9c664fd8803a0b25c6aa5f74e42f0969780c9bba427cb67c92d17a817b5f68990d6fcae15871460a2c50968af40e503ef12d0a2d39543af2c6ddef81ca3eb6daefcbc85697726030a99181e94e4f30a04d03152af7d12bf9249288dc5150652c0926889299aedacb1451e390a9187fb9207f1359b9a40cbf35055bd008761124f15ba5c88a13dedc14063b03c004f3646bcdae0704db6822e1281fca624057d0b1bff4e688996482d1be8ddf6644dca643ead447a027c1a8abe0dc32fd682eab1849a052138fa33803ab1e3e1a5dcc1ade818c48933398747c9dafe2fadd8e5df69a40f4b1c8871e5a13cfb00e5cd15b44124cb2b7114071ebedb29ab39c28b5c0eda36e014222885d147990b0727507fb4f0ce27b4b449840da2196d63b48e80febb89205e88dcdce1bf91f3408336237330dacb8c3fea2341b6e3842c18ef5ff3256ca838a204494e284fe8c960d226920a27b7424405275c660671fbcc7aa6f8c388e812d39fd3b1f83d44a97daa9e0f48ef27bd6dc75cbd787fa2ad8db22f0ce84627944684d737cae47531a654333dfe6879bf1cd43e7ccffd622100d537e6bde30f41d627d77e52e237edf25353dd68897adbecd7723a2453b8524583cdc5c4474a0ed93daa0e68222808c11dfa258aeef6442d0f4dd78fec2202d6245c5124fbbd923d971ee6ce697eca9a7aec79877243f08a115555a393152d406719d6ffe0b718099a26c0e79e8583120be824cf48ad466e51fdca8a503625e8827c0057a0b423826e900c548062514c7e441a49cbc7778c6d4d8d8a29a1fbc8989d35073f2e8b984a3d41822796b208378ccdf58bcccdb1a700f28bd86bc9c196390e8075d1c288319074dcf9e070f3bed41935a032d9aa1e14dbbc1e25f37001ba1e37f05af8610855d28ccc6cafc2f8d5706f9f37e927786c2e6e56dc666b7b7a543e8bfbe0f6c7332e64a7ab2e9321f1191dcce22bfd798527758e45984389b63b4bb215b28e0d6bc21ceb46b7a8be0f0359afbe6dfdeaa00e192b919da44eadee1e187da209dddcbd5d9dfa0bfa71307a93c30229773b19a152aa9e67054d2c47c018a15697603940e9ebd63467bbf4134bae0ef7469a62d09782c4480a54179e3d99fa77d562f2ba1952d6ef8588a190ab0e44c1d044a720f3305372348f35bfc627a70780a12be49d52c1a582f922b21cbfc567b773dd718156a238e968c370614b06313908c1d736d72984f503c999c92ceb0e93a975de977c6e4aef5eb938516d538f4e93bd2f7006fbc6d008fdd397faaa9abe2c87cde26296e83c17501195233af77bb580cfd3efe12aab0e0960386e961fb99cb76546be11aaa68b842ad6ee28f2a05f7c559aab95662ae999a075831929086e088e8b0acad246a63fdfbe4c205d37dc993cbf9be9f20ce525e4f8638e721bf2d02d07d535e3d8addd9cb993b2c1fc979d56f56158da24aeab22f59c386fe536b58e3fc5d37f3f1e860d41d9c88e0c012ebfb2d08657785084a35aad7343305732ea6a3e736dcb112717c2580fe0207125896a7efb6a1a24b1d5b8c007505af8384c6850fb76ddf5590a8502ab8fefd4211232521e75f20a91c2e09125e0f35a39918c8bf127f1509282d20905467393d3422bc7fa8b97bc9519202bb1adfefbc86cbe3633d4f9257b4cf1c5fc927726c3ccc8f06e9e983a10951a329ea55a0ac989737ded7230470b07c2931f42dc235924b9e441066bff5d562b3400f9a5d94b30117ec87569037b128986b064711e72224409cf0bb87c76fa7beebcf3057ace118522c5de1665957f7bcb51cf64000a68a9ea3f242670ed8149b2585f35448b4acee0c4ac27a104ccd0a65d76443c2f1fab64f089cd1426c52c7c95999271af496bed4decab8b9293671fc108b7456769fd388df1e439560508c68e751c9e20f31bb8745bd234c2fbb57f8ebb562396836e54f848e0eec3e2c30df4d09ea29290a5871a47878bcf79aa46049ef9844254f2d9142fcaa665c4a0716cd6ef5e894aa5505639eef7c442d105968859e273456415678a91dae2d8982905d95e33aa20067aa5d7fe2708590d9dbd0618504ed02c618a8fc3f61a77fa496c4c8ce1f822f21fe06b8bbcf801dcedd340c3267b8d1b0f7ab93721f6870629fc5ed5a0c3629b48c5ce5e133bf2e2de7adb143ec4b296c5a2f2c98339ba8e29eb6b25a5ba23b1964941a372db6f800310b35f91355f28dc93fb3833fcfa40e394a8f33c9c69a29190a7cd13992412df6e40af78ee050689296b02c34ca21ee04499b9fe7e2de8d6accd0aceac3f8baa1e82340b16da21907f4cc9b629d9dc70bda2b09edfff8c59937f0775b6b5d2425c53efd0fd59e616a4940f0bc09d4e0e6f25fc0e432a13a7c1dba0fb1a1c2c6240103f6d23f8d6a0d6261e26a53e9aa6d08696fb0d0282e8f8ab4eea83396f7e444428f2180d863a7dda88838f8d1a76270ed0756ac6c0cfc9d27b36942937738658135b44ecc16e867ce572a8b8d1e7f9a12b6cb957c4cca8cc77f322aabf3005920f44ea72d40d5d8b904cb847e7ccca8424500954c7570971ab02bef8c698e8e2b06caacffd98fc01017a5360608ec8387d4dee18123b72292c00629c3972e0d6f242d118792dfcc981c0f2c64977c236800b948a83e34f65f429ccf13ee4579da761f17b1a653f33245e8cdf140c46d9442e3b565785998051e01a6e88e941731e66526cb0b25ae72abf8bcd3b7eafd1192a56cc857bf803190cf8cfcbd2972cc6916e2ba3a3985fe79a0a4e47bd25d4f9edf461382eafbdd25dec00b621e631a6272c9788d3b3bcffaedd516c9e0c8587a031356ab2e291705f4b4978c2ac2486333ec7703868becec1fbc31dffd2f3a4ce1302ec0fef624c675e604da9df3961ac3591ca2e553a8c09180efbbb53ae2c670eac7dd06361077f510617eccfa5feda64eff84158f446f171d70cf217e338c8e420351d8bbbdf54efa3855bb2834880e25e98b6be534dd726e462b4994d29d21755d7a753dd47c684c21643b74e6a73d09fe9fd87b5b587ae8dc39a0710fcf626718ea621b6e42d7eaef248c3601e1f5b298fc9cdc8b4d449ee3f3cac7a078f0de460f8ccdf222e4e97401d119795aaf13714929f4cdce3cff21e89cd652e10ddd813f0533f99d8169ab90dad4f7dd43c9e054fa8b3c250df7b7a22dde21d45802466b50d7cd15ed31179022721ae843218fa0725d66309b1e049de7f289e94142398214db6d48b84d245c535fac978d2819680790d7794d1bbbcf7c98a4d54d7de2f6278a60b5ac63313ce14032765f35cb263f069a8eb3dd5289b722157ad89b4f3245c4b0568b0061d29359f493ed6f754a399018f06118a57d609b86ce067dff15860c506c8a2e8bfc2a3cb33103ed4c30a37d941f8ec5050d52612ce86e33522661a9b998e284789f33535b541cd9018fdd33e3ac5da2d7805f83ac388b833c07a97fdc17850abcf2340c721faf3e19765abafefa586faab3985ae107bcf82e43785e293c159fcb7fa485498bc9a0541f5363a3358cfea017b6c7f8702fc46cbeed55638d75c0bdfc305a2240373e4f6f779e464fbbf625cf7ab3905ebc89c77ca2ce8466fdf6c5fe5d49cfcc7e4dd462eb51c1fd6b66265a660dfc610f899d25874c321aa027196c7ac5fcd193214fd143dfe13acde2a7d131b6ca1ab61c5dc7ba047d1b578cfe80e01a6912c0663e40e63dc88b160ed2cd2f67058233946aab2382b667618db01e40c6019f7deca76be51e94bb8ea6824bfa979b8c7c3c4d971d8f28d65cffa40b33648acabf33ad3acf1275dced5f643ee50fc4f68900eebb80eb9c2a116960c6d593af8d492c1744b33444d14be5c9d91413f0fd5d8cbeb701d148fc33fc97a0adb31cfce25b0e069a90d88e575c622445d73b587c85074e31e3078af669bad6999ce81042f767f6fec41cad6436ffc4407c1b5136a62b660b67bfbcc151233dc76f4bffd1a29d2e9a74654b3c01ae1d8a3870123c45908b1e0e83c67b7a62969b05f8d442bc45dd3a0e897091f8f907305d1ece25826709f3b8cc37158078345ea27a1f6ea34885184f280150921194963468803b1e6dff3d0caac48605e093fec160b674180cf085b3e0025ed5ef0e3e40c99a8ea31801e96e3d6d58224130ebc123e951f4bac9fb2ffc8e6aefbf503724f0fa52b2a322bda77d421e3b7068abbfefaf8d530db634c7bcf3f6546f54bc3d9ea341c688faf9dd6349b2a4aef5934e23e36b79cc974a1c42d544d711183dda1eaddd6b7a9dc26fc4b254136e1e72f0c2f50759535cd2ad0031316522cbb6cdd1cbfc0f53c40b25db2fc5335a7d4dd2f919599c497dc65af4aed56accddd0a78c0656012074d37dc692b2ab936edb7460a3becba6fc390b856fbc2ccb16042e5ac6432aed6d742834a2d851a494d7bb4e585db33a539c014ce55bd25b41fca4eba0308468b4b26000c2f2ba76e3e69cd2f311952c524afea669c793c5f11db925b40696364aee4d5e3a21a7757eb292b2fdac653aea8ea1c996ada9afcdacab6c97d85576324fa44f82dd49eb917768bdac12d087efb24321f4b02c7e46ee4b7cc60e4ff85e113774eb982de7fbc1ce40d07f6257cc2abd5363561259a8f08fa4786365c9c6c19bfda419fa510518c4cd866ec7892765c2701cbec65f474c8feced9c19d5bf4fc2e6fb2b5eac8cb4704613ae49dcbd2e8467f650fa094313289861e6e16cd1109c93b6f445e92c81a1bc63cf53128ca27f4d36f0c39d171800a472f9e29c2f90e0179c4c159e56ae229e7f23896bc9787304c39797e87fec20478601da575bf8af0f7bb0948a35960642dd9efa36f42e98097c3ea9120c8cea0fa8dbaae33799ce31a90a86613f6a30dbf0dbd517e9878d860d2d4eb314923081fa25d9c8c1227dfaab583f0a327bbe7a223049ca879c5730543f22851e3d797978f180d4d426c89cae0d6015843e21ee53ee6b2ad45e3a49aacfcac3b0625ddac4e8a7833faec8903122448487c48b244dbaa55aa8ac77124b463facdc8d43651a25dd7271cb08b3d0801b146c01fc46d5679d22dbdc70aaf5403e9bed3f4bfe3248f005589d6e45bb1fe2200752b04dc69535c04862b0ec965d15fa930db24e7b997a6849dc48d8c52d4047637b3a0417a128c9a2a0947dd6505b3d0ef09dd2da7362fd6320b09384aab771e2673f104528d04f7de7b2d9e68b63d184620d41d111f2c1d2df88410f317e2de4cd75fffb5d3f0c7a2abb2b781462460e0c8d8f108f04cb4729608c9646587449cc557379cfc595003472fbeb42f39cb7181afd5f5e6037a410f394f93a4566d262887a2d49333746235f4ff8012dcb6213a0592b352de66e832b2bf2ef2916b32746aea0fe2ea6a9ed8e9812ead0acc98a255bd2b92b3f2211a59841db0d8d5b42141fd96e6064c610da6729327ee53f75904bac1de6d5ac4f59ec6d804a6fe8c2da74c5900246f1e2dddcb07cddc8b33296811f43ad59a1a6f7a8feaf4c6857606950afb28097e709694101fa2bae2c1562fac3b6667c8bf4bcebf06e811f6107dad93b2651122f85e0a2e67dc7aab0216b22dad1c29f588426c568a775ef4a0b6f95b905067a5f810bb0a24809197704d14bc34198ae6b31fe5815551424f139b2c2b25db0c44c32c72e93ebfbb529bfb2eb879735db21786a20eae5484c7b48363047e2d95166f9972c46421ce0ca5284d4d0e8883ed89c7852d9769cd5b121c62ebb46b1b8c51dbd2b118767bf12f7b20f27d77f19ebefdaf6750994f1c411aba46cb3797092349a962691220dedf112dcdc1dcfafe3cc17afb485489651129fa6a33f8f0a9942869af4025e6b2ef638a1618c781fabab403619a2798e6e4465aa1aa8640cb30759fb194a96e48fec4edd9d91735cdf2ab6adc0410e7749c5624f97b7283347241c4b9e1f52628cbc541942c8f3a02a837d1e0e4a7fd5951a4b01c8892ee4ad9ae68d5321f3f0f8c80c6d03862888c43df8edbb0832b99e42431de0a92a7d5afad99249ff07921d5d44c1186c50cf97e10acfd48630bc6448f2044fdc57a02f33c7ddf1c4cc02810f983d149d17a27471efdda8b4645a6a54e88937cc0471d73c83730e02f264e5815d00426986302386439212fe37c1eb136bccba89eb596666cbd3b6c9599b9b2fdbc0b6232ef02abcd0f7dbb7ecb5c570aa3770d17396b412efd6d595b90e394e72958af7db572cd6f456b7b0ee8159359bea94bfa2ccec54f179cf353da35ea2236926a19c007eb9fed7a695d8d11d9c2ed917a345019a4e19c78742d2e73064893e3306d1adcb3f1da300cbb76402eaf17f5c63b1a39a9f51ad6d102d7c12d8d9a5da47b60e79908af0b850288736469f10a204b25e0f35bb0c78749a41359b88a8c65bddfd99876e5e2602c810acb9808d4282cb8427f14343bc8588973f6830b52b8dae1c4974fd914ec1f7ca9caaa309b4e03ec8c4e68bfa8e87498155559e1c1f56eead94f27f375ecb912042fb9d4acb7a30191801063506231ad7703eaf6242f2c2e6228173486d22f5c1304155f5461e6fcbc78950e6f63f4ed27561709bee260492bea521dea9ab0e047f1c0b692fd6947075003f2c82515a74c9bb937ce0d9133b7daada0b28cd82ecab155c0935ba6f4d93fd3239009fe632445e5d11cdf37e5db226c87bd0a58cc521e70e9007bf87222cedbf0c4d348bc9577bdf3c1800716aa5428639b0a676071ddf9d0b83564717e79f07946bba589a5048f8c25c12555b6dbbc26604168b905a68a049d4fe115428db9f2035afd2518f22901c5d1c021dc6f2e6182d7917d22307ea24dd5cce1de314903b6ab943c87c5009ef2b13c1fe08451ad6e235839827b20e7d32f8139cea17c7e20eeb5cab4df606eafe54a7a83a7388bb77841aa44e3af95d06331f5c503dbf07c592ffda235768cab126035623065ecb61569efe04235cc0cfce41c9dc702868eb1e0567cdee8ec583a9a5336d8ade4408eb7da2e0b35fbdf79d1146c737dc99ced3410a70bf4cd9c0b22d89db42e6dda319d54b5b39f6ed79f92c27394bf2feceb8868197f21dcf03528f20bbc18ae2370071c11b995e68e28cb296881d1271952222a942b8814051425c542c59272c909a2b57e8213a9c1a59903116726e9ec160814f20865cfaa182856ec5add8702ad1fb39d0230c250ecf9921103fc43afd59aa5cd52169832994bb4eb63ced7f1cfeba71f2d33301b59fd411a0e6f42866b8179e45a8948d6af323cc5c117480b707a3e3bca0e767873554dcbf6f69beb8582c9daa2adba874012fe32473d68a0325e6fee09589329f06d625566e765832d9b3883bd3400b20a0daa2843fdd2b680e9b4dd4ff30eaa1284a95c95b717b7f7b3eabb9c6c76b6b70e8e8ef59394de011f042ab9818f5d0e51da2b4f374bc45d827f79dc0a9715913c895114e4e79ab3b2b938b885e028c5865999adda14b14be1a73c8c4bfcb374877176d425b13d18fdf9c9573a739fe9b9263c364f2a5d6298b4a6f7833366403cd1882cc63be781e9d844efc9e781e6ae354c2a73482cbf0c28037bd3325d7e373ebc6cb6080d257cf7659da597f7b536131787485292662278560e00609f85fe2b202bd7b25185b1675ca5970934463265a6fd42a2235c41dc57ec6f66ae900162737bd599299d2e0b93eedd215b53fe7eabfda6a0b6ec6e0d6c32c4a686c547d87e750a42f05829b66db1ecc9a86e2f5aecbbba99210689edb3549ea5014871000fb6ac679d9f51ba883b0c42812a4935fc2e5d2c29fa52048524cadd0d3b3f45a151de2551e195c7356b179a2858391291ce9a13924eb119dadbc60d5084907ebacf7be0410671a8735fe8a995c57d96e8b616a199aedacd789ea1cedf3bebf39febb23608e37e14ea02aad9790d0bbee2f7fbf6c2c01d606570c5822159a305672138f4b4c163b256d98fba83135410810fc412ad95aadc9fd4cde58d316097b1e0ea92d26585b4bd157286429ea7a339d78ed688e575f31d14dcfa65c64f8afc46efcc2ece9e3378c40b56fac0f191c80c0aea909033c516236ebd6f178d9d349539af26305d734ab1081c93f83d8b65925b1e02c9190d659466dfcc1bc9f843ffcde46d39dbd424ad073ee404d6243e5a8c60d7f9221c401157ed18336239cdb9a3eecc6698e1df6bfe49c0972c00534fdebe32bd809ea00a058f5920485863ef16260daf45f63af01ff010bfc016346b3ad0717d1e1cd2161161b21bb8d53890672668dbfca03bb2972ed43387859fe06e66a9a7067c3b64ac8961ab2d9bb7d72677152848cb8d83a8309337474b6aadc8de58676afc37e20c9726cfd6d4621a1193eae82d6eea9b782787cbd323d3fad579529311ceb400013737eef17851c63e69d3c508f522d38d5ede79c50cd62172206014d4048573796cc3ddcaaf8643931be0a42518eb4472400d18872fb5980006c5691aa05209abaac0f81d25c96485f65cbaea3c2d64e2b3a9a79a7cef657dd58ad29c17590296d15e5ac4b5c6a5ead364688c7eb16ead48e90f4983f90b318f0f3b8972f91afc454f98838d4d33100637b20fd1b175d821e12c9473a80a3c6ecdea56d3bd62e6c7dcfaf72d387d30f1172f6808341131861a031b61d3faea438c867b5b02a98a1cee764b7e96fdaae10e18503a5aadaf066bcd18081996a6faf3c557437c34c6b6850d228e33917b966e8809440d0858e22e90134c8da8e10c1a530ba9c846619c066dc63f6e340c6796d1055cafaae00f88cda1f1a43db52860579bebddaf634cf77c96a6557ce4a30d87702b722919ad809f9adb47b54e841f18cc16badd8e298a67545fbe3acb4156647d4aa6d345e576d236988e4a5893b794d40643c527f055801864bd44041180ba044b9e90abe833077316dfe081650560e4ac1e0e35e57684b9c00229c478ecae732da8d49614b7f15a8c8552d025aeb45e2c3b9ce08ed6f3f8027bb8ac14d064d20fd10d6b82d3f5fc40bdac7d89a919a329f985519869f72ebf07cd401fd714b67dc1525a8088f558a7546ff549e7649f22c45ebf28eebbc0126ef372380d8d7fb02b51f50165bef3eb0d1a96383c807ed58c592f4ec71f5477d25d148f99bf54746476ce4437cbe8f37429f58a7718a442268f4c17c50c02af42c0a716da4db31ebe45540cb6b48a8300d58a414f999e05b2d2ecc39e46d0842048f3abd91ec057afb1677c2e36fbed3a9322035256ca195b273658eb36a1d8ede3cb3519da39eb13179cc1ca2911074c42bb21fdace13bae95775f0f62d78e08a8aa80ea58ba9ff5a2f4d6aab18f03829b761fd7cb4f71ee477d762a4d368b674b37757114c0ea03881bb0efa161aa2b736a1bdbd0dc5eba4610a1b7dc063478bf08aa562c65f6ff05de9dcf9a5b901d0ba47c8377cbe89c52d1904be2d91f069acf43c6c9823ba3e0ece612127ac5cc42fb56b72bd6e2d9562a3e4028e6bf35707443c1f9bdc6e970081104bc304349abfd2762e4918f59fa8646925e6c254239bddf2a9b2eed9d8f2b1fa7d3fbf5580f66c513c5b557d7bba07534026d479723e3032d60a97777cd918dc05a95102173bd1ae3e3fefa0db81eab02db14bbf62e2d6e993b61c345039e3c3a1c88bd73fd0712e8df51c7c015d4899c6f0da6520cdf003904769a8b65beb19c997824bcc67f1ea48f838400cb506cce5513a4adace6bc9c50aa4224b17df90a9b1ecf0ca558c6cc311b7378c6a8884c1203303b1c6ebf6691712fade8dd23be1d2ea5d61b656c26878382e37c99147ad858f9cf5023ac2f26f8b996f44108db8f9dbfa161f37bae2ac8dfc2fddaa9a85e8af6295d24794df7715882a58d392ce84fa07f0d4fbe865e0c61fc6f810dc389fb84d36b78caeff8cbef123402a45b5974408c1c48f75fb03bb36933f600ea69016546ad9b16784d4a1b6574e61582fc2add3afd520f81b3922fd3c7d0584a6cda3dc1aac1a0dabf915d45424a7bf4d0a66e74eafea7802065f4c75b4897f203a042467eae1c17fb3d36673e1009071b0aa098dd8677cc0b0df4913357679e34529b23a413a0033b5a51039a301db31f472f9f515cd5e3af74baf6e2b28cc50ac082f4a6fa0441544eb04e244cc9a96901bd44de00087edc804396c4c63c80213eb45dd0214f8ac1bf0eb728af0e13b5e21e4a6dafb2b650fdc0171d2bb9a55209655a975b7b8523e2ee9cf82ed0d774fd523696026c07e411a67415c8fd81a7e955f726d47f847018fa2c2bad287b87b2485d37d0621401eb1603ae3f3da0aff945c05eda3302b2dbe591cf14db01e5a82fb203f7a0e9e7f96258db36d3520adb007dce5696cbe03a37111e30643e11bdf316ee7e939bbe7b861436df39b4bf456ec351ba05ba061cd1fe546d4a6ccb7802b61d5078702240c2d63cb3f868f4ce883572130c8f8537787593146dadd7f1993bf0bc55a8eb0d6064f59d4e3996b4c98b8fdf7b16c6c234bb365e2e9b08ac5d4822146bf0580576ea2938385599302baa469094d0c8f708d1028bf92ee0abc698dc54cf6aa7b6a6851410e44e348abf0edae237359565e195f89425342bc6655fbec79a60d421a603dea021b1dd1b519a60f9102dfbaf68f63ab312d16b95c6a28a95e0300d7deeb3660eab2b8831e4d68e5651ad63ee55144cbddcc0780fb33ce3dc31de15c06ce97d60c01f54d3fa1c381f0d8a37aae6aa40cef3a82d3606ef96a3f338521196192c8e81bfeeaf08b96a1137cb93d462a4d96eff3bd49a8bd552e32085a35802e42592158e893eee4b02a80b55c99fdc75896a82effbdacf3d5184f383bb4eac9661a6940c06c66e4ba3f237d55ad707db8c3c612962e3aafb0a45291299ce6a29f539e5c30815de15ca668d9c0e0355ba19ad08a2fd2ff58a3971db4062c206e860039037d820867f433a2547e83cc94e2b406c63e3f8f7963c6ec2ced04b060777b3892ac3aceed635f1a05ddc4f8ffbff63a80bb13c5b08f879fb0cf4ab0241fd44b3dc6ee47e24a75e9da0297500c262040f3d61edaf5ccc92bc78d3b07ab4dbf4c4e662c4af6975920e6761b818fa4cfa42f0edc7c66c3cad433bf8080fdb9424e233255800f2d3e0ef99282bc6f0f0681c7c3a1cad5152a7bd552bfd5ab5012846a4b7be0ec79409f883b6741d8e562efa3f6c38cca2051e5f453612581050e9481d025f829c71c379904f0374e8a5b3e7d8e8a5f880fab3fb0188818c809e2649233aa3c84aed20b2b9116b84c8dd943829d34b430c7a5875c47ffada7d58fc47ecf4503ab4faeb3f7de87ff8975f5a237b4b99026212e813ca10557cb4af01560e398059b444512585b17e0ee02b8a2a298cdffb97f16b21562604ff57f53d88d57259b0d55aad1e746501bfb502bd857ae124493d4a29f572a2671ff572a2f77d0ae0ccd7196b5b2ef02d8d1168bfba66607d055bd685a5c995430a41dfcfb8b268a941dfe7904210f8f583ac3da95e4ddfd7562bcbeab57cd0ccab3e68e62bebd5f465998186ef675e4d5f961c3ea8f596e685966b061abe5fbd9abeb7ff217d46332e2d3568e6572e2d3568f52c57961c52089a792d3588f52ceb6a3dd86abd666065511d7950eb35431448acaf3fc3cc5bd7ca954314555218515f83ca18512f25277af69de8816f64bfd2e0ac00be7d2423f07f0b0bcc39b9401538dad6abea7fa7faca41a11e7425497972828dd68411b54f9526090a95d6cf654be00b07f5f55d202c85a3fa136c845958adcf04cb2d12d5bdda776badad469c58b0048c191d4c13628c49aa26c28caa5616e38f0ec6092846d6d704cbb3bed68e13b284a857d94fbd05df4fae6a539f7af04f453ed224b19ffa93cb698862b5c298d8efd3a1b6ecbbecf42714cec985737af0744a724a3d2b857a925a9dbed3fb09f52825d40b07f5a7878da717cc7b7c57b0bc47924aa5c2824955f9f8d110d593aa7542a1bea2be72e112a68af9522b93eae54454676aad33af4ac3e453a550a82d5b9abe4f7da94fa45cf8fed40497f1f4281b1a3636df8387b3fc0bcb5f49a92c51249dde2aa16a50a857b97caca1213aad4eab941d5b373d78882c41a29d4c59b620d1c58ee0b36cb46e589a56536ba5c2d13aa9deaadeae6898d426bc8cdfa7524f43a4fad43b4d68612922d40bb430f0ab6e4f33687e9084e51685aa19bf476174304b6c71022d6c4868bf2e6942893108e832fa27611995404011e387002de3f7eeb22510e74382f3bd68fcab0bf5ee6252c7ba3f158d1327a857d20bc4327e2fff643e26a1bef220dca093d37bce831b1c3f548ac649759284654459f0fbbe57cef8a4befdcf55dffe2909168b656d0af6d917cef7604e924ac9fec7258cd172f962f4d1eb6861e0db3fbd70c01c4f52557b0296db95f515eb6b75b9b538acbab27683e30aa55abd96ea08e230a94a2898fd7114a4ea7f8f42e1a050d6a65c38a9f7c749a1520f1bbfb7a94f29f5b131a16af5e0ab94503ff32b57959ad55757d2cc6bf524f5a814eac1127c317eff8919517fb262f4f1f445701945181d0c114c8c4eec30da8c0e260165c65dc7ef1db572e1d49ff955ea51a917d8fad4ea53a89ff915ead452a1c0577dac6f091c699c9c1ef5c2a9af8a4252ebf7a84fc251aa8fc2398138a7d59f50b13042140a85026988beafaf541f47f5b011f547ec56bd98c046d4a35eb0d5e9853a9d7050ef38a88a3aa16acd58732ccc554db0ece748c1c4e8a38f0610625cfd49a6faa4efe4c2a9abffea4ae5dae0f8adff6890b0686ac625704cfd0647f0fbbeef67beef7b9026098dbb2cfd5ca7f5c26102f37755a96392fdef6756bf7202ae5e382a96559d7ea53aa9be24587d7f7dffbd60df2bc7c25c044888199d95c3c20171c01713d8e8dfd32001ff737d39348c35954aa594525f55abb7384c604a49af5f384c6063fd5aa58e27d797f30d000c12568cafd1c120c16554e2a3fd2570ac52c954a91957b59e4017cee9c1d3c346d5af4e95ccf881a7c7f9fc05b3306b8f60e5e4c8d2faee3a58c6241cfb38f6c504063bc578103a4ec5a90fc24670b439aee33fc6adea95a41e7c2cab5712165b5f89bf50335f5f1febc9e9ddb25cf5224a3da8f4d520ac6a92a0360d8dad5247150ecdea67bebafc695c55aa93d5a75e4bf57b3141bda7725ad58a80f51d61c5c87a55925dc7fab1158449389ffd5e4c602b55129cd5cce32c81a37f959ad1bf55a5aa5e4befafef95a43eca7156afc259bd98c046d50b966361d559d572a00a17a0c862b4b55626d64b0d687d945a34d61fc072eba3f4d9131223f89f113b8ce07f556a46779d27a06be9546b036ce07501628cf6595c8cdf8305c862b4cf428139c016e3e9534a3d51386afb2c96d330a6583549a7df757ce1107dcffaefa5e42202ff54a566d4efaef33db1cf7ad7525daa23f8ac1ad52b61a57ef53855eae8bf048ef5abd48c95e609ea53afa5fae4f4f5b5f4bdf5718628f5dfa75e4ae0ef3aa25ea092fd5dc753953ada179880ea2e809faac205b00050802a5518dedcc0fc84c30436e68caaf1fb2376470c076195fa7d5f9da141824aa1689e9cc0d6077efd0fc6047cfb5aaae3097c150d123036d69712ea556f5d49a86702beea513444a857fd4753a52ed93f3dcab5549dd8ef34a6a64a1d3f1bb11d8449a7af9f048e3667b45f992cd5f17ba932c0f21e7e9a71e1d49ff11ec472d5d4b47ec66b8619df0adf8366603dcdb330f3e16b869a9f799ad70c5120d5fccccf60f32c960ba7be7ec6d79ff1ba12457dcdf8d60ba73ecd6309bad2fa9a17eb6b1e89f5355fbf66e6695e576a7ee6afd83cebb1046109729a1baea37a9cfa58825a2e9cfa4a915a1faa6ea078d08c0f5f57ae788825c8c675250a249baf795d8922c9899a67a1e22002609a826a6abee645c30c47c2b779f17cb1ef5bfff22fa60a5fa10da2fa195ff3493c4d335efec56a5e465fec041c68bef5342e2c412d1796a0afaea5cae473d5a6d6d3bcae5cd17db1ef692fa62ff67d0ac583705fec7b9ad627d5a616cdd7caa56ee1520417223be35bae9a67b140577d2dd52a76fcf2a548353fe375e5ca8c9ff1aa5ebed837a3e66bde83667c52f5327ef64157b53f569a1ae43aaa570a077c0fa23981b494552920081822880043440f6088c802668830c68c0e66882dc00c118211353a9821b000338405c00c11458ed1c10c31c4589f950233c40d2a3004588121c00cc6fa3dca005eed079e5061c010e08b14a383210013a31360887166743004d8618c6511fac7b2081d071361fd568c025aa5f29a4a558b42d9ef74fa4010044fdf7742598b8a3520ac63922a497d7729b156d867551a1c6ec2f103c790068a7bec7b1b57f29d6894b8c3beb77198fdf059a7cf56074fa1ebd40f6336b01a9ff4e3cdcf8d1f1c391efc1c8f23fc1b61f835c2a711be8d2b7c966dd5d711eb13fee9c3e7b1aa0f5da807623dd0f793a366c6d3dcc0f1ac1c385caa97fb84af2420d51fb1b5c6a54ac1136833569598a6fa05046b172eac576dd1be988d3980b6aa5c9a7a08932a171a9ad657bb24ddd4a6ca85e78a8b01551f589bec572e9687078887874b0f618ae6a7f59606f592c2b1e0e16952dd8025458918071e10e30058c1fffca65625c2ef95d4a3de845c621c08550fa85e58960d306764f5e031b2de59ad91f5e02bf680b08ea9daf4d99645a17846af64a480856f43a3d6af537c3f365e355a2a1a36420bc476a964a4e83126799830616ebe6781a8946a75aa2d1b3c926e926a52250323135b22b4b11dc224db65b45d6656eff67bd577fa1ef4626176e6e64faf141cbd4bb5493dac1734b64585ca35c57802bf9cd1bfd563eca13a559eaf4c095e6cc08030432b6d9c021026391836b4b058077800460821c008a165548d0e4688262264c004e102304184b16082b0e20313040f37dfcfa74ea9d47bfd4755952a0c6f6ec21b87fdffa79ea7c90f71a9cf92e205bb79d8a752a914f08c817901e4eeee2c2230209f73d383c714e10a41a02b0547283608e53afd0c96fd53fd420baa401004938058af62bdea9158af7aad5eab25ae631f7c39f88a5520b42716d0177ec5bf7c40dfe7840a082809080808685b0b6461f60b141ba4b241345a8011428c1c304140d1054c104cc4bcc4a2883d1144961baa882da0e67b2c6c05c78064dc2a711dfbbec4c29a88c11306cc066f6dac8630c93bb045c996ef07080890d3eb83410a5583624b84493c29205f68ed971d958ac7c280d86f67fc7ec765cec30b1e16f69d829aa8636c89b0b27064c2241fa3a56a51a9c2f0e6e69fc9ba0dca54d60c9e9fa1a2095b37ac9f81ad784615eed47d3fab1f9fd4a392545fa84696ca5577e051b5dc10d64f59588eb5299bba80cabf1f2752cf8a2d119e2c2c076152c873e43bf1d8184f1ee4a8d357296ceaa687859d5ee572fb1676fa180ea13d59fb89d1244608bec372788000113932b4a48911929429a7a5a22987ca658cd006a18cac4f7d5a89fb53b799ac4f6af55292c5fa68a93bf8f8ad56558552f9d7d7890b0f2a0b0b4298646366b40f74aafb64a315ba4001844937a316310f8449372a5518de7c03204306073600c78063be7c0142cb0f63bee810c60f5b6c00c40148e687324014f1e50b1553542f547c01b3a50a305b9ea8191dcc1624ec1428abfaafd65a6badb5d65a6badb5ca137046ca468dd5aa06c7cccc2b35e365c39542d1b86ab892c2715557b5d6d56a3503f542bd4559313313ba4e10426cc9626439cba6a5040d122590acb01faa89857d340bfba458d83705eabfd1a25e43b127425b5bf6a74eb18010b4b0df620a8d45a3c68d1ca8d60dcaa274f4b0495ab92cf5200a85b261c3860d1b366cd8b061c3860d1b366cd8b0b152b98eb5afd801c224df02f59282632ab78ff675d600750aeb63abf862857d29f111e513a68ac141bd3c655faa978dd697f0082b6c398d926a6bfd5e4b2553c980d5d67ae36badb556f0860543b8f1095184857d27974d615343a386c6874b542b2cac561182dd62b7d8174e874be77a552b7210b6926c93dd6285b045d8a6ef63358d4786a4b0d6da6ac5f7a562918565d970d12cac0a2949fe658aab7eb1b1d812d5fa90857defa30d17cde2d8c7f91d7fe3c2b1bf438af50171fcf7382e1d365e491bdce162edc8910327470e1e3972849023870839728c9023c7e7c8e1ca91a3a615e3a14ee15bd4903285e632f03f95ea716e5c2c009c70e3c6cb6dbc56aad50c1047ada25a4153a7b021045f49b0191fd22099f14a427dea51610d1abec5163663529d0236c395545d27d0488304f502ed67554b2c179818168dfd2336c6439874135340e82ad5cdc7033eabb66c8038a07ef538a45e354314a79f21f52a9c1550bf7a24d4af5ea71752ea552f1e37388429fbb1542ec6c1b7d6be8af5b52a8d12d5db174bc78d0e11fee686c7df54119e078d1c56846f845a47a84c65be9f5ac686061336063e0d1e8cb288f03c9e15eb521fe77b293a1ee7a5ecf89b19a240e2f1373f83082f1c9ac7791d8ff333f078e1d0bc70bed70a38afe39170743cd2eff89b97bfb25818accc5726ba7c7ff3588250df0b67051d7ff3483afee675258aefafdcbc8ed7f742daf13a5ec7eb8a8edff1b2597210daf89bd7f1d60a2cbcd0789006921410f5ddf870fcf77ddf97c5fae040fd8d077f427810c78bc9c2c45cb9e1aa4d5a5c56e6fba95f74d408c14f7dd28f65aa18368b85811f82cb36d998140b031f458362fa827ad530ae037edd62464c016152fd72c4d62d3e9415358ccbbe4f52b15833686c120e033f44c2c2c0afb971e5d898fd1d23ea05ab4920617daac3ac75cdfc8c6fac7925d9c0429b1aae24980d57526ba431f32a57d209c472d6d7fa52bf58170efc4aab1fe3a188d1353a982c618c37afe393dc06624e9dfdd9f1e0db5abdec7055315c3ac0bf71811f6b0a93ac97117cebc5653e82cf046631d62f388428558c8730c982654e362c1f9a07df36b9ecabf9243bcef05277409a3465adf8f22c2c5ca7068d99d6e7a271815eea9095efd80747fb35ac1a34665a5fea183b26592fb58c0dea69becf7ea171b90db0857259d7b1dfcf8d8e771dae96db586c8910e56558a75aa696b979f0ffe6a686f66b2ceb6f5ce02be9c78065ca3069f95cac2b59ea0e758bb56161d5821f4b40a8e36f3ea97e517d12cec2c0afffc5fab01efc6a85fd71fb3df8c52a161606d62fb58c9622ac0f0bf5594f9da83e09f55285532c0c7c319ab2589f1daccf162b84f5a92f298e14bb22848d3330202a5518dedce4d81fcb848da12c1722c047d17c3fe302ed67ab0567a09e956a7d1f3803f54a6a8d4853ec8f07812a2c362cec072c67d57888fb7e6afe53a96c84618d9b1b1aff362f1f53f3f22f07667d5830ebb31a1dd5ca12feccccab5aa1cb5aff25dd8c200d97cbe562d58460abc6a96583a6090ea779c1582dd6ccaab59ac17a5924d6b6b0ef4f9c857dff4a9aa9f5e4e2d2b22b2958ae1a911cc9127aeb86e7fb55ce0baad70baa2fe6311bc29305a760790f2594f8e2add60b04df9b802038f2c4101002b950366e78f480e58029d809045390e459c0a4e0c211e4f9923c0bd8e954eb176322045fb126c2fa6e4fefa7d70f21f8dfe9671e4b90eaf4c25961b53afd15950a69e55f2cf5ab179620141016063e0f0b8b251126791637df2389b0c99493f5b9eaf8192195160694c343e408482695c5862a56cc8a1096c475aaaf0f1f4b1048f3c25981c6878f44e3c3d7952868fe4af8345e2a241a6ff336af2b364fe3852508a4f9ff1e3c40df8fcddb1935ef35af574e0ec2d5876ff3493e0647f3b606fc9a9a9a9a9a9a1c9a1935ac19ae2b3db685a96ab86036165b224cf2307ae47c0c8730c9c3807d8d13347e86cb092d1f44e39d86e6a5dad607a4f1f69dc6a682de93d40a335cebdd9c7bcfcd3c2fb662eabdcbbddbc4c934d13c954b39e86091ea292eb53cd9d494d7d4138e3aa3aa7c803ffe09018a90b22ad509fc6af51c5674eb75053085eaa92127af4155afb59e4e402ccb7aadf5b34b6a4d555beb87b262abad298b034aada89a725b69c4cfa250b099afa0b5e0960ad66a672a0f6c5df9a982f6c3b526d95aad2a8ac5b1d5a2405b6b606b45d9ba42cd51eb67551b387da9191587107b43f55503545b51b656fbcdaa056756406cab1a51d5538faf484d590bf67c33d6d6588d4f9501ec6125f98cac836e9ba8e007034fb67e7e2240754175eb61187af50a565b715055f6845aedc9daafd61f75a7aa6acb5a6b5142d5046b415b793e91a5435542a5a1b1c312514f564705eb57dd1ad99fa9a05b13e54fab3c7ceeda40f52357d8afa22a98aa34f52bc25a95ecab405fb5167cdb5ac1eaa9d6140fd833024fb53328954dd52aabd6aae043d59505bfa5fab2bb5a3f9dba62d9aa4a557bc48a6045b0217cfe79ada9286b2b0d187ea0d561adada80adaeaaa47659d6c3dd5193f389628c55ad95a53b552b6ae5055b57eb68275490dc10ab13c2ccba6eca97ed5565b653504cbb229b07ea7afd69ffa55585dd556ad754815c1ce7ca9aa02abad55562b0a87b551415b4f60156259f5b322d4efb316b46015626ddd6167eaaaaa405b6b95d510ec4ceaaba00aacb5fe7cbd394145e443e154bfbaaae04d5dad642c187044a80000abb5289bc235bcc1c35a52abadb6d6aae329d7f90b582a191770e0ef821263c2b8e0fac2062d5c1730204a0bbc7024570afc480911e0a3ab854351a20a034071e5272b289144c6e80239d244091176b0e4f2e011a2c4cd8e903be3899f16d0f4f871cd549f16d4317c68651cc0e7c9709116041174685d6c8e1709c839cf73020a5c72c01881f6ad113e070de752838ad34e3ff88337b87feeee3cb3bc67baddac34cfbb94e63445e2ee3b4e02cee5238ce6229e37f229c6ed0ce489bcdee6cd2c335aa17077113c1c01c8fd66d676723bd4bd3682cadd65d84dd1a9887597d4788abb5128c2179d8a77df724653f3e17c1768027968029ba04ef89ae2410d61029a4ea756aa8414d4002ad103e66831f1046a004d6093158e191e373734ad5c005ff844606d544b954405849af986dccc9c3ed0a49aa2c9c6941a563f9f0174d0eaa2e95446d3a9b5c3044dadfdba40d3ea66864d0d39ed9c84d07283a583081bc01d9a6a5c35ae4fc76647756353860d2714544029225fcd4744c544d3e7a46707358382a2e963a1768039ac9eaf822d306726c90985940ba81da8195452cb46cbc6ea668523e5c237860da81d9f171b4e5834919004e67e464208a0ac5583b201eba7f3098146ad1c289b6a2185932a3add985151acef04b6beefb35fea537dab191ee0978a92a2516334639342a5ea970281803b6055e9f8706a588d707aa269061435d8a0401cd5900d601736bc66ef65902103146c2182301ec062773bfa610b92a0148afc64140c1cec9810d6a85230bcf0a4a88c108819c1083ef01441c41123ba3881161dc0c0041ae0c48e58c210bbed834e799003325c94400c072280858618a6bc20e54991922423889003471515c0d20006d030c32c041e191d6146172ca8926b2a830c1ff4600a0c3970d89ca00357640003139080037ce84100b3185c68e14911911621b80204162862890a52a045073880810b2480091d70b82107644e50020c5ca08a076469c22511121b1a3230f8a20b0f70a0025448a1801f78c0e52c862930b4408464882c6643a345083c6080237ec882cb1854204a826488c3e00b2d4200e68a0a502185031400440f59a8a840546488ec07484f8c860de865c0e00b1680f1c0151ca800150e5000103ff4c0431617cca84c4961471423084d98d4cc58018f2d3be820a5051e04f0a0438f298a55191f19768c53102c10544f800bb00ab04e541fb6e7e3a9224be743e17b1c23581e1527b5e3d3516fc01c15c7aa060d1a5f08d6b46684343b5a3a583c666c5629152a6551a8d3e9ab8eca3243c2079a52abd40a9ca1f249adbe105a483514bd5430d48c0a086cd554b0859a81bc8fe6b301b7b8c18826d40c9b5a813934210031a06986855609e6cca8c102c13945012b8a88069a8a3419b2b9c182336093980f9a6260c309616aa82799cd08476ab0e08e4f47575d464e2894a009851ac128cb8d9a201c3e253504a049c7aa85968d960d2b496ee8547aa6074d44f64a090d8830b5fa684e4aa00076a84105ce4085506b7ed4200304ac4e3041165519226658d6e9884d4a08c804c3e0c5861a27d84c9299125053c01352426ca080124a6c58800f38429e2c3464930158d1c4dad98eb881c6474d6581c73724a5c3a786999410d48c6ac6f743a4e40b6126490f9a6652443447aa25384892ccac96b48e4a221468ca01e6f8cca064356850332c24af1812d034a35a22424d87a6d3179e2a6a05d4cc6908ea042341acddca04d512d44c4a07cdd0aa0758b4ca618347093b709ca042810994126a985189a91f343f7e888c522d30b5e324fb7c562dd0843f925aa566c09d131310e8483de17cae130e9e958afc84201972a39ac1b1faa941a888054405037b803d500b683a6106387442e124c2aa07c8a3a6e70bc1862671a77eb64f07fc4e32bc6208e202196d0003c27cf1d280053031002c333069251b2669314c79410a0b518e8c9aa8b04406f4035602090078f00a0e9401860d64908211125e3b74a4b880200b2712b00292146421dc68b15056544145145d9c60620925b818200acc841df10823908eca10238cda0b60e00003b36c03840fa6cdc8909954971fb6fcac50b08502bac884f8e4b0c19a51a578e04c40025370f9a44805a2244864407a62346c66d08007e8410001a0e145100b3480010bc0d2020b3f3e3736401184d9010b468af8ac522807e440c503103044103ac890c4010ee0c3932223434c68d5207c40b06540328f033b0694816dc1c98b9a027b026b8255095a62ac08ac16b60aeb80af0156094caa3dea08aa104e3c6874d81cf646b56153e344c3dad4109c6159a719bb52a96c2a75b260fd6a45b9fb8c0f091ede88e167ed6ed44c753bb8cca489eeea7713c66fb868f3511ede54096f4c772fc1c31ba1f006c8696fcb1f7bd83f2e4d8d9aafcddfe60fe9a537bd38f2aff9bb57bbc971930a6fbe30070f68b4bfa126c6f13ef645883cac089147cd1bae511145118692440581f92928ccd1e43a15d114ef7d753be9d60e7ed66b88a3080e186a6e8dcbff9dd1d738971f836132dd658d63e646185ea278dfd737c494280e6f20e0c696c7ed1b8acfb7e5c7a479ea4fb32dbd5baf60ab1dbc4b37d6e7b5b983eeb1540adc7d04f7930dace0a181bb909e37f269ba234f10220448c88f101f2141840011f243880f213d42788408010202fa01f2010a020404e807900fa01e201e20213f403f3f3f3e3f417e80fcfcf8f1f1d3f3c3f323c407c8e7c7c7c727880f109f1f3e3e7c7a7c787c8404010af213c4274890204082fc08e223484f109e2042800001f901e20324081020407e00f101a407080f10213f807efcfcf0f911e407901f3f7ef8f8d1f383e787101f403e7e7cf8f808e203888f1f3e7cf8e8f1c1e343480f50cf4f8f4f4f901e203d3f7a7cf4f4f4f0f408e101e2f9e1f1e109c20384e7078f0f9e1e1e1e1e261e77b38608742ae6762815771767726660b0197e86999c1919fe735e86ffff77771d77146235aebccca8c611f797853544a8910a69f8c05f36f3b26dc3e7ff0c6de36e66fabf6df8a4611cb96fae88ab4811773f21364548a308f7d7e6ded5feb5f9283e65ba4477b9f6b6fc7b97dfdd6126b8cfc87edf2fb11269a25747630a0c321a3c7ed6eedee568dcf09795b8f6bdf2e2f4bc7be724cd698a8497d33407d97049d6ae10209b2edc5d1477ce66f34468c3c429277632c2dd713cb4610a6d807c89eeb2ed759e46c5e7c7e7f88081f6f886def4316e3f6a3e36cfdf7a977bd946cd199359de74966e3dcbe5d466b1a0f864ba391e1b6a16dd221474441ef10240f44eafce02ee264ce131614aed8d3c151e186cbb5a15ee0e000f4324cf4205770b867d813dad8ffd31c408307ee2410bdc8b18a2c96b828e78cd818bbb09a1fb37c6dd4b16aca07281d15c447b349b5bf72406eb6d5d0ecfb429da76351714511ca93439c556be577b5c9a4b66f979ffd6e5f0deb7ce3420d6e3f344887c0fec89fca3c0802716a08019dc5df490a6cc4e9be9ad0958538321308052d1050f05431f0f0ea043054048f1021c0510f95220e6470e0320b1c3142390ef4b0f37247662182ac3879caf090cb8be38f241b1029d1e9f0e4d434c62b0e2841638f0f87450c01e05b52161c46c7c5164101e110425906819a0f50d392264020633f8a12756c1efc38c0046988016b2e488f1a17478a20730247004891d61d81a5460a949132798180a6d60c304e1882b2860c28921225f5809744181088ea6bc00024370618f700197087041c4083c3231b6862270bcb003195c475d64616d597c088e20c114a626acb04db488b1831c3a788d2ea8c2f64c800b0c1c21cde0022190c2e2e8c11546a44b1491c41428aa184d1ca101202a10b203094cd416b020491216280097d90b90a81d20c009524c0102115c20204485028b20471cc062082a4576a840e400430e40c07004073490a5f2a440fa4289de0a6e7857ea141dac9864906206251c760d42406e89021801064f85b39a90828a8372878e071a9495060827664e02706420658aebc00a2a21fc20a84a084620f916377440d5c38e27b00adc2de085051868072ca2e832e408b80189801e315604c10422ae258927269490c3881712209e2bc10880987862064db81c3ff281061f219081065374ee405f8808810c1e48dd8080bb0835a42084a98c10ab2cee5f144c35e00014b5561963c68869222c2304481c11a30c0888600239a4b84007ce09a38c02ec504516ab6ca4250b1b9441431530509540d8eca8c1176590630420023ab8c1c1cce0a20ca205f4e85100530341023165885f4c09ad38018521a82ccab05180145c70451819b0406105193f00811712a2e09e1e09544146170350912983819a1b4a5290610505867441f8d0e5082da0204301651430d342cb9e410826c8f021015d582089296030850f4890b1bbf801af872e4683058420e385218a70d8e1072a4a88b203192978a08a32684032c07394850c1202509b9dce2b2ae870858c990844208a2e0b28a3698a3d06183b7041992058b0811c94708e5182259eb049a199a5a007e51854a8008ab3c70cb526b24c19834b104a4984a00224786008a43170e0c00a864cd841021050d1640ca52c5888e9620457a43064680ca30700dd8270429007f440648c206000992922c70950c000c818385c72d00204446080883072c6f0136c1c3e40820db20ef41023065b86a041065a52c4131e627c4003a1103e4e82c0c28f0d311c6003111a3d2a1208410b2d3186604016443f1c613aca95018a21030d9078f10080031d3e208618d70055a0f0218a0478200261889184042b4c81f54431e2061b8891c3439811a2d882450c225f88617301167e00635432418b8b20f4a00c9e03c4b840155c3c1113042e9ed09191a303465c01c6dd9d8c2c82908118ecd0e22bb2034917eeee34332a938a6f68c1bd2ef570af555aac3ac3042bcbdb8bc1702eeb9936c518acb6d3b3122b9118478a1bbdb53a103260e508894809d1d04c09d1d14ccae8e48b18f9180cc5a768dbd578a76ec9f50e357de7ee3b1ea276709e37f21bbd39b1d4a988e6db4dcb9dde0def9b6ebd43e2d2c73e6da7231fccb800230461a037e0a2898b0ef8204a15301de01f8091238b28fa0b5906d20c2241247821841b3e3da2b8c2e6a5840754414b40802d6ab45001d529b1c0e80a1a377602b0050a92482f408200c0508478a5ba2d9ac108257c09f88145471520f092022b402e5424d082073600c0fe62a7e606d0185f17055889043052fde6c93783147e10460a9c20e20628d00d4a0c248d501dc4814e6e84010728c0a8010039e82067c09d17440867d540035e7b74dc45c8d2e41fcd11f7a2774f8571772c6c51137777797822da379fb659cf2c77723b14a799e4f92246feee1397f865fa6a7797e2219801af99e74e73ed86205217772624dc79b6b87b16f7cde49e4e39ddd12920cddd5f2188e4feaab9eb702772076dc0ef73bfe15ec3dd69b887ee5ee3ee3380883d3a1aefde79dedc51ce4829b6d9764155f78287d60b5c6612973c62109147072888aeedd87a787882f4dc5acd878ff2a7c7e6a3934d51e37d5352eca55b9b379ceee45b7ac99d1497a7b9abede05cc6edb83b0d1220db8e92cb69127767b90a674bdfe6a9d34fdb9786c3c3eac5dd69786881dccfbb9473383d95a076c0ddab87f50a77cfe161f581bbdbf0b0bec0bd08132e5133c51a35752a9639b743e2a5d86b13f6283e9fc8e3da97e72ee3f4c7dcdd877b45dd5d8787358bbbeb5424d19e9ec1bbf32e3d2d4d91d01ebd8fe6a7c560ba9bf2d09d4cb6cf9dc650685fe2f3bca4bbbfbbadb9fb8d87f5752aee9c6de5ee2a4f739ae247ef97184d771ba71bc5397cea72e66ff3a6fb6fa8b9f7cda5d9f6b7005e8f70f71d1ebacedd693cf424dcfd86873e80129fe6b5bdc6bb57fbb7e547b3b9eff7d29cc3399d8a1687bbf3f0d061ee6ee3a10fb93b2f9fb6142fe5ef6d93bcba4bea5424f1d28c77d31d32d53b643ecfbbd34b6f5a334f774fb93bcadd4fad0f74773be304172cffd2d434fa37f3041996ca634110234646ad2e2d5ac02c5ac0570eaaefe7ca151aec7f4f437df0af448194fa99bf9245cb079d90507f7a1a56ffba92c3079d5e576858bd4e8f7ad1807ad54bcb077d9f7ad1807a9d5edffbd380aa282b3686b5f5fb98b0bc0acbfee95465b1a106fb16ac0f825f8d00cbb1b894ea2ba1aa7ce30a0441d0053ef8bd4af5f54bfa6a7df0fbae24d547815796ecabf6f7aa577daa2b29d56bd32ca949a9ff9450ab97120afc93eac1d7a7b2af847a3968df5fa99ebcb0bcd624d5afdeb258be7a3fbdd20d3eeab5648f7ca0abcacda8847a1f675e55ae54b91951affd2da9a3eaabaf2a57c6d4eb747af015657a496d962f80192de599d1d75d9c6aa169b3c418d51aa7b79cea1ecde8a805e601e0c2fd16002f017821003c8ed3dd46f1d289750ee3764e89f1466f7a77b7df19cdc191bb2374a7cd1ccaf80eefaabc367360c25c4ab17e8ce2de69a637cdba1cde3908e5a072176fa8d94bb70e4ebf57dbd996ceac715aa277df4b6eb344efd5ed705881fbed1ae1dbd5e1dbd5612be6914c86840393a77829cf7080e2e9124e75d88aa96fd7c8881322453adde51aee666274d62b2f4eb7bea71123d88a79f439d88a79a4bb7809111f3fa2e48d127d0ed04e77310e5b318feebe43e5c54bca8b8b96cc7af7c4e952d6cd986ece0812918d8f304e9182b2a8442742526e00e3862f4e65ca3b11927283016e38dda94cf99cd27c18a688657a79282f67273219122fa3378c58df50f3869a387d979ce715b2210c9a712e6b1bb8b868834e44b10d47dcdd867cf26ad8c2c534bf8fc7b51a9adcc71a7886a4b84c73d318f78c8e4d45b8d8bbbc1497e82ee7735113cec58fe134e760df43ef36a189c779d8f6396f2c5eb8d77e9b803e162ddf3bd12746a7c626969bf3bc42348c1169586267f466eaadb199e6194e5fdc0c657a66e599711aa7b83d9b010bc7337099cdc0e4e63e77b999be3823e3799772aad3dd341b218273265279e65c4e6d3c8ce6ad67338c535b182fe215da11b1bcf262897917b765f0c25d8c5d191a20034de7f3f72ee70c21795e3e71edf7ae26c34dde64ac7c41cd9c152dd70acd734ebf57fb8dada850b327a6d6d3a107a4ebeddd2dbd2f966826b1662a23c64abc615f3271a9d570f939341a13ce5dc438b531bd184bb3b97471b8a6cddca7d9c60b93e27dcfad61af6fc8f3c263ea9de7ce4674de9d9168bc25ee222d89158c531bcf71f9e735378a97b07017699ae93ac1b98b2e1581bb99b81a0b4b46ee22cd4879e62548446c4fd06ba69705da9767c62d85dabc2dfdb81b93536c65d7c5ef6b349bdb2ccd5d15f3fc5d93bb8877282f9f3b20b1dce4c5a5894d9b59e6dc4ea583535dd02e81bbfb799a4a3b4818a7b6190fa3431bc5b911a766698408895990c990743ba59c9bec23fb468f71f7999ec2f7c5e56ca69b765a9bb3d9d6bba4f26aa3da6c63ad612e52a1a54038ef4c87b1d20c4a490445a3595f9c2a91b94969a2d84a4e6d254e6dfa76cf2437dd4c48f4ed9eb6d406f737a2653224224c39485fa61ce4d3b33bcf4b9271c76d9357058bf3bc332eee5598964c5cceaa18dd2a3c3872df5a15550f2b41d16d31445416e9b4b88b39f3b509dbfcac3f072f99e76facbf8799e87062d6017177715f93cc5a5fddc34a9f8b740f3391c9906c39a73a5ddec2dd7b5869b6b1ce5c3216f3a33dc836948bf330fada447369163212e3e1f1f909627ba2777a8d3e67c4291ae6c2b8e78ac8e95cccd9b897b8779e62dccf254e28a78b354ca23897cba4d217b134cb9df334794a4c4a40ca9b536a255d2089c95d8cf54e9d2e63f34caf8eedc4fa3c4d5e1290ce49eea2364d2c4af49a5bdc455e46cd9a8b38b74d5eec73609a87511c0f5cdc24b6d9f063a5bb4d1c4e8abb8bb8d0dd71edf6455f1e5279e6db16b1bcf9bc49b9c15c44b295b185b1f56ea8a9cdcd83bdadf6a8f94844d29cc4b6e3ee1ac7c0ddfdbc3b6fdc347a887f44a36f52b2a0dc1213cf7c4f8c3e11f1be8f7729059339d5e697efee9ecb413c24bdd82922491c5923e32ee2c8f4625dc3c25d4c917858dbda3cb3c6e9d55ba748b59a4a3c73be53f8ad8295ee3efa9dcbab7466a3c7b87d63b42ddcc5cd84d18ca2e6f3f0d18bbdf373ce13ebcb7bcca3797117695a441a4d9b34203f75656658ccb4cc8c66b01c7a33cb18ac7017b5b943cd8d667233211961c2a816e76134065a0c412e0f2986eaee62799667a642021753ccc3686f671ccfdd651e52e1c1451a4e334943cd1b8db6ef631c121523f1bca899cb2815176928c6f16829bea1f9fc2961dc457df7142e8f5e52e74c4e619a72a3b3a3a4ef7e9ce6dca7f9319a3fef110632230c5c60c031f1108610ca0f0f5f08e3be6ff9c27e01002e608c7eefe6943cefe682961b6adaf2bb407301a674ee82de96a578f15d44257da5e8c4129f2726d13b4a81e9a0196d81cca7f7dc91e6d8c2177711dddd6ea849b6c0452c51fcb9765f9b630b52ce9c8dcaf2b6f0a9cec4e914a3bc272c8c71e76116b4b86f33bde74e6b1c79f1eb1b6aea74f7ccbc9dc91f43168cfcbe2d4729b3519ceb5ddd794933ca14ee62695e5d0c35bf77a2f961463c8ca273d4cce5ccd7e6cd6994253c8c8e5152a430ee48e65dbaff19c5a9908748524a143fb6dd4f3312eedda3303b3cdae2aeb1be484740eeeeb5abd15d69e625285becd23493af4db8a70e0a93fbc69b34d3adff7e9a6d38857d120fa1c0fcccd9088abb3bc9845f9bffbfc21747c715b6b8bbd83b6fa8b9024ecce16dfe631c52b80213a3322e92e62d9ff7f5fd231e1a79116fa8896ed4cc3a2326f7dfb8f73ba346372e626d428951f36e349337d454d237f78f71484fcc53a7bb45687d75bdd4d431e12734bdb5c9847fe7a227404f542ef6769117628971e47d7cdea22a448c66a534172d51d4049f576f268c3ece6512f6452138b9c2c5126332a73abdc36d949751a58f6ddc836533c5bad7263eefd1e3340bbdde658cde8ddead51a2c738a43387b7a9996eeec4282fcf7818358244c4f6a44c3389a1a018c7db79c9d4e13493f8dc9518471e95379f4a69e69544504a9348495f24257d7792de36cf8b9ab630f89c6dac9b78d182ee9ad0b6b9d3ab6bc2e3bd2661ce3a2663d21dc9040b17a9d0987061c2e4cec4a897e6ad4d1c13d86ea64299990a6154e82252a16db3c42939c3fae2c874a753a1490529e45581c7ddcfab672ae8725a12b10097bb33eb72aa9b299dbbdaac774f0c05939844ef52798dccced3aca5d84897dbd566e7e561b444f10c35cb8bcfad53d356e6193eaf9eed5d3e4fac6768366f38c569162acfac671aa7999c9d179766695e5d89e21cdebaa7b719a4a4af139c66a15dcd4c379ac919de77c94c75467438672261dcbb3a4ca2f7bc419834539ddeedbb8f84d06c9eb797e67d4bf4e690ce9b4b339a6d28d1904c86a4c40b39864a9adcdd95d07497372a0172acd35ddceba0f9d1274a6edcfd96ba1d0d979994d1929071777d7949c2f8a363121dd617a7242e79f93c833c4c5263888748b6707731cd4f4bb3d0795ed24442de7433e1cffb9320e9e2ee222e33b9cd5c0d89d12ddde9fe869ab8f6b99c7e4eb1151d344412a278c806435d441e464bf4a63b1263dec6b527b14ee72e9a8287433417f13675d6e532f93a68464b8c4b933477474345dcff739111bc3b2f0b39bccd1e6662a48799f4b0122e33a97b58a9b6d3425bb84885a6f1edea66ba8b71b38db5d0142e52a1096d61326d38d50919b90b7d50190fc1c3a030e7692a057599ed2026910a4dcff62ee3b6c99b6dac8396cc94d29bc369186483cb9bde74bb5989ad9869c6eddd9df54e9deee2f45d229321e132934690747923d9285ec2585f9c11d617a7c3249a51d2b4d976faee3b74f72d37bacb42360c45a7374582493417c964489777d312b74dde6c9b491bbd3c23b1cff1f103e72e8a73179de17357e65c9a5173869ab96c4389b20d3567256a2acd6644454a69e66d8ca639e9e666b8cc42b8d6bb4e54c0b7f46e23b844b1de283675268953bc64deb6ceb9d9be33cd7473256a32c1bade9e21c1676d9787cef3921b2b91e6c6564c3d9b31dd1c109f1dd3cde9349b29de285112739f3b273992c9c82edd1a25224262167ef858229223a5c8644096508cf7dd1aa72f6eb6519c5eddd0d6f866967ba7c3e9bb64e79cced025292e2fc619414284c42c68d2d4a8996e266c04684784c42c50e1f15132e112251aa9f8f8f143807630b83fc0dd691e16998197a7b9e3e9117de8641b6aeae0f392bac4e8cdd4fae2f6357a22936d8df73d65b21cc9847399dcbb9cde50b37775e7dd6df2c6604472a4148db16deb4cfa04f1b104a72f0ea7ef921c29a53467b319d3cd9de7258176335d8e9492e634c5255169ce88f8400428a3b41956ba9bf637d454babdf20e1143888743ba0c8132c4cf5d8937be95bbdc2e6732dddc5fdcfe14ef8bd3db6bf36557c87462ef3c7736ad314996fae2803c948520444c0c4d730df6dbd448223a0ab109097d5fa026770732729c3651d3f6681e8142f8c1c2c5f3bce4fdd1e2e585f263a4bbe97e14e7cc7b92b97ce4ee4a3cf4e9810f17f7dd49a2a6cee4eb6de6306e9b6836517314b7f9254e339a713cbdd317e7837347efdb32121f180fa39feef2e7bd6f7a3373416810448820475ca4c53ea724fa1c180da7bb7dd19ba9fffe0d35972e8e4477b65d3984769e5788f6f7f346cd477738725f9d36cb256d069901648ae7e5d79777bfbcb811c8520804e62ee69e34f7cde5bdbbe87ddc6d9753dcc6ddcc12e37eece047123f96b88b68d6edee97e8ade5fd399f93e27dcfd738974f3cf441e64b14fba880bb47f1d087cd47e8e2dee52fd3fc39785fd23c75bbf2afb9cdcf49f112466fa88923f7cd5d5cfbdecdbd7857e809e3ee2c78d863a5c7049e13f09cc0dd8f3ce4c912f2f0b858e2126314d73ee75b2da7bad7268a9752bcd18ba67829dffebc3bf43a79b1c43a5d4ecfd3e4f5ce9aa975d63dc66dbdcb29bea1b7fc12bd1fcbfbd36ca6baf2e2f486de47cd87a1bbd7e66b1387249638c5af51f3e6300ee9c5b277ea5d369770aafbf4b589dbdfab3dbedd346f8d9a5f62f29e3a5ce225139338fd5e8a7755bec49bc43a26243b5bc46839524a89e223bdc3db966137c5bcd71827d9a140b8a373519bf8865e1ca977af7579eb5be2adf1ae0a4e710b3b462e6e14eb7107266e21c69c0849d138d531e16d8a3711c3442e252671fae3a14873b1c43bc54b252ecda59cea9e4613558e7369ded7dc266ae24813c7fbd2bcba5d159c22e98411d1fc5a89cce77e7d71a10e530fbd9f66dbcdc4295ecada7c74a3bb4c92f751bc64de1ee390748c5cc41b9fba5c2651d38990943f2f999be8c0dcc52739633ce78be3944433a9836634a70871a3586ba41c2347818c1299cf3f4f73ebc7e92d312649f492e86e89872870114bbc71aa4bf146cdc7b5cffbf1592bb1de3ded310e0985008428f0b8bbb8cdcfdb96bf77799fd1bfafcdc7a9de5d12d7be779e37c528be7d0e3e6b17475e9ca2bbd79757e2d4d4b87c8c430a4ff062f7b99cdafeefeb8bfb6d3e265153e33c3cc18b8b29dea769d368361f8a87271879891fc5b8f678e9e25e7f0fbdb9cf19c9dd9d78182be3369cde93c4e5e79d661c895e1d9c16f130e6c5ddc5bb6ff937d44c33f918b795c87c8e616c8abb889a17476e9d7584c2d80d16ee35f2eaf4dbf22b91f7fcfb5a77712936124b14eb47cd8fdd5073ef722a62d1843dde696de25cd6af2fef718a7148214c27a239c5ba9c71dae47defe2366e9fe7853dcacb8f7148210cc8ddc512a3662ec51b978f6bbf334afe6b131ee390dc7d050f4d4881c6d8f61bc529b6d9ccf271dbe4bd7ebc730ea7bfd3acd3edf4ef8c7e2e3fe751a21297468cc460449872d036736693991123e7798570cedce80e8a4c66e466dad02cf4fa3e12112239520abe5dad71ce8891b1858dbb99983763bab9ad719923a530e5a05e7a4dd2b46114d77cfcd8e5f0366718dbc212b6ec73776db82c4157a2589770a4849b9d661d3ef126618ca358a7779a842f371e92c045a442c37b4762264c028e0420f71cc944822aec1146a442d31b6b7d6729b669f4a67b869752ac713a2bf13673e619f608c2dd5da4425b9ae19a9992bab6d3e86e86c934db64b28dcff31ad160788c8f528cf232908f9edd467739778d68a6ebe47362403e7a769f9362d3666e1497444230222466a1c425bacb413ac5b99d4633996225329f67ce35bc646ef2ceb499e2120ad668d6699cee6b2406b4fb1ca01dac6733774651f366a639c5b5dcae4889cc67d0de659c33b791262546b3a953cc0291149b29de55990538bd24fae477644cbf2d8b5468b627286ec2749dd060f85ead77eaf3e6a4a60e978f7739df7e7c71a358e74c7cbbb9e7c574d08cc2b43904c5280a52998bf0ed1ae522283b17d9d8d009d9d85862a24a506c6a78d143efdf7447e2749b35b8b87befd2683635ce5e794f9cc669129b1a4464a91cd4086d5237363450e0b848a7226ea631b6c96439dc86860a363486d0987177b1f7328c6de92ee8c597a5394d314d264b33ad8799e84ca4750b78ebdb44df7d87d29c4429e720bd82ee61269b091745b109a3b009a7b8ff545cbd29b1a921e3ee3b9f486c6aacd066ce6535575c2967dc8ccc3adb763ef5c569897755dc1dc86d66c02e2e65e0ee363469ba2b79f9b4bdac49b6a136343436ad226c5a5adc6bb06919ed0059da21536d737755c525aef2438e8a9e1021091545516126657492d128473a26ddb8f1518af191de9dba747704a5b70bbaa16679e6257833218132945e7ce65368d4e5d22528d63d11e2e1225d2e5d8234e4634926cb91526c667e6c6672dc73249383516c5654d8ac6a50e6584e0f2bc16c5655dc7f5fa567c2688e64b25179e17e4b2f4e284d91283d4182348018dcdd749b9416152ca7d8ca797edeafb7597b9b89d33a3a15331944f4117fe8e890daccedec90daccc9641a5504ea8a0dea8aabc70565859e6a51b58295550203b815f72a5cd89c409b938f1791b8cc68f5012a42648857991020f7fa537dbac4604af9afc4669fa361255cf91c580f2bcd7ab5d9ac975ed4ac891a3541191429f2e98eb4f9c4f80d3587dcd0b7f9906cbe232308e3fe3270872136193d7d36b68bbb8d8d8dc17d6774a3f868684c22de7dcbc7f888f6b6acc91f87306e672129bc12bd3c2327516ae526af2dcac65a7c3193a8a9d3f492af4dbc74f597e82d6f3e6ffab913a719a77517d5b56b9eb5bbaf7813f1e7e420d56a57f7f8bc2f6eac71db7c8d5e9c3b6f8a7318a733737fc95d99532a341a0c5fa2f76a333f3abe48c356cc14be8517b199ee1225d1bcb1a642a3c1304333ede293dc28de55c11acde4e74b9ae82e97cbe4eb4cf230bacff334a920197a222493e91e569acdce13f36432994ca761a0028399f2ced3cc799d7cdbbdce59bf1e5fccd11b356fba49acd338e56134ca8ba7d661ac4485a694f312bdd328d66816a2c1f058ebd4f2d6ba4c33ef51221d26c97b4b14931a97dfe4cb9d8bc69d8b6e6650ce48bda0121be927b31d48e1eeae149e369b6d7cab07a841dc1d04e2fe75e5eaa578093f6abe0ec294e8ce76840e02a0039abfac4477369c12d1418f0e68fc659b09e7fce7bc6cd6dbd564322d74434d1da5c4649a9320a1d84a4e9394514aac54a226a93114a2124a5089376a3a29314edff3a88512ef6ba6fbea3094281b1f6d9d9b6cbd1b2a312f972812a21ca4e0e27977a89933b7c68fe2ef9dbf73d18b2e8c66a59b4b7779e9dafebeedc43a0fbd0ca3e6f9b9bc770e7a31636d7ebafb8d6fbd9bd38f77b947f4e2511efabfafdb3d8a7559a6a4cd34dde59efe5ca67809f3f2921769259ee17447628d9a34fce82573728aad9827a427fc2ef7de46f4ae275090bcf045e312a487820469440245c923097aa4a3284e5e44b12e7f3070fd952b5fe22f62e4af5cf9184c9b22c6a9ade8c512cda91de91d8df6f735d6bb1c5a2fbdb834f50dcde77d1aed758eb6d9322e6fba73414fa3edb20b33fcdf7d873ea74c8d3ee78848cec41de9ae137cbb45feff65f834dbfe334aa3d1903c00fed4e52e13be5bef5d7e5bd6db44773308c10c6e104d30e165a539cbd1369c7e4ef10b779f7868a83cf3928b876ea859bb258a754c485022dd44a7a24e8e64dac9914c3b37d4bcddad65b21cc934c6c8fd65d98612e95404b22493c964ee7e3d94810d0e64e9f37e7ca6d99662dd794bdc2b2f86a2cb21349b37a1d2bc4639bc89d0dd0d07e5cc7b0a1d21c1e57502e5ccd9a8c42551898d4a9c96184a6904833130f8226acd749d885f128925ded908bf58e22fd3cbc3e9f9e979434dadefc6375bcee563de2ba599174377b0174dbdcd1c7c5ebd7159dbe9f3c4a436736fcbaf718fbc377cc6f4cd29e9bb61af5353c784e4bc3c8c2ec945e769ee2322349b4279282395689471e7a2dd93f2cc47689432c54b4f72510b42a599596042d23bd38b04ef9b974ca328ee164a89cfd364c2c345252eb3d0511730e8c2025d289de71532020a7274246e25d4c4385e46955eeced14eb72aafb5712a0205abf1e28c8d1117a7bd104252dd48644ac949750536f9d9abb54f75be3f2ccb0d761ac947b27df448b5aa64bf8379f8673f988f6224e8753dcb6a13ba36f333f067baccf9be4459a8f1f3bda6bf369b4dfb7dca5484fc3b98b9258a7692f6e9dcf8c7e264ddc7eda2e6d262d96437baceb6d8d53bccd73639b2d6b13bd3852e3257c9a27a9b37137f37c4d9a5b9ba4a91b89d8325efafbbdda6b9cea700afbbf4e9e66048988ce9517a7585f52ef926c5967eff2e7db7ea61cf4629a7338a7839a7f59d8f7b4a1e6636df2ae0c09d293a2175f4c71ce8b4b5ea4d18e3eb699709983bd7e51639cbe387d9de4dcf2febdd39d37869afac521a29dad445113b7735189cff30a0da199cc484419499718179158778473b788c4ba2322d40c4270e35e7f541fb8abbfa80436628280031b884181982cdc3de5a1180e88a9428c13629a10c384ebf2c74880fdcec748f8cf21e165bafc39243cec73f4538101979914838a418ac1f6fd9e2ea71bc5b39b6ed49461a414a7d8ca0ccd280996acc2117881cf5a0fcda1c5981138616ad4fcaf091100b9fb967927a1e6ebdee59d57e76e7a753f3f7ef498baa7455003112c89c09653fc029a8bcef386a1e1facc59e7a723ee1f8f1b29b14e67a65bcb72faf288b8d799ba3aaf7b55b9bb2ae5eeee4edc95782d62a5c7c45bda6d9d56d16534a79464e26e2fb820a50516a2201d4159c1a8c84913262a2c519204c99050d0b823eae494514619376cd4a0615346b5652144deba5e4a2e6561e43a1567d84cb799ee9d0f9071473fcd36327771baf56333dd2fc3a2e903577ca0cbcbf0ce79190cefa30f0cf181268fc1ca2099ec869a6789de251ae3246ee4fee4035e12e1349355ae70011829eeeeeaa548454b7c9664b2d9d6a63e6be60ddd2529b112999b40c1682e3ada3943e15d28b3141b798009b12cef97f83c470f5cf10026d1ab93620fecf040e8eeff211640c06205eefe46fe6548901a80c502b0d8f2b2870016a5bb77712dd1811df866c23a37dd5dd79f17ebbf5076236ada22f0329d8a3b3b4a24c6913299de4cb80325b8fbcb4aaca3609c904ec59cbe3cf2da7476345e32b79933539d6e3321b9e9ee1a651b4a74748591e6b9e9ee5ef1ba87993c07c4b87b89f58e68367b590e7cc0831ef0200c1dec400739d0410ec8801184155270e0eee00dc20d9471f72d7c0355d036104446f7e76c409734da9727366ddacc6d40c90680dc3fe765be4242035cb84ec51b6aa65bf7cede3d75da1cfaebee3670f71ab83b0d66e0ee22b89f44d8e130ee6f7a75a71334ee73b609a8bebc4faf4e676000ae5331c54ae44d9196e8d94e269b91e8e895852f62b8eb94e635c2380992a8f3d6995b97336de6a9cb5dcca3d14a5d6e57fb9c7cf462d6e77dad778f794fa3d17e480c851ff2273c0a8fb16d06fb22af33f76ce373484c676e18795e7256e47fe333cd497e67e773b6dea5341aed459db9bf57fb9c98cedc9f51d85b31b54eef4cef69e6d05daab3d14bd2ce9b4bf5b923cf4ba25967eebf77d768a5369fd6db26ed5353e7fcd2993b35975e8ff74e97cb24edbc4f68d8f6e37f2c0bdce88c717795bb0cbcc4557e4889378a73b5e6eea887554c71f721f2e8e894b88a17959e5461c35d295740087771f34e8cbe4c4911511194b1850a34b97b055ce348bc7bdb4caf6e279749b1b6c36df3b6b323d3babd71bfefa7ba1f52e214df6e66ba7778c9d428b6e1f26f48c5170c80c2fd653aba1cc9b4a377a8e0b9fe5196680ed22115b25c49a47b58e9ba7b0c60408114b83bfe9e57f2daddbfc8359408a75908dbee918f1f51363e2acd21ddc34cf4d6b74979f1127cb435be11ed7c1ef17091e6e5a292284a690e193541893414a1f284a2cb9152f4d0d67989eea5782993da1cc24df4798e1a9bdb093e75668a9491caad4d243952ca4671ee868fd02825def786d3db6dd4293eef5169a6295e7aa2cb0d9550304ed24bf19279e2a1f14897e65089e620a11b6a6ef42ee11343d9bb27ba5cba64ebdb4428c5465a974b97949844771945c79dcf239cbe4b8c6c39a74678df7d8451a2a3149ba70ee3249b091795172f21ab38a9921bdafa36c165163212cae5ad77422de872e992bbef109acd17089598878ba4b881bb3bed310e490a0bb8bb98dba1f86934296e17b7f4f7b510d2795e21a6eb44fc346ff226b9a1a68f1fe56f13dd7d8a758ff50d127dfce861255a2cf6399f0383d9320a0f8466c5d47fdef29627c6a5d9763fcd36d296f5ad6914ebf2dbf2ef235e6e9233520b4cd749941b9ad31672d1ce4525bacb49762e42898e525ca64f6c4ff6ee490fbd4e72463ae2e124392315e5a2128f4e64b7b6d33b3ddd59e69ccb0b173a1553ac3b316e96e299363546f1cd052d70b1c4555e544ab34eb2629ed74cb1f6d2e42f2b7a72a482175bd8801c889ff76754c76bd8801a0694e12fb3e112e3f2267a91878bbe892d4990c1e9bb442623cd14a3f8a6d35e74e1e016078d70f7183c64008ef7f4f4883c628f0e9a75d20c9729c6fabc78979ee74e278a243ecd3820e18231a280edecd4767ac7f6b8fbcc4328a6d8f7662edd8a8b78183d1d31033a2527a78891221f632a4dd8b6e1263228e9936dc34dfeffd129323c5369cef0adc43325f2a63b8712b242a9fcf40330458cd8f0e98122445078149fb35979e66f0520172012291e5b975fa40899eada8d028eb83b2e332a43f1cbb820d1f33ab613eb2f52e4457cb33eb83b0c1e76c982ba804424341a8d424fe4994a53e7440ebc976e14efe86e6e07e374d776f499c91d1f3e42807c7a786c3f808290d7a7a7f603fbf8f8d87a4a5b8de727080f9010d2a7e7470816f2b3436ebd4b77c88de2a58de2a5d946f1526d37d3e54826ad67ba34330b32d96c363323674664b219ceddd49662a59c06f5b6799a3b8ade9dbbdc4e28474ac13bbc5b419723a5688d8f4e7c24a4b30d25d2e5ee36f23944d29ce43cafd0798564b219990ce766999c695cce505c96266e66eaee4ca76532db89b54c668434cfdace48a71b33924cd6e3f5fd9bee462237dddd2ab912352f4e28b743673323387d971880a7446f5a22414644628bbe48487117ff1f1d91f84f8ff0c275ba23b6e86ea83986475439e24574977e3679dabca5f7fc5a3e6b7f9aba5c268df0c25d2cb5998b8ce86284d1d6195700327a9b35139753dd17808b8be82efd9d59803417dd7d877028519402dcb897e9e5e976b312c57a96cb22bcf04d5e1417e180d9c65a29bdb914efcb9be972292ea8c43a0a6e9726af446f6d5662bd33525e8c1bb3922832d97967f9362b49234bca8b71b39b5e1d115f10d1838bb48ce3d1be57cbc1d8f6349db969b6fc39348c6d33da8f18db66e7d5fa9e425adf93c4d836c3d836b3cdb0be359db98d609c8408db986e6ec6c36889e6da105ee453cf86f0b2b11e42cb1052dcbd2cef10b38d3501cab8e7daac4712000bf79c234011face0870fa46cd195ebaf89ccd4a34d7ccd29c11e02627bd105eb8eb2984d8e2eea210383407097144f370511065eef76a414420089a8b1bebdecd3d79b3d441047103c41820a670206840c0dc1d2fe5dc0f5eb8bb889a3c1e2efaa10b6ef20393c9c20f4b5cdc42469bf018b787b67cd9b2c5dd7d0bcedd7d0bd096d045bcaf0f5ffcbcbb2af8b559fa5084a7183d774b28d1a33e1cd9f9d4e799c9abc3279a6bdf236f6996282f9f423e843d6ce1ee3d74f1598e64ea81a987a1194e5f5c0f2edef4ea6aa636720265c9a3230f6178e0e29edb699cf280a46dfb482623f1924c566e3dd3691e7e87277608c2dd757ad3c1a8697b9c66272fe25d95abcbdfdb1b9764c638a41d8434b9cb3d2f2f29b1decd76b8d1818c0e4be860a4c33700070cc0077717f5c58f6b680f1dbf443389d3bd82138d5e9cfb137a273eb33ee137d18bbd13e39d8b448c6b610051dc457cbbba01c06e5700387d714ff309e263e6032bddb346130016dec56f02d022801eae77b0a7a5d98816a9fda8d4aface5a11c069942ce08c00820000000731200203024128d874432b164d77410f20114000580be627c529c886194e314328c186300000000000000000110006407e2caaf031ba6634736565d9eaf727125db8d6258742d94fbe8064855b1de6f1eac49e287ebc0ca6c829b5b6a97b61b3f1ecacef8b17a64852dfaabaa48c366a4c2fe21d3fadaa2685f737317afe8a1cbe88a9b0f314ea6859c8c17ba8e30cfa2805557720680a991d7fd1c6a13b182f0bb669f5c05860f100d58e95a647cde5f8abe79e65caf443ae83bdfd90aafd8b1b5a3aa47b4ffb34b143468c197626f303e2e5abd3f1b291becce09bc42292109c488bfc5837f366673c3b430c71d2ebb960463819a945409d89987475ca92f306ec17801f328ac89b57f1faf8ee43ea75f59af68fa570515fa190003bf31ee1b62a73cb782dea5c0e3efab7349e692979f1fb3b9ff2d580c19b2c4439614772c4bd95cd6b62714ef657493dcc60ccd7e5f6d9f043f5f4d072624df28798e0c17ac95e146ef98221b551ad98815dc33b8b8ef1d5d292a65df9298a2c8a2a89b7932f344ae3e762aa6fb86f7a1bf8b32029f8ef768305c5e4618c89e70f70785b28ea32b873686a12907a49e54fd1c3a6ab284471e2606a4c530b190c87858baa485ce404af3eadc618e22595c14b1fdd1edd1af04a42f60d93f640b83a5f0a9b923c9e0a631be86639df42d2f201dede038624f3706fb4b36cb271ecf40d46fa74520ac4d7e832f21a78493d91395bf7df274139f4d0af3bb215a27a6148d6544b64d6c519ec5e10d8701dec82b7ad8c2fc0c3720c95a03d3d3a1de82a4d61d9c179ca6ce324ba6dd25d5aec54182cd071d2b807e174ef43e0c5261f56f384efeb3719f9cc1a700182a3ac6032dc2ceba4107c470a7af23621c1eef0625a4fe57643df9dd4ea01932edf83738ea80eb10f1f557c42ff0731c02e6ea01c353bdab132020ac074eaa41fef1cbe4e9dbc72fd3a5979cd94883f4a5ea7255746ae3db5d73ad9a4b699f82e7ab8c40f53b06a54f66c617cfcb501d04eaba3fbbe9a79f4aad5ddd83ad98c5f6a4b68272b7dcdc52e1a9ef6c82d08accd673694c6112b7b5a71f97dcdbb2fe9808a7f6a008ee552eb37f09670c4af6d98d3ce3bcbb0d94b13552dadc715e7e3a93f10c505b8623ae51e82a3f0ba6dd68128e5571d6f9dd6003f9e85cdec46724a31f431c832ae32604d05f83362ec6499aa6403b29bd933ad5005c9eb48688fa3048d338632187a2b91ccd57d271e8a00aa3ed5026c810917062913d99eb4e9fb92477949ea10f520b95071f7ec095a3254deeee3477ea918bdbe020a8236e974ba44b1ef95a1b198d5992a0d7b210639afd09dcfdfcdcc206c6febd22bd7e7d189ecd498e69a40dff71e4d3fa480cc479b52685634e9379ace687ed5385ae738260afbf15dae8e0833120e8636639cb7320820cb75d158e151b8ccbefb284162cbca3a8511c6ddc3f1a77ad087c1e06e6ca3f9168125bb5fd5f34143e3470023ff2a65ffa76f317dd5b8d25038c7023e7cb3da98fb42738e8d16306aa89c40ad71281f1fee8231df702c9e9b81e01a00d736a32088e91407c9520bdef5dbed5965268f5e706dea53c22242b1f88ea19e5a17ef3ba1e35abe9d0d7fd276711595decb6c535c691baea15f0a0542b33309721fad985f90c9607811e7c701446e109ef0228869f80b64d90fb7d092b739ac3ed5921f05d32ee9a94db5a0d34e954785ab7f8dcf06e497b90e6ffd3a4fed53e584d2e39be1397397e45224ea4ee1f3fb97110c791852e688140feaeb759cd9e6e4c9518a6cc313b848c2ea65f68f6a289fe88217ba04a430ed8d1dabc9ac16dc13b44d42bc4eb0187f6d7adf4fd943fb5b04f93d99b7dfee3d68c2a5101bd4e55507fef2f6f767af335bef360e51945caad42a1e11b87eb0b60736fd92c164a09c9f1f7b6935e5c9587bf494b39323e482c122708b02576bd4b27931875bcff50c0a832c1d6a97bdd7b7bf9e8894a82141c2b89dd4de7ef0a9597b2578ede724bd0cc38fc5c44a5e928b9a657d09b6a006c31da424a70959110cf93bc18eb0e76a020980300d8132d6bae1d38bccf8b24cc3909d988804dfeabddddbc04c0e965e33e3b97f52584a04d8b157f0b17612da09d8562a04d67c477b30972c4e38fb99734e7bad051ed1f9b19ab67c3f54ca8771d4925c3c84bb31d602b45c3a2f856227fc4ef766392f4ae2257ee582982114ded8ebb835f16818429f95c0ee25605285a1f66149a1f5a86e8f48ba1245b867eb87c2740d2238cc0489204e594349fd7a23f8500947f39591590087724453ba763bab0159f9dee7602183f3bdcaf0229a6e54099300babba0fb70ccb65c30da9b046296b8f99002c02439307acb842368e79e931a3c0b6f56b88818ae7151fe6383aaeb4a697d281119f4490e05be701d8191e9219412b46349df28cfc8a529dcb14dc9ec85d0333e46533b98c8d280e982bf0ddbcd5c6d94a20983ea58aeccb56bdd6dec1d259a7e6a871070b6ccc9e248d482e1c0c9bd0d7d466695f9a0022806b3b7adb34054cb03a679179cdf481246aa549ed47f4ae2a2a612e4578a2782f96ff401f5f1c538f9cca58affb4a705e43070ef77671f7f4f3e3ccf1cad001424074b780907119f68e5028a061bda5d03a7d77c623593224314b7ee2394259940185a20eac9859edc0161f442f98a0679638f8c1191106f6453358c95f1200c38f313e15f3f4ddb46d36c2d8c8495b06ec95c125bbef2eb3018306f2bbd0085aec18506ce598b7f36f86226debc3ac1a2541c6c64e74553f50da19629100b219f5b591ba95b914b260c8b145edc3cde32c5a520d84ced4d5994990a3e15bc4d9b765ef9625b01814c6f9b1db5865651d3fc602f735cee3cc1ac1650b22dc91a0f315c1937beb9f41f477e12bd96a1a85ee7af8ecbc8f412cdc1705b8892c095ecf0cbe14296a39e78513f06de6a541b251ea8bf702f9aa01bab145351f95782c750cd8a9bc9cab59d36db1007b2ced8d2de39e387524044e558664c5ea1170d6e0026ad1aa61509255ea61fbfeb50f841dd1a82905522e10d985a44cb718bdff56deccf72a3f222688a2b0597010a431c2bdb9b5ec540bb4d510cff9ac9ab331000148601a5e4e609d5033aeb7371bd5c5ed1b3368e499d43acac0444dd9225f011c1ffd0a8b9aaa6b6eafefb66d870ad85cba06d725e72e17d4f21d84a9cd6e38a9b4fa37d6f8b0225c8cbd8772f9dadcd6222dcae3aa0b41f1f0813fde01f6b42d33f3a832b9314c42b0b14da85a1b02309d37cebb3717505443e1349208e5a2610113615cc7f0312cb096c0ff78a7c0d25b9fd649fd90c3c941bc7537caf3cb6f443915fef25c78f4422226666a526fb47378bbf721b405c9f29e4c2d74cfa47bb06d7f01a0d77083ab37db4907ab044296d5f5bc944ec1dabb727c90312eda037657ebf60d6b502b932d656b299e987bc78b5f2f10f089f8733194843ebbdcb2d2cf769158b88deb005e8b6949887302e91573a12a0861a474fb36fa4e1ebd772d361eebe9b7794ec25a52e3b4b3d6a333fc6db4098fc4b4cee99fad061ab46530af220c98877876884a4a66cc1488e454c4ed5ce798fcc3e7f1242d16c232be8dadee82ddc0102ee61e35c6c12f13bc2386bb88ce16c7252638484108e37e36b3cda8b4fbf9748422ee946e52ec2008831b36068d3ee242ec2eb1eeec3d0c93fe5dec409b566763247a5d3dfa34f6ba3366339016232a1ec8d145ec1a8aaf769caf4684a727635c561a5e901a5f4967d40e39be9858c858397b20176771d8e3e18851be0d9be95b0e85a930e0b609dfd1b45272049bb3ae3716067cf8c803ba51c5bd81c4b6ed674add669314844836fb765607718290bdd854331491757dffb54bcdc85c598dbc84f17b0a836ffe3c5d5805312eb2cf0060556b1401e304dd5f44f63bbf036e77f6349cd46ce0cd5a055785a4084b8323e690014ee0dee9933df2772ebf48eb187e47a998c16efa79ae8295f96e3115234073e6987a77527286a02e8db556a3b62cb284973209ffd228ec70be782e88d7e151de25961e3ea21990deed12780c89fa7638aeab257f727c1a7744715b8e257bebdb98b26c5d4c60838a728e62a94d9a98959ee2c77bab6b19a61dc828a149d7e48bc73b408582910dad47f41b428bbea64c06fa79da63384c98f637e5d2182d4f90a289cb1efa06b4cd72e546526255547cff3262d7dc99a6827d574d17421b042a1e0ea5c895845f31f216c062e7407e15d967d4b6570ddb868d5502e5ca0b7b47b38a9b5b1e1dd2d6bbeced2f5718d08fbea9ed1fdf9709048faf583d00757888e76f9619e4117c235f143c598bf41a3a96133358d823ca1cfaadfd29238772a2b68622f8b93c4f6f64ff0f9e7978a3942c73199b66be3fbf5f42b87ecac610e8a13de2b4e62e6db7f3d9be1095637742d649f68bfbf0567eca032c8c04373709b1add181085c07d20f47e313035c43c7d436579d5af57f3a5555456e4c6148050b1a4aac5d25c3a74162eb054e1e0e9913e4348c3b6f21836b0ecd4e2ed8722247f8acd83508453e85dc338ce5d35f8a2d433eb2d4e288ab60b62f291686c047cd9b738857442f3923e92aae754aa1fae01a6980bd31cbf587cab95340211bc49b35649db9a3eccad6138aaf5ca979e5286a89986e4c51de27edffafd6bb623a8ee932c91dbe8289b7f9b5ca23833314ae7c975b04c87622b9ed8fd56220fdbbd03cd2c1d00f95dd4fd3e289a9ae52be864f1eb58816a00df43372c1299cb1fb91ebd940c00417599aab3c3633880e29757c1210b746dd20b8d28104b8b5512f9ff9b0ca5265aa1514823ff3f6dc10306033292dd0c9c73331a2d373c8272c04e8b64d352ccec4075d10b837191950e85a39d20bb28d50f59bab514b50faa5aa18c5854d3c411dfa5559b1a2d15cacf82b38da102479553f69e96bbf8178bf6973cd86b4f4a32a6528577640b1f0f2ea9b568e18257ce5743b3d0d2ca345fef43e78d72623a49e83965d458088c2224d8dcf833d2a453242f476fb9882786e12405f2b6aeb5ae8520889a51b4fa7afc79c0125481519da95b1bdcc9eb3a66164a10deb8d86aff31d7cab170001449301378d33ad786565993973131f592ed9cade581f6d14b9ae7018db3725b0c0423e9fcecb2ae8b8651d669f6e2b3bf30011cd65e2a710a711dc9af4522e8fcd8e168e33d354602ea68e25142343e0b49402617ae20616d2ffeadcc99b5c38f82b880dac2a9a72b0c4387863a3a34d1488451b4bf84fbb2f760fa077bfaa7f3b0638c863ed4016a88c9e28f6f4cebde257439b7518484148f662f45c9a319177cf0ffc4e94168a3f43e49c82074f63a811138b9aad95c32eefe4dbb3e1685b746ac75e145ed14fb98ea7d70a64f22501b6dc817ba70ccc7cfcd15eb2f1fda3f08c0f1307329a0c821d642168b40926d45035453c8067c048fd09cb61a630de9574b798b4f41cc438d5a3a62b76a6e633622e312a49f8f14b88794c6879660d48e4809c4274bd7870c8eceaf596035119be0df4956a77556054e803126babcef28b868a0188aa39b0448b1d836028b859537a75ffc6110baf7177be5cc0418a9068feb50be2d2163d12084ea9c94ef75e6d3f1b2991002d92af44a6404b96e1906e20b2a5c55facb9db4e7f51009caa89a28a58412fa9b232a6069e0d1fecd79ee4f138ef48166704a89c056a007a705533fe6cecbd98a808c1fd371bb020e5c7644da226eef725c611ab783ab29d189cfa66671248b0ff8fe8f053c5c02ff3c48c689ad7893d9e87a5a4c2cf19d189571b03c9da32d0e6274f30925bcfe3688163e28db85fc06bd2677189c643192ef3e51a0c73ede5cb169b5215788119452881271901a10ebf4c4c12ce4a0a62bcd3dde7839ff93bc20eb23bb24645439cf130d399588ad7ec5bc2db5a2ef7ce2e4bddd7262de2477cd58ea50127dd6c2ee8c88374662a75432f1f549fef6912d3de79a11cedf5b48ad878e3ac1490593649438f4ccfd414cc4c1787c34baf134cab0a11c96836ef7be67c5a9b4f4c06aa23ed75125fe5cd506c8225b980d645016aeeb83eeaa84d02fa2bf5559c8386b99045d35cf301d4fd88499450d8dc73d4a440c0ef4500fea80e81064f392253cb6c11a89b3386112e70db68bb15980de23136061392f41d2520c48b5b1d593871f74f5936ba801996a34faf1d276c51b45d17063583e7e60ae0c5bf97b50573d14b93146a31b214ee55fa7514d0919f46440ec8208fdefaea0000d9d3a49069e9508c7390cb6a0a181fe1a2aa519367704a9cfc987fbd25794e04b3cd7a63debde0c4faf1d360348dff6797835cfd269b66bdd4be98fea721df16d11ac9e8bbd7070cee7bea8ef1c60586dad4dd30d91d6e3b5d4bba980987af72b40b971f54e3f1dd9fc6a4af7da6925ffa916d2b6093341a45573b44f8b55dac3c64dad150ffbad45ef8bc7e63bf7df00d7141066785da4e67e4eafb1e15f4fde9319f99c86b70d18188ecbbb587ef51ab5135a221b27e95a4d937a832dd40cdef13426f2e7ffd09be5fad4dc9a8965a624774af96667c183cce84ae9d88a1ed5713226f07e889a52c88c2d8f7d742291d2c80ad74b5de371e74bcaa4b47d7d5120762e620fb466e2c434982935a483c572d7bec392ba73dc4837881a93f7e220cdcfad8630ef84e903f3b2104fe7ec3b494f2e98673401f614211bd094649110e38b2a081f960fd33614e50ef84080d29e07523565ae9bf1b6cb9c0d8de8026ed4f18a812d40550ae685c7e292d7a649dcd4b6bb7bb8c763f7f4d8ad85b4875cfb11662dcf6aed0cce86229e0c538f88a72996cd4828b3145ad7d61130f2e40d4d83bec8b7f8fd4eb796b36c085bab346e3f17b704a5a70a4839d194cfac3ef4eb67cca27592f823beae1398e2bae5d3c70385e1ae208b66b8c93b6c6b03549686b488452bf40c55bfd10447e60e54e404b5d183a9079cf84bf18c7793b270b1507e8d426e0274aba20da3c5596219dfd64b162214b06701a0d44f3a1715360de7fde8e6211fc3febd22984abfbe3b5f2fcbe48282ef62d273c754f9df09359f288874a8c2b788d49093afe8247926952b46de86ce593ee9ed9f6f8bf389bd94f37918f22fbadc8e94e415839d75243138139e77fdc3f6bdca8d1ad589bed6a11049793d7c5863c3a55445c2e59a1732a765cd2c180092c8e903f12cd8237395ad6133036d35bd7039d4be1381f4b59914dfb9da543f0dfa2db4e558cd9e035059c03c9fb746ebeecb6f14b48df879aed2a0e8a5a9e4d07669a9eee3d358d0b68bbe2f8ed91bd03db8fbffe869586f7fc12f2737c7377956ce9c5b7602c8da18d0f01fbac6410f24645d80c791c8939dbc64213f78b1ff2f12db4e41df0367adfceaab5ce22cda3f601fe7516a29027f49937df86cd578d9a5002dc8d5d976704cce1dc22ff55ca1039e518380445d6fd996b8c22636eaa8dc7274fa0901a3651d5eae18b3219c88101d913ab986c823b7e682447a4e7523a02a4e13ed10e068421446faec6bb26aea908a2d40625aa8120858394aa20983a83de38bc8d013577710daef6eb1f6e415118b117dcee0d38495f0bab6671b08ddd7e800d328ce8d272fac236eb168cb89c93177ddccef51edec077f34168178dae2422c2200b890b3d983298b11bcb3071ae02e1f960f075e27c03f9f1ebda58cdf87b7035981363f713a1341ef16091cb06f1f6df5dba3ceba095f2e301f1684cd022831928ff719e2d60d3246f52a5cd0b66a460192a132e7f57ba808990fda86789b1fde6602b393bbfe20219680f5c453248513621da0410613c1fe80aab56a024a1d39869875aa3e52f17fae8b97bfb1e491c1321c5c88abd046908ff8d920182a26fb4d90f414e27228a9c5938dcaf47ee2d3122cfecbdc12beed13c5e6005cdd46ee3f10915d005b38c0289522ef4cf42ec6fe4282f0d71a26d9a6f09e85fdc2954618afa579a3c217f6fb73192b4add011288987c3d778a90c8a96e0c52210964eb31cab192a262a8e9d0618d621885fdc26800e8e31185b724ab3ab0665ab0b2b62dc17e97adb4660ef325a67fe0c4dfa18ed38c353d8e919251140cb62a68a0418d64d5c8846cfcd1916de30013263ea701c9f0325c492c0291e5df55f750c81f57ca159bc1cdf8623d41e0a600754f60d62ad32d7fbd84ec1f749f91e9235b4bcf759d76324991ffeea6648e4113131c5b068ef8e602f53c842ee28912787fb10efe73b673bb26c18464ee47ffc01761aa2757bee8fd218a1918f8994462a5ffa444cf061322f78969f0f606f6aef921f1784ac9ef54621807c76e7976693657d504c89de63e1b10ed3fc0f3054850a58c9024913dfeb29a20d4227e05a8dcef1d6bcbdb6c552fffb27a7e25fcb0c9277103cf03bd0f8a99817a0253772a4e393b0078e81af9241982a40c7647f4b7296f1d117428e247c3df1d394030b548fca5bc51e9909b3a4d3c81618a6f9ac02a9c10cf08b0423f9009f8e3b575e954c9f689a7903e89658d0659fe82cf1b53c78c73a3aabf7b44f1e648aa1c3f71548f77e7eef9ebebfcf68fc9a10471c90cf3df56f55b5c948688b897f0aa8a26d3cbf1f50a7a0b0000a91831e413dc148c74ec0af135b7f845933e0ea00856dfad0b5e1d3fc240bb98000b8ca20539d3ff7be2ed069b660c9117d288e226ec0623b0033212a365f21c4dee2b9e79aa03f4f0ebff6878112770a0fde1173ec2cb6d9fbaf4fdf5c24e52ec1523a1152b643943e268c940e053d0d7ca0c5838425a35c39206f0fd98ee101a5482541a0789a6ed4a82e0d2877ce8500baf95b3e802ae71b69dc4b971a2fa3fe7dbbdbe60e68143110d912928e8a678ebd0f5a76f564ba486d5cacfdc066bb38424db58ce1ad71e2eb9a33a2ca674aaabb903712568722fabc9216c4f22ac1893ab41d617277a969d51f236408990a7eac066fe52824e312c5039dfc19221cf0c3bf549a67d377c502ff38339587693f61ca77eb65103a43dacce003cebce6334e5dd583a2e22c67e15053c54ebcc4c4c8440a2a194f2f8f94afabde8bb23112d7eef08b8b132b8d0191cc5904adbc7057ea1686aede73bfc315d1022092a15aa6ba6566e496e56e464eec2950304cc15c28438d3543160cb505f31c8131aaea642f2664060cf39953a2c08fbc88d0b240aeeb77d464384004320363a9d41c0cb11ae207b3f11c3967cb8f561dfb138d72b31013d0b7fe2489b38ff51e77d17bed0daba5a9015b1bddaf42779f6524fd93e5e10bff95f0fc6df1d5f9ceb5ae88764a439ab97831c841ed67bcc39d626f00bc9fbd207299528a319065c2cff7a2315c2a48174ddc0df9d5b14c2730e9264d9a17b973b93d51545baea7e812e50f1f19d96baf73ebf4888f26f7b389b8ffe523017aa1cbf764f38b19a502f2bd04d008ddb4cf975edacb66f69c137b3a53b09b7a8f2aec954bc10e5a96c207d650e0de6813741409bf8e10fc5e7016bdb04c1d5f71177329b4d262aef03d50038bfb7e8768c85d97b4193f811fa333bffa0b2267a98e6078865efe1ed5fa08bffcc35ecc2e5306ffc9190f6a71787b8aed45e4ab329a648c544aefa8314f3bc86eb961dd9cef48d698eef63e94bc71473d9d8ab67395733d89c93cf9ee52f1bc0334bc040663378c191bb2bbdd1e86c76bf7e91f21aedf7e443e14b5faebd9c63e98171cefcf80e86a869636d4478c1bd185db1e4acdc1140f803252da6bd8bdd7fe3ebd54e279d559ec0d593d0e190d1df3fc56289892272a5f361403e21e91919b7fd3bfa75b5140394f93a130764539b6c5774b45c7df0f767954ad557cf03920c4afc5dc713c4e46cd19ca9de214e63a9fa9e3d11ca8a592e94d58aafd6741fc2ea85d70170de8523f83ff241303ec08fd413eccf47d4021bde6e1db271f3977b07acc4b2214179b3051778313dba9d0817c0896e3909069669f5b2ca38837c6e46fb712cfec4f29758c89abfb144047105d8a0ee2e4cf29f89142f606e56e9759d021fe994306e23467578cec04f824be3496e94bee4fcb61231850902d0cdca51d4b0a62f17e827b935ec1313d0e48ffdd2ea0f18977ba9efd03f79433f386c270857cd1e03df0dc59fcae8045c02a7912a681e2c97b395356626197fd22975e40ccad39f9b486887faf2b2fea55e418612f6729e28859c0dfd9009781a8632fb1ca4eb455724177f4b57e0cd0153dbf8c937918442646c89958da3438420e4cb9173800a442fd94eddbe1e19384eb2ec79fbc5daa6ad0190241909abf94a0b681e513428cadafa9922f2c0d074892984bbcee65258897ecbc3f74d8a0ec315d035ac323fe285eed5c47ae6b54a39f6d8889887e49338158a559f1e58e0acfc86a5946dc6462af203eb482f47149de2882dd94e8374ce2dd53eebb0b6ee58f1f4794efc494b0be94f1b5f380468256eb14d0a41b21693e93d87b7145f32689789b625e1bbe08e1d7cdb19bbe9ad73a4c7f2416163c85490818afa4c0e85cc9a963b79e944d2be1d1b58e9b37f0e3a62344d3393955801a207746dbb38afc63b6de8354591acfb8591b56151ce9c8226fb156deea9c432530ffdbfb22ad542d0aa1fcbc9f799a8786730ad4dc2fee6e30400aabe8c849985b6f4910bb8185cca86a188aa628614c78daa82afed3d99e9d190f72bdf929c3f7e6b2dffd0b5aa9216d317feb98bde8f079441f9745335471b9c06f2f53f371f37fc0374925ad88f63d62267087e364622cf5bc63328a653c7bb88cbe86036640637821771535dc6e962334bfb1efc8c10141e10407feaa98af17e48c288a93065bf3d8d6f54e747e9d1abef8f1ab804eee28fe0bfddc89de8d13f3fbc636f5b4e5f8d8286316d2960d5bed149b3214575ec59e44b70ac2cce4388c85e1143616e21858729af9d66ea88109f0332de6e209a55cc0dfeb912df47eb2499e7e126e544981884c6538d6192aef4eb1854207d0e2a22f9382f966369e4558f0eeae6bcff3bfa992cbe821d3f63db3bbeb6a58ace1bd590a3a927448bb9438aa99786e35c7fcdd9af099b673c76022e39143bf938236b0dac440639e16184979bb676a1db4c99a10cf561aa741ad9958a2608e7dd0296e6219e632125f800313a240c81fa4d5bb1ec400134dffbd39909ce37fdff8791bf3a2567600abe0b81220f6804e067242d28e732abfb650c8c3da28641c8cf0e0cfd2acb7fe1fa1aee8a324c615239923d67692b4c227b717c8046a5836839a8298fd372be6b696664a2981816603b459adb0ab13882c31fc33a19fb208587b8bb34ed9cc8f6e213b102c482528e602ecff81fe1960b6a3bea738cfdf5a8e21a9fdfd204ff981dfb7a0e00416237e4947739455dd41d062ccc39ef54f69d0af45db356087d7eaa7a98ced08726871d1f0220385dfe6f2bc7fb5edc55822c5c24e5aa2c9d44b3c7a3890522af291d7e7493a27528218de70433f30883212f6f9543d3c960bd06720df1ce984bfb06a64b661bab5ff3cb249f8a352c9cd95613fd1eb9ecb0c7c60d20e32c737c779a5954a0f9e02448213c9cccd57b52938880b4f6e9e677a910d8402158832b2638ea27b3d64e63bb8bbde7743091ded6455b95acffdb516ca0532ea04837977a689b93c8d46f14e4a237014f83951c1b117e461c11f87cb2226c597078e6ad2c82bfb0cafb6b3cc2c4ecf0e8deb0b3ac14c3edd43411ac63a5dc9564a9fb66916c9d9297be4dd35452a1f2673895c837347083bb57509f8be23216dd02e5a1a653884ccdcc57aeeaabeaecc6669288ede3e49c2307b5f4f64cfa17c886413f1d62fb103366609b712cc7b1d2708107ce70571c26de126fcfddd3a5de2538d7fb8b7cb3d3749e067222712ea20568848d2f2ee6df0513d2959d65c3167472552be590516969d27b812ebc32628fc074d5caf2f608ea1b1800428bd5067970f6d897ec8902ec296bf6c3d27212c789da199f5982356fc32c0e8704ec55860d2309d0abdc2457f627170837f58e143ac24508685e0997cfb5ee7db2832edcba553b8df27af62fada09893084c330998e693dfb70d7a160a72737a076fd8b0b738b6dbfd2c7a1dc06ec44a6c9fa872382512e713bfd4470dd1341131f092abcddf56452195f466ae4e24861e766589c0482d47cabc6038db39ccc2801b51f851a3355305c4731d1658129cedcde3c8ff916a2c7e68b5af38d097ddba1d3cfde4f1acef85276d684ec6b04c810059af2cb5f9a19181827349c685e5c3e3a7d62d472f5465842387c6a2dd8d672492a2e1c9f5bf708c695fdf1c2f3e14068947077b4c9aed241dea65a66f70123ac5c736c4a72540300bf4fa62e3dc28c3700fc2654b143ea50606b334841df790d1ba3ff5675898317d71f8c87133d03094ee462d6986d8c0c2f2770077914e2f3dd7ad08285f7d42a5cbbd71c72600434d8098348ce15038ec93cb64b9d9f17d08ac2e2877a3203d98ae89678ebe4e145a5dfd01fc9195ea1b69d0be952f5c164283c82d97dedaf40749f9906d48293bbd46b0f7e8523d620dc018ba28828715333a8cdbbb166d54b9668168ebdd5575688732b22d2fab82af4ed4b32630309ad96cd51706f172243daef337e42926763c8b7adddbbc25d31c53d99ebb907b220d9f8ca535527b309b72b2d2b3a9222b9796ca6cc8eaaf3233233a95b1c05e9282e7ef06fa5c106549d40743865ac1bfc25e6d26389e27c3a3db05814a6ffb89a637e8d0a29cd30ee843132a7e6fe17537312cf8c9410ef07211d9a72d979534cb389907b9a3cae80b19c96996f960266d1f0a8fd60238e91c4bc89a29ba5b0da0e5fc7aa1495f63728b4636998e9c4761440561b360ad59b62ac6a8661ea7d139c65c586983e69b6b80c56665e6f1fdd13e494b4275bf9847d5ec244cb4f188847069bbe8997f9aee1950c5457c1f220a46bd6983fd8052e5c96636fb6e13d257c87b8911a595e54baaa1e1e3e8d1263160cc31bd254354e8590db70a7b081260c8c0458348d8a6097a0007a164ade31eeed31974af34eff84e0156c697cf1486dce507f76d371609b7f4ad9cd682e189f35f942501af47626c11786b4053ca45121bdf1a035bd71a69c640517932c3227bdc4a139ce0511f57a3268c80db9928cc724ea41ef2a90bdbd296c12c58bcd706a63fc612aa3977ebb01c8e4ae912e5c71e3009d71014acde4c68d15a8a040947048459cfcb399a4eece80668c212e306accd7ee6eac95bae64989be891f5bc02db207d0200213e8e1c9779c661239a70e48d38bdaa35f1cb87e25f5c71d04061e6dfe8639461a75cb856a659f80275b4f7d44c61820a25ca210c0f0f001aa09010924434dabbb41ae4900c38618f1061b23af3e0b40689764e2a32549ece2109bc82d04e748026214f6dd2bb6dd743b1133f1af2d3eb43ceceb083c9fe3ab1c60c4ef61d4d8df30b30b3a372ea09a91e6f63cd0e2c8a04b7ad2214c41000996abe58bf46e9be7cc737da0f44e8b19a73a0658241341c0176e0f467a5b66647cac0b872ac62c011302152ce7de3d41e7d2908cbbb45d99e4ccd153887f6a1f2f1b044e336521f2ab5dd77a4aa46057d22379038198981ad9bbf5ab420b8f20495a30f3486037d51f003b09688eba8e199e3a3fab572d856caee18da523303f2cefbead8c41bf813cf53b78dc5b9509ffbfdc8813f69a572ab3fee51cf60f4c7eb6ff02ef68697b57cfa601f225286f4a63d9d7180e1d4e42a3911924dc795bd640cd3df41d4c0fb761802287e7e4be599b0a9c54f229308804aefaa5f676b7652b96c67192b585c1ee4e7e3f37a3955c0b1f15772bf80d0cb1e457840eada1054e6a81e793cad3c384bff38bc44e5350dc66df52630ad00e00b0a77f86c2e65fc01b49b1eeaec794667c1967618f3fc54f389481272369cf8fc2627c8f4e4f8765781eba21a02e3231a3d0170613a70046384b71c7e686ad2ddfe2199f4bcf338f2ce293bf0c38b3a026ad90d7a099f89c684b9cd0241fd46a1ba05aff32b9b49cae86d2198c775d2327d9e4672596f52f8dbf1bc63b479c45f8b98bdf94c961039ec4dea2b541897cb839ebd8fa64a7878d60c680fb0cedae9d458d35796aefc62809e379006cc59892be1658c55aeadb1b4eb52443ae63ba474abf8cee13dba2e0c9b4847c37b1d00f53e6fb6591d31f3d5ab3854abf585da9b10789dcbec83d6ea60d93cee23c2379148fd9d02f15e585002b50dd77caf93bb86eacc82cf0186f59215d758be0986a66b8be0f2da35bd5602a136702b06d42d1b0a5e8082001807c341ca961be5fbbee2923688c5066d4f297ec28c6cd82caa4735b1763d3f7ae3df1f8d8de14898e51db45dc3e19e4ffa601aaf270fa670a14ac55309b55723ac2d624b7e21d78a015d4c7f0cce722a3b8fda35bd605ef41855c85c93743e1fcbcb9f7a9c63310c0fee503e03490c6d3ed93b854d2b3b9d8710569ca9d7ce09bf1fbd60485a78f3b39785f5b72dafdc174a444434a3032b326da3d9fbe619a89c3745b258c3bbada45bfdeffffa8ec887788f9c330ae9587b137e2109c4ad40d5c84fd266833b7fb4f8ba94a6fc1ba7f43f95de37afe7f3dda23b1e2f496ab60086dde0d840231a871428afb7d730c3590ab230cc7420f0d78b4c8643206cd6a16860af399f25e9451933b5f5de13a97ce8b9890c4bd6043132d5e8dc1b7ce25ba140ac26ef72247328470f26b5b593058757ec4f8eebea0fbc401acd8567da8cdd58eb653b7e4038a8a2a386427265c36ab9d9f77a4f7d7abf9cdac04e2d49113bce36c605aa03b14ab8c6d5264667106c880043960d2a3ab73d1af6b8d034bcee55fbbcb9a6c29180a19ad883a1f90eff4e9bf76acc1247bc22d8cfa50750b15f4a5f8b1b12ed23aad0821ec5f4aaed07d385d20c04f4e61d22ddb153abd923590cdbf87dc02f86d4f591a4f407d24df8c2643b10067ecfabc7323d7dafba3129dc8df2f7039b65ac368514839351aaae561e85010ac246ee9d9617e87bc839a0d2a4af5368485fdae795251bb148215e85ed3ad659a3cc6f485c3f99a8b8a2c96b249cf61d0d960c45b61bbac47a63c25650362c914459a1c0fab2fe51cb656d1bce901acb6d26fbfbb4b2e340df7c4739b2e0ceef1a2f91e4efc029667e27263300050219715755ca1bbcff112516b50a547ec87773cee98dafc9268be23140c2e064fde056ce2d0c93d1d16e42c17e55dbae6dd5a105b1921e93541fce243a1bb9af4eddb151aef20e81ea0ca2daaad77cd071bd8e054febb640b8b7858c9078a527cd449fe1903fb2023dc6fc7744974d7060d07fb3b4beb9c82a1bfacfbc6485b3de6f585de94fdfffa1666d8abe750c6c035bc073b19663a7f69fded9e8584e5b6d56fb32c235cf93c1b91cdcf02f47fa1a8003bfae69cfb242ec3d52676183eba40b4eaf13624b3c9bbeba42037e84a497f7c9a77df69e3afd59cd9388739bb07c567f828c50875b17a81474a637c1075cc8b16c857e42b52c3a780e30134ade1880117d76b9e2ca7255322c2ecda9c13e54343a2ff516274f2eff11ddfc6f130f24e7017479b55889ab378cef755cee1fcecb0e2861793350eeab61b611b7a84706852a217aabcebfb1fdda6db49a6f74c3753784029763fed634ae5e5822f6bda39589958437f484ec7ac413b4c15e5d9fa5ddd17378acdab5a6767ef9b9f298ec3b798a50276a9b6f05d3d2a39e31d3c90d90ab96d2f1c9fa8a56942bf0ad46ce0a4596fe7ccd8aaa5b0baba0cf52afd00dd0cb856937059f93fa688030fb86b6b72b9c179d15f2761345ee4c656b6b854aee0d90f78044f5cf231729ad9630fbbbd132add9a2c1d41e29dc3989c12161988d388026820521721a3565183a6eb786af617858d83f7a5b667db65d9a3303dce98086273de78a8adf15963917c55ca8d2d2ef39dd706cc4cb6aa913317c33091671bcebf7e0ac55b20491e0ca5f9f8347b90cca84c7a99756ce46de55d5d3416b1ec713c602869e725635901b7889993db076809d5b46a459fc92e696c4daee0a2b6f89c0c8086b9bc1e80c1545cc76a4acf972fe7557ddada4d38a28ca1fc1dbb13abdf2e28b1b5d031f7c7ce8a150dbdb2179fb592970b299355194d219d88341c8b55f5566a728cbf3a064548a4d03032fe31b35f560e4bbfd27d7001bd730015454cea410a4a4bc129428e751994f19db35d5e5eb7d49188677f6b99768e5c53720c391de33a41bb04b28d1740458ecfd948665f40a4739031bf0caaa61ac6e0fa8bbf0c421cfbaa8f78913381b8a9757d7be7907da18c1fdc257aaab0e6478affaf39fddede71f44ec6ac47717ddea8ab52fbd1000685c3a65662dda1bca58c0b7fbcf693c3fedb2023037e148af309450f97ac44ae6602940cc5d75d5e0e814b2f79a90da468aa631c10b2710d2dfb0a456943a5dbd88cd5249c9f4a1eff7201c0456e26f0684a5fa9482c991c8aa4fdc62c7421c00c023d2d5d50b90c38176ceb06667de3f3b62d12f6a2e1548368784cf382bdc50f1cc1008ea764b2eae2638531179f4e23242f229d201e95333fd42e58174a46821d556f02b07a50eb539a89bf7c49b462e9ef4d4372ad1f4441ff886e1306783322333c92d73430450651bd94c49b61577e68d67670fb5e5e24ba40b7358034013e49bb5dfffc1ecf92d58a592fea7e01bf0581d23a767c54ea86a580e6313f7cec95650535ffb085bc47ad54dc0c6c6969f61775d4d892f94ffd9d8c9c65920830c9de8b8da7acfda7999934c636630ab999e947268c84d5c022596b430814d0b4ffec25dc687e581ce8480e472c73769c71f7149650d8c49b90f7fde16f11298678bf312f84bea618382f1d87ba285125de840c6f12b1b774704202d5b8277c314bacead8e2e22de62c2210e868fcb6449feb2b3a4e9d2c2176355fab8145ef01174f97b4cce0a4d3bdc47b580741a9eb74fab22d85558117ef20b16749b18c9ae864cea4eb8445e1ae18c7c2b2cf5b460a178ddf486c1b1db8a17d1473bebd4773dd65fbc471a8f8ae8ee75d224c413f6b057e42f11ea972c431d7dfb010edadfc88498fb40fe5995eeed57c609d24083662786addb062ea88a05d8a4a8ebe4b0320e32e213c55c966eb0cbe6d7fa067ed403c18ab93861f20e19b0368c3ce99b708a92356b55c230638b108338063ee004c55ef3bb1d14edeb291a49d41a486004722e463bc3f7712a2a7e53d0f6403f9d7afde9b39b36e5d848b4a6d9cd9e5a781f62688fe83f78f7188d86af6c110d9dd2534daa51d01fb081cfa4810db22898fcc06c328d60aec9e64603f33b0cf33dcd68eee1fe81678f4edc232e1008617c5fa3a99540a4f310837291d4907c1c3e45e38975c460fd2652a4803c75b31725de415bd72fa0b3e0d7486999dd199e828ae64451d2d8ab673ef8c3399f68f780c4c80c26f1fc1dc31c798133539efecebe49cd305a9e5dc8076d7ce01f7e44a5d39757a0e7d45bdaab9151c61c6a31a23377a1e00e015cb127bcafda349c2995efc735d3e4cb2f475954ded60bd11b61af5f758de7626ea71ff517c32b6067e4304dca4c2358d4d902c47289cf27a9c046e1b10192a3662587cb960f39a7487eeca53b190fc7c0e036a984025511c3d3a1c967018328010903b9a307fec4aa8fb211af79e68dc4f7f1db82e1d8e6a63bea43fe924e0133ec390d24167e409788500264443834c6bc575c964583cc75dc835bc52b5128c1cbac52843e60438e521f6add0605d6ce08971cb67159d7606e6911cb85315c3babb69ec6a8929ef62a0d6117b2e64e98d89b9078298f503f20299d80203634e89687b006378e069626f806ba2f76e49891f421ec6df476e5b7b942a3461e220e7f346c7d2b0788f1708ca601581faa82cceecde70a42a13ab3d946796d89c3d51d09a488323d7311888dd5acd63d3f0dbc791555655e3769c1a36217ad63163f5c6ccdf1cdc1d3424db24c539322b411f275dbe9fbf706ade17626c2736ce4885bf80e031b6cc9a6914349a185c626df827fafa4ed25930d284acd579de2a02e12e84065adf34c6888307b6b9f419bda9b661136292718f482ceb85878a8749ecac0c7bc6761ab346a4b9b6e33a3d7a5ac3018261f7b29e22e44a3c67ef5bf95cc0ce709853857b410195d180b3719682c98e60d135fe4a3a5fdc4660801dae7f1665671eb4890e36e119d857e15735b5614fef0c0d334cb8efd74595132cb528d4e55786beafca5f72e252c1ffca284be399c26106d887cc8689f5b6bf8ebbf4e532db9e5ed09ff253007b85bc424420de0444d6b0b0ef3857825ac61e4efae3760e20de095c5153986d13fc5c73b891287e8dec78412f560dc0afbe65fb2f9fcc782414c23258f93377d91e03321cc870e0e0571ae114e3693a22099c8f93b05b70a6e00320a16cbd473c63b12889d8c037e4589a78990ae9c10fdb7ef0c88e65cd0812408f08fd4b62c2bc951a5bbf072311b8a806ac8b5fab948373f0efc37d1c0cf5d6f07f25c37b7e9541cfe9abf1cb8b34c369eb93a86959e1827a57fb4be26bc35b2ec6b4a8172795c45e71f03580dd90c16876ac49761562c2138684b6953fe8a22a90977901a6fcfd4ad900a8358a75734801b4fa8f38b5505bb703675b7cd979d75b40b273fa0b33e3ed3f567025051ceb9578feb5fc044d8f853722471a479e667633059c34b8ef6bee3a74bc4d2306fe69e57131f2f25935ba6a52f51795ed77aa3006b9eacd7d4323fee62e9d8162c1ec53c3efe517cfa968a6af832f3963f0df43e2935aaf8d906b352264d3e811fde1a9d205afc915d3e08e598edd7d1e7827a13b8738fcda57efdfff9923b65c7b30b647ca094f1bbd8e27047f030be446fdf49741143042d9cd3b09b526c7f1e2cf1c21ec8a2fd66540c7ca804550fd5e3037ded21a4a56cb3f9d67325ff4da37cf551a8f5c6dc81f5bbf53e7d39ef64caa09aa786067da34f276e69517d362614a880f791da1b37e872cf7906efa44262ab31bc638efbc186e02c51307799c23cc73d0ed77e22405c00bff94df884890fbd5648537572315538be45dff9ef6f8672debcb69ffa7e491a5cfd862ccc7809cf181690e88449e570f14544cc3476dcd29346f10b7e5d2e1afe8f1bc448ab23b06ef9e896bf9d1b668351af4d9d1d0400c010a5a3b513b2b208efb16543e4d585212032a062dcdac51fe112ac0b3b0efc52ecd9c52a956105d084da55c1c00fd54472784f30f491a7a8a21ba78109832bd7f692b9e27a9d588c1b3560049efb4c37935d90af980b5315156fa976c6963ebbfb7ba48ef9762f00719865ce8d6a2bff7706bbd34474646dbebdb4a3e3c342e0ad0a17525be0fb3c045ec3e91df48fefbd179baf6c5bb0216b35b66415597af59e92ec821419c882fa4f6faefc57a739dec20228b7681ae9c33cbc4b20c081f483f033edeb35895c9b7e8ff2e7426cc8b3069dfb342c1eb6fb79f052136d8205c9c063729c2f7c01ec921eb9a206d6772c435dc83304cf898f75cbdd700c9ebd66c00dd234c6d109df9c3eeeadebdd12f2e555e22141e337ee1b0568489b3d4a21d31be6e477ee86087b919c8eb38de7ff3c98cfa41e3791cbe2f2be3b25bb1b3c1e4e55d6d02a81c0a40c6b15e3b992e274c7286e4a3434f3aff695aac05a6159418ecc40c149f69a759a0aec7049dad1ed812a3f4f957dfdc6f7285c840489d44a6fbb29eb488eedcec0e6636f0531b33935d9db36c2648eac3487ac3b5e91fea57e63bdb43dc78a71561764a52b6b036d003739b52df8f8e1467cbe6cb2cfa41216fddc107076882056a30b22c01baf96923bcd3525dd9dffc76f3f8a32ea3060d4120faef43b4077d5077297320f4bc1fba7be26453296077ae162972adecb730ebf06088dc2e1adc08d3457d3c65d5f43cc80f1cde8b6b59bb4b8df3198d48b904d857bdd1902047ba9738a287ce9e829e1faee1ad25a17c37ca690b6d4855a53112fd72449599090380b4886aa5faf0a101d14f10e433201acf9cf4f7d2ff5bda94349ac8e6535696973bdcf144b6667106c6484cf87a60fa180035b25db2a0074730576bc00459d1c08e1f9269c5a6a4f8aff32e33992d74449d679597b8309ed101b9fb081bd203512cd3d14ed3cf9cdcc8d8442df69da05579a3458185467ab52f08d8cd8c5b03fae7ba383c0c364c4f6e0006a30f049d049b7e69498394ba018e898715ab048ad0b703ac240c2bc89471509feff161a7b1d31573eef347e09abf83dd8ee79e24737fbcad8cb7f3e193c525b0c59181cef806b9983c756c07c03be6ddbaaff91662ad7c73f077e79d61212815559df61dc9ecd3fab715a1f3905ae01ffcc027d6334a5f1ebf00993f317cbc35f970541fe8114a0c69c6d4cebdcc3019303a76b09e72773e92ba10fd2de90f2be97eadb3fd8450ef0dfdfebc46e614f66158ef77ccfbb72b652f9a549cdd447487ce55b71994d6189d10d01e70676fa6668dedca9f0270a984a7cd2bdcb6b1c0c973d7f697dfbc024bb88fe5f0a9bdf4153f33e873691e05ff71f2f5efca4bf4990c1e44fa4dfc9a7a9da667ebad287dcb7fb8529fc2320d5f256ef82a60132c0dc11ea955266f7ef14d1d1fc092b487cd5903f530f2dbeed647ca58f623577469dc3ad04fe0d97c13049b21c6cf13c335c04567bc4cf6031834a929b9fd79fafd9caf5ff6bb4de4fb289d4bd95f45a46d4dbc97c47eddb74ae9783806155655a8fd0fd10e48c73183c809b2f73cc4e07c7ea2d710420033aa3b7cfcccc85fe34b5cb97a8dcedfb5f39978a85ceb141d2d6c5ef2efb7b69af6d472a36c0edaf1cfe316c91140d0620bcf74c7afc3309ceef49847d6d45c1b7f9bbdb6e1ed815be4a3b05750bdaf6f14b73e91bb1863ff10c5bfc5f58bce1ee3366bdd0715cba579cd67067a110e33a15f92ae4bafe43a191cf966be57c6efe3c604aa238c602dcfc4e3b4003b952d7c3e4db66af665e8759f4b185223fd87db7e627efcd2643899a5cebdd36e0cd6c5f4d70cf73eed4bccf25ee19d92f4d1f02eddb57ed1dace6f96115e054529d4c63eb3e0c7aa2df6845306f332a9da2ebd0ab7d0a85bf7b9ffdd7f0072a644b6f979130d8f5789ca0414cfa596861deeda3bf3baa24f1dc7d5752281d72bf623bcd328ddd8154d9c3ba5edfc820f6f4f60b2b8c8cf443932ac7c192d7bc694d59973c5bec309ba973ed3e5cef0f267bcec195f7666973cf34bcff4d267ba9cce3c782cd0aff46f73cce8278e95fccb98cd99fac24c29c038231e5f6262fb25b7d1bc3c19294bc694911925c99c5232a5349928478694272365c9983232a32499534aa69426135a3a5c2abe9f710ded51ea199be1037ac78063c89f89d63873623cdc85c45d39d2f819c550bf7c838288fa5ed1d26572df9132893bd673eaa2a848eb2d4e06931bca4ef2da9afaed68e67dc89687b66f4c154705325491bd273afb2b4944a3e9b0e5ad13bdffdcf503689f7fa91ff86bd61ae0f8c14d086214163f93090b8b276911538adb030fb4d214ebb1522eaa4816c39ab92280ea4be52d7a4744cbebcdc961eda166837c51addfc3845f359dba7692109ec7716feff17af0dc9037679f3cca7f01fcd43fc8794ac0a0d32e9e8017d8002893398cf317a52018556689177d0ec48c9d2f12a6b3a0965659f936fbadcf5f901e59f6de52c44a09a46448aa37f80f2d13031017056791b93326a0ff0505e8a5c4f7618b6276f8e251a1d90561c69900ff1a9339987e47d9dded209a5c40998bb42a431f626e149d0f690d1499736bbe6d3a719ba486e7e87d00a8da453652477f7a8b86fe72587ea6635a09bb074e696ccd552780ccf312e5628b400010e1e6ccc3193a11ac7b4d8cc8b18f17cf2ea53f85d078a412ae8cc6e78a3540cfa25f3121bc2461da57d3fc875dff6f78de755117d210ea18e3469bd0840ec70cded065b78fa47b53aa3c8426294ae505174a362a03753a0293d1f96eee57c85fbd4fe80e52c1bdb689409aed5cd2df04ed9eb1718d671b1caff6369a3f28c4b606874004e7fdb50c1664c90e0e84eec20a548743a7fee038b8fb04ff182208e8b61e0406f220eb2f1fe39c0be4a0b970b3cf0480b6ed82f76ab6d34ef88572744d8c618d4a6f8883a09864b096ef7512e21b2e8d420fa3952fb46369ac1fe8645927b8c0ed8129b636ca70df76787e5f8e93c96913ebc67b6c826c6ab9dc90ef3782613b2285e180195510412af084a47abf12c8274551780f7446974c4c9c0d4905b067c2728328d82a37cccf6413a301ca2e9bc0482ef97c02fcb9031ded25eca9d51d1cf91c88851ce49a1201cafa1130ab12e3cc1c1dc4f176a51b729643dd1967156242e628c731f8d6f0950a0af093435f6ca6b0b3018bf68e82bf3a19d89bb66f41ee1578bc8f1c3d8cf967a57e904d1bc7984a52fa7136aa54f3297006b3b9ecc19c4ef12c68fffbac81c024aba7066f38ce07efe6cbe076353d93ce1bf7c05cda5a998f1483f22523c1a1e0fc9a894c68f00a5b7b5ba1be9c9c7be001aed206edfd2999af30f1c49f5ca47594370800ce382d1cd065d7d36c2878cc2dd5c83519b46dfb149a105254352cd45e8b2862777bc536197cae151d19931fef239965a5e65e347325eed2616f8117323612af702e2403062fd65cc45435968afa1e349c2b72e16c657472f0bd79f47f375b8beb0792b6a6ba811ce1c1a10ef64513220e27e95cd98a8d785f28aca6db745dfbb1b0799caae79e4ffdae5a24715f5054cc21a72c102d44f2ea770eb1a21086a2e501e00b3ca71e3617b62c5ca0f96fe97cfc7b18bf255ce36d93f6c6562c37bd9d8c60f1e590e274bde581f0585304a035d19d744f63f83c6ec4411403dfa7453c6e10fd72fbf7801b29f58e7e4131063ab1f19874d3383c3d374b3dad4a0a735040a024e7fca586b56ad7d4e39711cda9ee89944366d82c8b6d5f33e497fe474146a2c90da8c95af0c5f88c5db5b96d725b4ab02a461e6613d48e260089d0d8a3b0857ebf706f71f5cc4361d8ee0974800fba6b76e78ed8f039f5746c0a0f47792f9d021424007a19f0b34f84d14bf8a0a724109496ca546b742e10c6ec72fc8abd32f0b76abe682bf46ba7870afe2aab1c090b260a1c0a20a0a4c0b27f0fffb190c2832eb30aaa53a7bc1d6f4c82de8ca53d0817e0ba77e80672d22859a7a8924876d1f1a0c0a4dae120fa435ac8bb0c4fa81f9dc5484704c433c7a76171a65e8622b6fd626a3ac56cd63dde855707c5d04e784d9c24e5ac4ffc39f10486299a6c10ebc28926cfa3a0ed06ba52bb7e8746d27086c9f21b3e7932ea18321ae0c531e7c5738786d0106faa29e1a836e4a285c69854510fc85c2eff36be03b6e9ff3cb9b28e0eb16ce077e50ad265e6dda249b3de252ba15c839e57d44f4f04ebb2264e80454543d08160dfa1f3a9031e0c85b6e3ee3eed77c809aca7d65992e51ec3e427ad12c310f33c3971763b5261d1ba127d03a0c26fe2e9c060f176329776d96a3b3a8f2f7f3bd329ca77afd75caa2a0b56df74f2ae2cc1d291157ca6e255fa3053b5b5256e708a8179126035bb68fb8ec5ec347b9daa121f9062711c0fd5138a4e7871139d6100bdacf7828329b2e56b99be197c86f229e164014749c323a6f072f6e9f00561f895bc7ae80eb9e670207b4fa5289b3e17747df29aec0ef610c2e1b915a57440742cdd2445c5178366483056165e9f1adf2c7c9780f45e8e7bedd9314ff886ffc994ac44532130cb087374d48f353170ebdb2af188661a3fd8904e7f25d799479b04475b327ccbac646c72941625986155f064bd5fec4ad59c1602a6cb459a1de1cbec2162ff8400deb4aa7cd0f715194ae4f253bf36a9186d383cdf13b9be78040e0088e33f669d625cbcc1716acc6ef2a60acae6d623616b9d89962a4c1e2ad44165ef2f627cd818bc95092529faabeb5338cac761b7aec4c574ff205881b10fe9587eb6ad5346a93f3cba8a947f43321e9a2fee451a67658fe53890cc33bc534f001554c307716a0f58bf251afb0610d1de56d3dc9063392acd3fbcd02793c516cac3e55a1449c689c7f8a4df75d67ce7009a5c47a9fc9904149f4fb06d00f97ad35d1d9f3746ff0a7b53e1aa856d90eadffea5d55fdc20c607182ffac7128f9152f55c6bb76cc60a0203b347344b3d700b7dc0a27e170b77e09db32d5bfc09207b5e126ab26a7d50f23293a1013c1414013005e5310497c51c30bec27be1f2219d9c54b8444622eab2e3751960ba59efa329fa78a1413c2900fe2a4bc0c9fd33d3ab872c8f20590808d25c75a6a0d85a15c498926fa4712a98bd304cf5baae436237d35ea97ee099965db01f6f18af59d44537bcb0222fd6bb538c30d8d73533d02efb25daf8e3faed00b90f948cfd3788389f1fcfa38c819c010e414ae87231709fc4e462762aecaf8506e2d5b43c984a013f3979076b3a9f3ce7f0875da62b9dbda339f0938e60a7b0d3159c19dfcf5ee17e02ff0dac1c17991655e856e649281a1deeede86d2c3c679a42fc4e7d3c8a7def669787f0e872e219a64b49c6c4c05101c48aa8e53e9608e8bfe8d126db45a5bef2a25ba97cf1657467aeaed3554e9d70ddaeb9222182ce475c49a5dd940d0ece9d5044cd843907f56c02ffe993318c9ff92bba948a88dcb0bac0c7fa0ef757dc67b63d13f4004de08345e78ef13ebd895c84f59033dc05d12e21c885d147eaff538b6b71ab77713e0a24885316ea0ee672a2f94db1683908c9081f6c5058989daf0ed8cf22df1f1e6815c6e58f1a671339a46a52ef24571d2d75c098a60c4767cddc1988d8a60e1f3a1a8df49106511b71d34ec44aeee2510cea68ff8a5b612c4ee00e01077dbe7e64c663a1a53dfd8cb8ee99f462c5824a8d1d59a4b049042124feddd4dc83cf884392d085af94602ca8ff95b313c2ed03488dd9ace860beb6e5a9171ad6fe27b457a27ca18fdc367b9e63c4e6977cb4c7c62912a10c9654587ab35bc2184cbbefd3dae8db46e7beec5465c6ae1adb4cf8e0040eeb1a8e22af6a68ad216470533115fb37833004ab7c47ddf7091ace7964471b3ccdd87308acac50b20479709e16ac5a9aef8784137f80481d5b76512e531439bbdf14202ce24e70e833de396596375ec630e51a6db212248ac01af6c209b0a2603b7574ac10470ba3ae42676ddc86adee6de4ea85428e83a4be28f5afbb68caae068508e5b434abf2d381fdb02d85a680a44e3d638b3dddce36be318394ef910f1ebfa7db53e142b288fb2eab16703b22126cb0c696418bcb48e546c2d2b15877aef80442fb892977a69f995f77483daa071f9a1af0e801f4b95f158da219ec513c845a023a6a557c4d492cbd53ba0751f3c44b4bb29e74c6282c8d55e767e04e72336de674cb55330ec72612717dfe43721c0db05c0dbf0e29b8f01c18519d6d4b2a64cae89879c41071ce1ed7864b38ba1b7b05b0d06f555f85f8b02458ba44ae33bae106abdf3a652c681973c2772a50f9a319059966ed275d31dffcce48c438aa8c1b9350ccec3333c7cf518eb3b66f8b46f2280a334ba74e1b7776e8dd53ae18c7ee4c48352d3780bfe45c733e49b4266d36d3a11595462e4c12e3d1859c83d6fb8883527b9251ce0443a5a2db4bdd68c8d79c8ca5c1dee879e4706e05318ce3955dc5e20fcff1b784add7eba3ca0aaa2b8a79c34ddb836a145cb244f0adf1b910a0c759c6ea2b8c2beda2e62e927216857f74d6a193f0028fa4f978526d6ca24c3e39ac7dc7c1887487249ee5b8c104a089e4d8118ba283c15179a04ce7aa6cfaf54f574a3c1d9218e85cfee774c054ca6d0defaee075070bfa863d79f470282e716c0c9c54a40a235f5e94510b4e4205910275905c95a2750f9ebfcbb24c6590164cbaa58049705c010695d1ddc6157f9d4432ef44cc348dc3f472b43cde94617484d6a3ccb8974d94636ce3b4468e59170e418db7dcbcff5b5b0695461cb1845147968faa49013ccca7d3b519e525fb00721ba7214f10465649a7aaeb4a2d6e0a897d880d870674d8ba7503390ec2ce91b9c39f3e36ece79de2945e7d760f9c5d4caa010a47e083700f921ee2af2f3617e1e995aac64f76e6b9ad55f97604b4c2083da1305732f13d09fbab4521b85363d6d69508e3fc9ba7382a65e9a566f28f2a6ecf510266255570849d15eb4a392d4368e0a4d5042be00287fa7a744efa3471748d4cfe8e1e826efcfc80343e3790defec4017832518aafc198a8a2139e733a0354aae63bb7eacb54dac962b7ae0d513a7e619868d7806f5d96a05765a4650acfc538d0ab6a30f239520d37a6c5b8348b63334bc0e4eba81650dff0b56c8047469edbb36e304bba7b267000b0188f14b782b3d97ac00ea0186c2710bf742a2efc32c0c981fa97900120c5e392faed2366c5f1726088a214e38354b79a5f35568db81e861054cd66144d14d7a612fd0ca32786c8e8cb79d6f62a53a5bde209cd3f912070d786258b81410a1c04574f1b9abeb0fe68c1f38cdd34d50ee682a6f0865dd3916f11ce15111293d5842623b0af41e915a857ae7704b44e6edb300b318f49ebf0e8d0bd5c021f0c7531ae05850660cde6fad0090540ebe023264cd6ad47889a345a01274e216b6eb09bc211de2526dc20d0d33956510787365a88dcccfe876203ff8040fe81ce86552d751beb6260ad5257500a716ad0bb09bda58437c1f9e31b443a435e210f2747e533a4dbc8613c26dc4324c235041f2f45327b99096002bfb354b762e2bd139d010d4508a4f93d7ccddda3514176990cf238ae1c5cd426967e3d7231152e7ca1815971a3d8c7bb3e0b6828c3e6db1d2749eb06828162c0a08f799519f2e0ea7b2e4304cf2204b243abe3a38db3fd085b2357f069843b28a14f30f3883d8c7d6c2aea57abea3bd3ae884f7d5ce2a694114893f3fae44038e5462cd33ec98faf653aa36ead81e076889e2a7e29d4de4667a71f240ce338180768a3c3928256d6ab026d2aa5a94cf4b53f62c87666c412690c730d2f7628a0b20d95b707af009dcbe4080ed81b7ac07112ba067a78e4dc477c98e631721b503cdae9745c128b240c87a8dacd7d4f6269facd7c49deaa732a2feae42e66db8b35acb6a9c9903f90d9977d5d90652381a7eac5159808e71cc6f3dfe5e7fa29d31d297d8868edbc6c4478148c519a862816e85cbcc56fe79f9e260be215c8c021727b5dcb165a60b422cc579af49ddd7589d0c1381617a2b7ef242f800ae518c55a748201975c1207d4be72e3768d088109ba469af207f7ce1bd21c53d93a692184addd9fe91eb2021e5240ff6455313d2881436407c0ac723c7fb7753ba2f395d1cfb9b79479124c345fda1d82fa19f12c08d7903f3a857bd316dc149bc1e6a6033f70a69b969b88f0bd2d8cc1d7a14541e05afffa973181871cfd28a09111e27e985978332cb1edd1ed1a5a0ada114dff0b5822cb971061119f98fe4ec5d84e97e93f0ea6022f2308f9dfe0fa12f38101f14d07b9ab6fe4ea7770d88b527aded7873e02e59f7633c662f26ac78bb4445275c4e52c2cb61f5fd917ed066c91ec959076c06f6544e3dc410fdbe7d45bc581164ad3596efd9c90b12ca7280b280963a11c381f7f09ff994b438df59b1cac088afdad482cc73b5f5b70aea394593a4cce9370334114b475b73583dd3d10224b7b997adc4aff2b7ff9f54da974d11699258b6029ca28f0ce023bf5a9c2a10aa11601dbdaa54c8ffa200411c0ad0560ca7adf145d6fc81d4eb71574dbd905657d1c5287b52612c2bae40c2a63dd484254386b1ec8036ad5b7731b15d05ffb4d6df151f504542bf504ae5ed7dc8642dcfa8ffa04f523846b312f5ba0c913716a31675c913049c3fb3518bab1aaf0bd5f0037611c286a87e2edd221d48cc7273b47b7eda2c6a593c89cf654d7f05416d0c9b9caa953ab30f0ad7c7d63f6375e8b837088e0b3d4c1145d417f112d7249e6026bfd61a5205f5489deebee5e982b8f1e8a76a561a5ac9237f2106d96da116fa104aa47e706459e7dac6d578a32df3fa250b8d54c74ab019453bdca8b8dff693912e06a5b9e9f5d156a104b1da89c80321328b1818c08cd9a14a25cdd8ce4397621069b0baeee4f16b5c42c9c6248c05d945ce8682c9ad74572215943985726b06b40ed6448b3b2c5913d0f9518a1c138f3af53fe55d136da4b22d001233edffb95a37f6c3549f4730be9ed1f49627c350202450b5b422d2d677d3eeb3accadc2e5042901954e6a81a1fd5ab38fd27f0429aa57d42c24ba08905df8d2f144c299507da7c31fd0d84dd63200e0ec69289e4d32b08fc12bf4789f10a66ca3d87f518ec8e096a62dfce5a9dac39c0c07c601d1f3f28a80309a7293517be3a9216ce073010e198ed0816641bb62dc19ed8fd028fd69b131598cd5b91e86be333bf74d73ecd627dc7746b6042e40236252384723024b384c23e243385223a2cf26ee64d262ef97eb7b64b83f49eb7d686255d92891436bf258c23ac31403e3cce61c04c9d27159de8951003d8f9afcc5f36a8d248b2a3aac68dbdc087ef64680e13a8dc41933d79fff4e71f67e232cb942397d23a782b2da229e110c388f9f9c429f01f0c998c99f3dd0604d5bce0bfdeca11149f49e099b2522fa589c1b7c0f2c42d1f6409c0a545770ab53f64f9e7b487cd74ef435ebf9960f489146b6d40840d2a313a630c6c8bc00ae8e36ee8b104a0d52421a77b734af66d6a9ef5520c29fb66c16921d929141371bbd266b90fcf7ba69d20027e659b5699d91a66f512b58181f8034a9187224d85fe578ca9741dba413e33b289c523ed3666ddd3c886b8c92251532a6d3118903c4c8544326a2db7d768c7c8483e99c24952592269a494cd651b552b438b50a9aa0877347da965258623198dd48ef9e3275647dd55271ca4f54c74ffb620af5d6c86ff1bd3b1672da4930cde99c52b80f5b993e2681b64dca5d5de618fbbe4a5d96f61918b612dba8f76a24682e54f606d6cb04b692da83e477af15d0d5aadbbaa8f5377729292c0cfabf4f030e08149d81d5d3dafd4401dcbf3433451ce2a56cddaf8911ce30fb43c366f1a7056dbe7f79d452edad244fb51a0be90ab9b6a05f7ac3e332a3fc7babc48f983dbba005fa3b8120eb1ad08c0c2e1e8df077939fbbbfe175275b37b70d8e2791a7147725ade70d2e13688ab44a8ab321060a2aae7712dc1656d05094ecd4b81d26a12862470b4f4f349cbf927084b2e7f4943087b8481281f95d2983f1e9c16613bb1bb65a692016414d3e5b75adc228a35c6010f84661ac9f3e941f1cecc620cc52652a4b9520510b32d77f30895688d27d1ca33a5c05085eb727555dc1458dc1601e819d44e8106c7cea2f824bac1c918be08ad060af31bae4c91f9a138ca08f56d572739f16a3653ee219d1cc8fa183e257ff537c9ec7f6a40ac49af701f521601004e4c4af668f1daa0431972b1dc9eda601d57b45665ab786ad428b18f41b612c15e2ec7f4296c24a154e64e1c7e79fc6f684289e36619ba664bc1bfe1508c95b79fd558d561f2d78731be863dc385364dcff4861d9f6689d16f4e131684e6178f1df6d9d258ea4d73c93470ea871d6beae631b6446aa49814cbe76521707c2759faaf2548b1b279a4810ba4618b2014034268a19d4345a2929d52159644170af35d4364a0d144e85a7dd35c28646ecd5a2d6dc2908a2e58380678f88c67563053a016556123be69728bbf16b166b2730ab6257fca41a479afbe76fee8cf50e61f72f8d2219d0a6ef062bcd55ffe07e5f83b59e725f661e16c33009c884dcdf30c628073e2f2e8e8af6a3cae6ae2c099fa7a9d89cf5ec99719782dc6b0a8a4490727b95a18688813886735ddf1233ea82bee467965533e34745b85dc0eef7bbc888adcf93fb2bafcd9e196b7dfcc5f7d34c593f5b97691a40d2413b672f2c446ff468be24831ad9c5235d96328047f825b50fdd1ee6e847d1853acfead050b8d6a0fa776caa8ac47edf5240ba00c4997ad4de94d58e0b08a99b10fe2e7f6489d997f919bc3db689aa60e3fab1c16a9b15698b0ed9359fc9f054db65aed01fbd449d780a19bb014b0b2023d3c7272e278c0eaa00689df3f382c13717640991dd5a7a866aa11d408f93782088cbffc9ee1a077c431c87e4546407fbad82774412b75b1e72d8572b6d4f4805f95d6c6431089c24c4fa8f22fca2b59b05af85877764ce994ea05674b84eb057cb47313800de70be7f59efd46e7b6d6e96f960703c6edfb5367ea4f78fd827521adc6593df997f7258cdc3dbd8af922debbb3985d90d8c8eaf36a2b1a8f5d2f1ba3b35d8d3299955389fbeda64599deb43ad5eac8874793d787786f1398a01dd8feeb3f5010143b6106b2630ed707b2f2c3f4859ad8a2632f2966f16d17457436e1da542857907b6d1a1bbb6a456c6372d39363803fcc093b66d15559fe13fc6938e33c8dae1450c26465a90dbe05ace8f94f5ba454901c97ced60558a0a7001acad3e6f487499138234b414ddc1845f5f3f08a0e7b195653e8e1f7882a19c137ccc5788b46504142c9f20edeab3e6580021858120c50147579822556a432bb7794fbb250456856ab668fcd477d33cce7642f393bdc73d5c9a3fdc88bb460feac433778c816b7232e11c1af111a5b4f7cb4f1bf322e6a9baadc34f0144b2bd1cf6200e1c6934d586bd8e1ab56ee8ddf22f82e49849d9bc0a9ac0d53f6a15ee0fdc149eeb1f050327a694f1fa2e1dfdf84ed950b1c54071d512032a9c78a9e7bcde4c4cc0f5ef731a7c34283e61c2594131bfef0ae03f68ca211b35bae01faee7eafeef2a5c303589daf9b5972e4c22640a9f992524b04393084f4b5d19127289e1ca25ae45a5725c029a2a9c8d610d8a943193f5bc4d70b0350f790a02f935d28eb7eba1d961b53a86fce7e4cdc4ba27d2bad64595f4171848d57f012fb4688bb1b27a971a487ae8ffbed70d22c8754609a8ae14a016264a8793deeadc0cee9878ed067b82f3d9c77f867f2c78a18dba947573cb3e4c76d0ce794c755c6bb7611c54734382ba1c3d8b383f3a6baace6d2b3d3ef308d0e211d8ea551f84836eb5b080c134edc6e3d9c0483eb6d20d2e0aefc255c86d581eeba295916d5710fba55511de560ec3c56b91ddb808b305eb23ddb630a6e56a22bfb128d6cbea21b87929a60b5721b661798c8b5646b45949cc9756457a1bbfc84e377e4d99fc4de9721e804676b24e50f7f1fd2b6e62657407a44a6f99211c7f0832a23d65dd5b4eabcc7d76f75ea8dd57981cd9ec15e43209d71379d126e831c372628ff5fe518ff633d94f5b76c2e8f1ff51b4ab7a2a4bec118eff4ed13e5ebfa76eb8a85233f855737fc17ceff98b2a771705ec80dc619fe22eb0fe079db268b5307074d61a61a2d585df7289dcac9d914c56fd69085615ada4b7950fa8b095effbb0b7b7c54d5d2ca3b296474115cfd4ab6edcc15bc5804f24e6df2d477833c6170d5968abde4ba6f110b7e6413e3ac103da08b2b4b37c3e0913acc2aade2107067c18b74c540b4b68c57feeb7cc21ffca23a86d1a884d69f4a5e7ad79803a3b8241e400bd649f525046e1060817cea20b01e02c97ea1a70394e7323eedd947ca579e48592358b09e4f6d4d032006eccec5b0573287e1d93f6a6a2c16c05b4390e5f89381c51b589f369b5ece87c631342d1028017357862ce7eef8330afecd0dde8c8b72336ede30133ce192ea9ad6af2fa5d1f7994302cff9145f9b73977cd4398f5147d646e73d3bdbf9af82d30ace0b338fb19b4cbc13112865af520d9f7efd4f4fd4e78fa9cdc127ff84ecc529e4051c6c8e04f446c34d771877d41d2c9c0571e301d6c98b402e207d585e339ab8b7a3526676b7a36856d0a1308ba945a17501d06f909a5a246afa874826c3d4798e5f34efc7ecdb6a69e71914abd1901127bababe9af558b7fda969011c9433c70830d75e5adaa37cd0090d09a0e02d12e2b00231b0d137617dd2cdb963ba024b36e28da7056c6cae001448271cfc6efa310d75116b50049a892804460b9bf5ececa73ac055dbd271b3e52b7f51dd510a08c8b37e410ec0d56d4843381019ec396e6bd241da0739d889400bcfda8b9a5147e0e617ff2ab32779cd0c850d4ab051c7cd804ca046ecfc78ae87e5b85b88ce4e589229c68c735d2adf097b717c03bf1b56f62b5951fe5dc8620d63dc597c636c9ed25c96815aa2170d580962792d8a1a9406b6f96998ecbd9a0e2ee3681bae3c6dc98dd03826cab043f10007d9ae100bc9ae368295e18cb50945fb1161d14bb4d4ff45a80324a0b2a8b219bbdd00f0dcb76e391a8290c7faa5ccdc3a0e0efceeef827daa1fc2b3ddb284e10ca8329c1f46fd989dc621873b9baa0a1a692ed42654a6f92bd9b14c3c58a2d56e3276541ad1540120afc4879902a44958d81eba7bde56d2ed0259cd44019c3459b23da76b2190a1ca506f6771b68b7228e07880fcf41f942aa8348e0a052789c5e08c9cae225ed6d224e8bd356a8e75a2eccd5f46d0024f795524229d943928c0ba7e177558b062498e07cfd9435fd7b77d7d1b1a5a74ebd3b8d85a01cec24f49b3433efdd48a526efc7b8bf3750649c40782593cfa1acce07926cf49ffcf48a4578e01985b3b190fdf0930e10c8a35ec995fb282b4f42ac674cb5d8a9a1b175222a1e295fbdd5da6b127a83b7057e7466141435027826514af24740898036cd3dd97a6bffa051f636267c07c455533849b08b34f1fcdf4216f2cfec3c01011d478c76ed1848acf25d2c448ba5fcac4de1d7b2b3015ff8e1deaafec6496daca7adbc206d7073afcdd6308f095f52f4458abfc48982abd6bb4d18185c48cb173141a08672fdc6818b0b5995eb3dc3f79bfe106a6e422f2dd43b36b65173c3caec29fbc357e4927ec2d3fd47b0b74edfcb49003fd3308c3d1fef8bfd13f57ade3a5589349f8ebd804f7c7ffd95ec1c4a6cad03f75008d1f4016a9b84caa6c448f3e38e345cffc1df3bc4cf82b42a7a3e5dafa8d87b46b8fabe0a6e07c4bf9018e65feea4aad9a33a67f80659ee6ff91719ac67a6bdff0f19f4f56df17cc274aafd9ff117f83ef457d50ba63fe84619f7c9f5de71977fd7dfa6593764e18033478384e658cdddf8f24225b9ff5a4da8e8dff46b07fc50edc965bb823a0140f32b0330fed57372f9f890fd8f46139dc5639cffcdf505ad4c07282d93ba9df1735c23c209a7c274fb8dd7dc5989cff62cb4ebcebf66904e8cdbf4338f8e85b7dbc4dc1fa486cbcaf0596362e33e7e7226f1eb792176fb97b38ed66090789cb5094b907fa3e7547c41dd3e31504a32a3f2af1f6e1e3fae9869ae51b2a6a1e8a6a9f3035aad11bb378ace4e3ee1635a64b22875c808f7c2ff04021820d4f55136e3032d57abac1759eb9654930edab9b031997f0727ec4e1e22bc089196641b4e3412151d3cc868976eaf997f9aba219792063d8c412e87b373db63170a1e05303092032c89e3738ef62983f6a57544907cf9a4d3a9fbc259b88b8313675e9f55bf88b8a2fa90a6192bd998024cd3aba2fe3526a7173b75e9718db3d759572f532b4b373a3f5cbf24ed0fe1ee1bb2ba51a0b88161d759de596419257bf5dccceb321edf23e95e9a8f299818ac907edefcf2b9b1b10b7862d3662f45e2bd6cccb6a33c344faae5e3104c59e7c6261070080cdba013e8db27a706a05abb493996e0229d8a4f5a61ac374b388b11d68d9cf078204eb363b7bd5f4827c5cd83316ac2986369fe0a88a33db5a43b050f16ccfc2d2be790880004f582a389e8e63d0d5dfdf4e327967bb6f1199d5c450d5c6576d21cb9b5cb5467b9d30b77adcad6683e69177933377961f4c470ce2859d3abef737683fa7c2459845dfb403d49ffc2d2398e466a4aa215795cec02761b71ed9a670032b6891633dfae92f74aa70dd2431ea809000440b284fa98b1a6ae2740012781dd33e691257e6ea5b02a9554d9fedcf73c65578de8266e0e88f189b259908f0597280991dc239879c88b2a7b1a199bd34e539a62857c36014e65004f0d64f328594f58707b6314073c991f82dad19fecbaf471d159d65aa7a667ee996675321bf3d16be98e6b2b0e7a3c15a045f142c2fffd3e8625c9e8c17a6b2ad40466aff5082453ec74fa399fa3fb17d6a95cb90ff50e85b038264002d7419c9412295e228a4703e2bfcef5ba5a675154408ae198da0f561af7f7bbf215d4786adbb4cf2b8ec579d143f3eefb7f3f3a5c7b6ecf82b28ea9edbdbd9f8cfcea66f9554657d1529392d85b0511413d8da13e42958ebfa7772f991caa7311d3c993634ce2ecd9dee35f89ebc6f623a401f7b451748bb703407955e4a3dfae2e4e0aa5415bf6d7eb69359a541b8bc268d233a7fe512047c1ce6de6ef54efe5a2344e13df91429666acf02f2e3853921a172c354cc3fadce9b4aab453d6b70ef34514c51ca74c4c5100a1616755f2747cd34545df5c8c74b9caa5af87e81fd591b488f559605adda23b4f354743b8378e6435e143e51d491adfc7680104e4dd792a6c638a1f51d4001f010745e36f8f5a785ba6b36b29a33a6d32187e2f8b225c80a979ec0d2c281916d115b270619d0dfe35076188cb8ea841b199b7764d87bd69109cbe9ebdccad4c6170332a95e34970e9acf1e29dea6fffba3ec0645a3b866df1170be3bb303a267e19fdffcee35480ff6f109a58917dbd598b0c99d1ecb935250a84c40808d70269e924f79b06b34eb57930f535864e8a358aec458db04f9df7dcf0fefc58bb0aac700decc2f4da6dea340d83ea6e234935425226045403d2ac54c85a174cb1b91bdf9c76213e1d291c274b9a06c43c2d3ec32ed856f22502a5b3eef5f0d24af4f407169abc50f50a78da68bc473e4811bb50d064b0da00f8171c37fffab641eb5ae79faa5f8986854e6fbd2efbb56df570befafa3f8c024dfefb26b870473dd36b7e11abac16b132c5fcfad69f54b57e1583616c6d67e2f7171d606fae64b687d526677339fa49ac198c22eae667d96b59fcb09fbe34aec55e8ff5c378a24c22f497c09978680bb98a10b5639057c48e4deced7063205baba066e11f88f0c5b50f40d3cfc05b70d8d896061b81a62bcfde8f64877c1c912b39bf0c4e21c60f33058990ddee52cb4b1a91bc3689a59a110015f11e8c515af2a4400ac02ea68ef25afa15459d869d8a74df2eb4463dff5b54650426790e11916b8e60522b87ecee03a9e773df92e1f2e34e6231296d7ce386e2f9b0bd1f7aecb659272d640bc25995842eb51a36c0949779d1cc604dc3bc36b5bc54bd3ad97cf13dad09fb61c8fded303c37091e99f554cfb2d107f8e33fa0f2614e8ff4c665b83fc59dc7552b5900e57a69d098b6954ec5bbc8e6861bf33c68d31bd2fd60066a4fc2bdc7819615aa6aed73c1b5ddba870aa77d9b48c6528694304c0db7595828c421c71f27a4689759c0d483fede24a6e4098ee3bc5405fae91bbc97eafddb4c898f9f629d581bab017a34c37ecbda14739c5abad7696c1c66a0d2c2dbc1c62610d0acd868eb80182c4096f431096fc1181f8e8628b1c9f1990a4897a5a24a316730bebdc0d3be93b9dc0aba4c9fe1375d84339527bc6e7786ed2697a6029a10a4f0d6d7224ebc73bd8e88284b7de287bafff6edb4afd013e8340bf502452f958e53fb255df63facfb021908b5fbb1deb1e23600ad116e2716291ab9534fa80b71ac088cac8d464ae837d76e1b2bf7b36449f7bf2b551e10d7341bbdec080c7d1e2e2ef23d2a38d3cbb954422983a8e4601e663e991f37db3cf3e751050c11af2b3e5217907c7f3e0cb919575aa2035c9161ff541438029560b7b4570d3cece0393395cd599e48d37a06e9c6a269350dbbc1ec9012e2de84c87b43affb2014498deb07da02a48f77031dd7233e7fd896f48866d68d864162de8527e7ff9ee7c290b00a678a6e2c0642591f2f1fa2e365e410e86a4d5e69e8315df70c8d388a59c767aa25b69b5d8e19c64b741491e4f23e2bb8a050a4c3e9330b274aa34b376d1b2da884b8489499df08296a4182a6ff02ec877ec6f2af6ddcad755df8aa8e4c9c5c9bc1cd06f44642c8cb501ffab8c20e878b5a7f560d13f132693b2570ca1433391b578606f4dd5e0030122aeef186859ca5791f8c7b5a5a83c10258d309f969c3a07d526dce822969133c100b524a03f9fd48cf850cfe94b4a8a148dd298a0f8866ecdc65fabe4c66b734ad71568472406f391ca439bd4e7919525359fca3c1da3474ace85fb8f41551bb8d0efcd09c08d2bfa87548868c16da4762d872d3c49454254ce9f98b09ec4f4174c310484252403dc739a6d892647f1337b5d58434e5abaae4df3f0db60614766c3b299c5a2d9155986629a2239976bb67e5c635924fcf599670a80d1ea147ce1dadd41cb2367b52c3820b16d3190d57eb132e501d4220d3f31082adda399eec2bcb9cc843f8297f2a5dfe0a2038000c092ad97835ec2ccf0d1a6fb3be41cd522c3369131e95ce6cc2b8fbf9da08dd7b65a5a4820aca36f3361ee825b7fa89e85ab767e51ce811ad340b7722a186a31ccc6091411073d4165d94278179fe1fde8f9aee95ed28a4b0c42d63127cd7ddab72d80b554d08587028633477a53df14441ef3c2cd2ec8870f8b804ffb4070c057662c45f5e7ae16e656db34b889b0846bb036bc100bd70d5f45d6c2fa325deb839c3bab9952a0a7bf175aec3b1c16997df2295d884a42625f1ca1b1c01338aa48c42353181ceabd9e2411236b56849e86c029dba9d7d75f1a0f6888e277d3c882da0e10bb2ef9fd6dcfd9f7e047767d27f477fc1bd5b60d396fd74a566368f694b5ca1b34707cdd47cea10d308b386c3574938133904d5c3e4d7f9e20afd00ed5b9eed4f219c9b3247b795c1ccce936a67b7fabc91715e7ccf0826fb85a0a52e94843d608623d7df4ea9077f5c13102919f2d765a7bd808061c03808b1b51dd8ddbafe2980ac826bd141ff063c70c5164a5798f2046a7ec2146751255af8b5d506dadfb5aa1587188c0b907a14c91e3956031f7c620a23c5c2ce025895328a0cea5751ffa9d7648d98720c0df574830a533a01b1c590811e0db5c0b7ea77f4fd44b699bf7e76c04d96ad1135dea01bab177d233bd0c3857aaba95e00192b867ff054cd9e62ab997823a0841fd9d69d067a710d362ed20d98ef634ee39ce7a57c406b053d1e0ecddf97ae452c0781cf2b673a264300eb93deb956eff30d9a9a27a2282cdb3783a8ba5f0764b51717cad5463f6bc7906b1d7fe88446c4f01d043fd722c2d9b35b0bac942b53d38fb86e367b8aadec1cab6e38d8b9d9870b54e58885238b17f4c15e16ee2a39c85566c4cca61011df4bce2e472c8d911918601013cfba2baa1456e6bb3009cf5bdc1e6fbc010f6977a1546246fec9cb86daf49a4c9e27228616894edbf49720044dbf031cd1209c1529cbf65fd17ac4a545e2a84f536905a9c3c94b899e59db7bd64318fefb6ba36e580fa6e6273e00c6db83704ca64e742a444a4a6ae89003239310d7e33f8b168e191224ccdad34566d24c10c5b1f4569f98ff836ed667249ce12627a3312cf18b7ea47c35318e1bb27c655bbb4501fc699252213840672aa53aeae15fd04f3216eb6278e7762ee21edaadd8285a0a50e6b5ea48edc55eb2b7fc2694e53e54537d46c3f9387a3d3ea64b3af12a04c9e746653f56d40330f0e14de211ba892174bdfac5661d45d5dcb455a5deed1a35cd7101f1ba9f9aa2c23fefb99848b165e51175246536a29bce56c34b98d38d64f98d9fc7359cbc61c8aa97574f384a762dbc0448269a29064652b00a0d017dd444d1a584684c64897ed5adbe88409614869bfb00d3511d7bea49e7be27d3b93b93bfddd3ac83e6d6d10bc5bda0a080d1bada78ff932206e73840e98a554be66a9ea1513c080e8d3512a9c958d410348d4308c33bd7787b3708dc1016382d3f0cc85137e84c57c1fffa93b156d06b3eb3fb1cdbf97a54d537e41164c011cda65e4464d803666f071800e62a58d4941a2eea85c196194f4f9ca69e8c72f3d84b11f96f0019903433d0c552b6aa035a3ec79c1fc82273b7cb419fdcd96d63c055c7f4e71c48374a552bcbef5da795ea1203289b1d1731c212f4c94f976df98d3ae0f83202c682dc53b114679308163a584b4a57daeaf15bcff21cabbb9183381202182dfbc07168dec49905bc2a683a6b583d002157a3d5ffca3814d84b8baffad0c77123af596b50a2408a7a91275807bd96ebcf6774e2105d9bf226abdee11468110fab3c17ce37eccd08f984b184c1b52b3306dedffbbd144968ea57cd7a8473daf0dc967ec1d4b61f8f2f08c4865116ea41f60abacb6526ed25f5637f11e223e28f77165d370077efae6720b3cb9f0b86fb7c52f33de0ecc9627f4336ab87bfe524363e4900bc4021b30f4633c5f546d740d07f04524a42c8d5138bcd3222208b6e8349af4966b427ce46b82e9718201b914ca19623d11a7bb26b7c0fccd92e0df2046faa4e54429870c36673d8a0f8d6a8fafca0b4cef67ac79b467b8e229c8e5fedea2ff120d447c0717000bc92628a80a8c5e7275c92daf615562d2c304f5193ddb079de14e3d61e02f6ecaad1f367b6a745567361992757927cea022e1f39e4cb8de711a7ade97603758606d99e20d382be754643ffd90ebafaa1430f8dead3da5f49e4ec7ccc36fa98d0aaa7106ea6882f25da0a2ccb04be5845f9114c1028b2b66d0b0b82aee8298920b2e149aa59abfc4b2a156081412a808f2ce90711d12881759be07e5cd9edf5a1298a856016d019169b918d64365ad9ed956deb53a70baf57f16225fa81dfa6bd124f1581105eeaa5d96461a3b139e1ff3757b1f228aad860e49f876cc4c1c60430d528822e3646363c36b0bc8a953f2f046243c3526c34a9640d0cfc7f142c85c252247ca0c777d6105a03cd1a40582a8e323ad31a4c7856a347e685d0fc7f94ff2d6f52638d18a2b8178e2b0c6990d5a981e5ffc5af08fc5c5043843412790c1c313144a54194864f1a5dd24099d228e151d898a95c99826158134d40c1c8dfa071c7ffe8698fe532e2d050a247d411fa3c9697432034ba3c181627e25d5c20101affff62ce30434ca9543e5f7ac2fc7063549c169d9f2f406154aa1409263c07fd4d6738f9174356d6e08b016f85110230636b2889925358190c3da28f9b51862065cc21862855194960a2985d62880ab91786b9339511011992907188898c399e7b29d11bca402632942083e7d5d302c72fafc0a854580ae8111c3ee791ee731ed9f2398fec7cce23a8cf79e4032626333a6f3223df6426843799b1e04d651e79539942de54c68d379581c09bca30f1a6323bbca98cf8a632416f2ad3e54d655a785399ec4d653a78539926399f63c1071b38d920c78bd29bbae0c09bba18e44d1aa0dea481096fd28082373991e44d4e1c79931341dee4e4026f7272c79b9cb441c1298782085cf0395a5cf2395a28f2395af8f1395ad8f1395abcf1395aa0f1395a64f1395a38f1395a04f1395a187d8e16429fa34599cfd1620b064d4e181ca2e6730e11fa9c43cc7cce215e3ee7902b9f7308fd9c4340f89c4330f81c432af91c4310f91c43f0f81c43d8c0608393063fac79d30f3bbce987d79b7e88e14d3ffc30d90090097c0e20567c0e2007f81c4086f81c4076f81c408c3e0790fd3980bcf03980f07c0e20f6730031e17300e1e07300c9f91c406e3ea78d249fd38691cf6973c8e7b4f1e373dadcf1396d2af0396d28f0396db4f89c36547c4e1b273ea78d119fd36687cf69a3e673dab44e135c9041930922980003092c68928797cfc9a3cae7e4917d4e1e247c4e1e177c0e1e8d7c0e1e7a7c0e1e6cfebf820a32780fa214e04d5180785314ef4d51bebc290a7e5314faa62829bc294a086f8a32c19ba058f226288fbc098a1f6f825281374121e34d50b87813942ade04e5006f8282c49ba0ecf0262836bc090a7f1394306f82e2c29ba0b4f0262815030f20c860021305165cc04193a7400449c018fa92134dfc9798f82f19a05480ff1201fe4b4afc9706b066cd1a01ac4962cd1a12046b48376bd6207184116bd6ac5983d3d2462423d614b1660d116bd60c214429883552a4f0ff6c6c5032438717405354f02207832ea8e8e962f5637411001eb890c0ffe73163f9d1c963c6c262fd135c70f0ff2512b6e0e349502ce026842d58da034c59b490cd498bf2cfee31e2fbcb3fc60557f602f44fba02841a4fe4a1211070f2ff432cd7d0ff8f1f27da1e3886e701288c5005cf03387840045840828506b06883051d58bcf1ff5aec5f8f4e0e5f3a3a2d5d66c7061bfe4b35fc97685073f45f322afa2f11558104066998f87f521539fc7f1e8b68d16302456a4cf2ffa5ceb3023622c80010f9ff1b33ae1f2bf070728923ff8f4385140e009341186ceeff935810000c5f9008e20062fc3f6988444688124250c58aff2f75e1582064881486f0c0ffdf6870c41c1b58c20062d4f1ff4c9a204024a43b5296b4f0ffa5412049a2064a9610e4fe3f8e13411130b17a43863ffe9fc42d702394628f31f4f87fd21ddd54034d1133f43105e505151434e6f10562fbf3d2048941306052bca03c725498201e823c288e42792e1d162bf3a0c697eb0be23c4bd0cb35be7807fbe7a178c73c97ce6a140247958af37e14b246a397e7d2e1fa43f115102aa8831fd7d2bf1e9e1f950a15821d0b0acae14b8777de6acce16bcca210c8ca181fb39431045722dfb925965002167a2fa129620f189daf07431d812eac078c0e90eacb1ad5595c1c3d8c8b9e988b44504ad098312b56b0202fb33e50b3c491e5bdba2962c678ebb58538cb43857cec4242192808f5650f95c5a020f10b5fe21766ce0241d71048c48dc69087a3ce211f47d62b4bc91f180c150a81ac2d7a524496977b0862e3cb85ed2ff31d82475fc63e509441a54205056179ece1d8feb2377e3908e3c13c974e0e5f3aa81cbe32068a22d7fcc37ac0e860aa116489286ccc528e58397ce9609e2b63413b431a64ed0489a8cf1b8fc24c8412b94b07f35c3a64bea03c57c6f8500816a942b0f70e6228cf9551dc753406002bcaa178c487502eae8b0e4f184cfcc1b020dcc7575087ca1e11b6b908ba8c3876c487b898b5609b73d186ff2ff36bde28fb274170030987920e3faffb2fe556fe488afc9782545f8a214d098dc879e744212fc1c0caad2f1771224fe8bf74c64c99fd79432532a531623ee41e698212d03fc92b85f929bdb0bdecfac4648fa5a33d17c8c3f00d7a1139cf32f2309cd5653ea4c1310459590c3991f789093367f18c9abb8cb8f0b543b53174cc1ca34aa58e008fd8a52e3825174a5c2088c364c4201ff2d1d3311961d4a627024b8c90e0ffa340492208531157fcf311fcc214d14311658a98520407ff44443208115efc7fe0474407449886a0840e91c310744c4b97090a6ae932e3104f5c3c74094189108208c185144f881ade24041493101f8422538248230809fc3fd6d265b8ce13e36abcd7886553104888477c2808feffa6204cff8f0a0016b47bb8eb08e888bb1034048aaa0f0cc28a46171fd29c08e31bebe0111f125318377f792bde61429fc73dedb5c00fd45c047d8eb80bfb070efe1f13fabe091ff8f8f7810bd187285ee41e2a101f7048d85c8df78394b634f9ef210794d0e7e53087afdc03971ea0fc4b898287438282c090ab113d226f877c8b39f020c1835282422e94bfec05f419834442498bce63879772e400a403174f42a603133a3c31e9e02487397ce51c8ec8219126c1811231448d469c892b982b6b5511ffe2d0f97951f92f8d70431a429f97e5150261297da5cc248918ca30c9bcee61ac8f0914d4f8f1228cb7c030184aec5ee24a7f39dc61d280d38d08388f1cf12115092511fe4b21348900ccd19737f88de28bcb7f0984ffd207a50e2ab9a28635461c2be250d4f079accf1b038689864750472cad83e697f072385261652f1c31be02b1aef50a359631916ff05381e3e7a97600435c8d61c63811d885600fdfa0cf0e16ee8540d8fe722ad5b55e21cf02585eee41e3aba51ac5cce24429f022354ec41015825cc0d17394c33fabebbcafc4c17f69035225a5398ea0fc632934de6484e68fbe9c41d65187f1f0681cf3e6afa0546af3afe80bdf429a93296950094901598abab080f0769ec449654ae0a10bdbe06ae42deca893a274818948ff7f2a95fa2f39f92f65f0ff3827473800c88de14daf25264a4a949ca239d11732c2f13c3c2987332202fec7a0ff0ff45c99afb094d846ac800c543c96e26abc3060c410158e1132f1a8cd31ee02b5887d5e18cd89544399170d654e06cb360c75a319d22899fe4b39ff250cfe4b17fc972cf82f5550a2e0bf34c17f49829c5293ff52041fc57f8949024c333879fd7151e42a1247853904bbe849914002092498e0cb3b186ae44763e8ed306320a6930168084afcb80bf4728805015a84e18502c02e628812b304137c20f68147d8a7930a527da0a7c61b7bf441f20191e7e166792bbd88a9d5c2bfc88536efa104818b8fe250efa00b042265613d8112b10fdc397ce94861759ef871918be04a63e10b3483a1c430bb5c19db2aa8207a612eca18dfc15022d683611fe87a6141fc158e4118383a000b9cffd2cd7fa9f45f225df24fb2845409181e161f1fd2b9033fee612b2b564ebcb712571470f1854c424afcbf5adde65e38a600c5ca3f8992ff274d72a58bb1f906351fc12f73e5516c796357a2affe7bf61f093862bc0c2cb53f2ef21eb896c752a118a2547b0d38625fa2e1028698f423623851d9946e9320b979a4044738d008238b6c40032445d4b0424ae48c09c041c793e630e5b124812943792c51744a7ca80492562530ffefc29b547b3c2605f4e951150003853c1014443ae49f64c83fa9907f5206fe49849004f92705426af34ffae39fe407898f7f1206fe4917f82759e09fb4c73f498f7f521eff243cfe4977fc93ecf827d5f14fa2e39f34c73f498e7f521cffa40afc93e0f827bdf14f72e39fd4c63f89cd3f898d7fd21a6a04bd4e8328869c47759e95290d1762885a21cd12ff2b31af324fe3c1eee95fcf50980fdc29e23e45dc676f9d22ee8326041836f00f6482410af17b8554ae6046b9853a0ac2c48fafc8e4cd8dbc2c186ad43c0cd30879fd13396b7b9b87de9198bb60a823d651e7891e91117fb582443108438951a41c8d50c4cd3f30e42e4c8aa7c59efd813b2d30ccf2338a5e1710680c7908f6e8803e23e8237a2c8e8c1b6390c9e27310e79d4ad018822e23ee6dde6a794141118c89448231628c63f6bfe85da9f279ff0fc19bc6741963c2ff9b808d19c35a5c14731891877beba85e9b7f625c99af5262aa8891422a91d03883740314462a0534c90b200069518561644413a2f9d074341d1a34684a40d3848426282614987ea8783144855cfc29c34d3f26fc7c806152542fe0f0c2eb05d60b31bc40e60530270ac59fe89a3f51d7ffe770f0272d41fc494b0c7fd202e54f5a487fca52c89fb2b0f1a72c0ff85396a23f6519f3a72c3a01f814b0abc19a67d2410459bcf1c5430009203742a62f81ec8f8f222f9ac25bde880a3a21019f80277541a4232a2d44f9ff346ff281e3ffb144655346100ca7702f1c4d5eca90e2e89f948028ba6cd145897fac0a86a1386b7baa0fc442b07fe922f4433a7718d8bf9e1f2c9c67193f1ee667e71da3ec0535a6090a8201fb3cbe7b07a71ce52130cb32090503573c186a7c8147b9c5f272178f58ac57cb133bd10b4f196fc450da6be5ef6883622e1abb29990affc0b0d25a6d11180a6162eb45064305056141435f82beecb1f26b06b0a807e31dd6418cb358b9254ae153424e048e01c1607ccc20eb28bbf290c87b10f7c5ffb35819e31895ed4ddcf8ff42e2176ecb202bf1e5a292c2973516365894f8f0d5312a54b00861014385c57a752c188f2122cf17cf595dfec4f0acd9fc7b75206bc7c4b3850a4fceffdf98b95d7e0709b07fde4e99ff29d8e7a9014d3b50fe9f0b6557d7a1e45f47902741c7c80b4f190c3bea44209d2eff2d6d840214a14fd5d2462dc0d10204501f8861a83c06041372220c1c370b04bd2211e345230ff3e7612b9d3526664ce444210f83b1e0e4ff15b9c2e60a15586a7c81578ab862f45256d046a9d407b65aa056895614792cd502c32c56f6f8dd63650a2cd5257b63c41015f2d1957756f52a94fc5771234c98918baa546af32a413f6915edb1149137661f51470c51262a397c6a144314e889aa14074ca93dbca1908baf90bb5e61449d1d15be84d4f8ff190b41cc0353aa051418284ca97f2c0d306d69a0b6d779e1145b0f2308864bfc7f00deb46d79226f85cd82476d2e8a2c56e651a144168b44a1f87fa3b1c5f5e31c51324cd7e89f85201282b0cf13fac0b008b37bd83922306252b0540b0cbbca64856c8f65520f0157452317c5f1059aea1462880ac110fc3c95eacb3a95d224d116d1f6c8ab222ca58da16d2101a259f02f1ef1214c62a31157e5f0954df38b69b230a4b911d090e6ab3104bdb8725864924f3c9672e5b0884b1f5ecc6044ceb37c628e429697b300897c280371f1e7681fedccc3e8aca0c60a0378717f5c44fda0421063e50e6469c1509b8719431d7d52b8909873c8a560c15045e03734e4b1781186ea20d6e21a6cb158bc08133326450c5f212862e20bf4c24cd4258b5fefc1501dc4505158acec617273972b875ee86517162548c4886c9d1877017b94f8813c23e8c345315fcf9006bb8cdccb08fa889c670933823ee290068bc02ee3ebcb0e03fae49dbcd323f21f9ea3d1d5e238abf37a46d0070b6775f91393657f3c8c989636823281090ace8b7cf3a1c5a4c00576421714b4a4b06a6923d5feb26a84143040e1122c25625254e07e151fc10f851754e03e4110d30916309d3087e98433c2309dc0c53f8649c18cb63785f38e4991826152b051e41b93824d390127abb2d85e498c0f282e822c8d198d227f09612e1064617c359291600294886163ce22c10428f1e32e570bd439ccad95fe78f7758c95bd6c264d0f456097b12312100b849207ff8e90a43c89e7494cfefbf877c384f17f120f3b80a3e7040a2860196685046ce462ef200417051450c0ac5cc1f6c71f278cc7c1020788c7e1c1d9791c1d1ccd84311182e20a62889a321af1548a7fde4ae7968a8f997be188618ff1d11343d4f74aa58cb211163002128fa58282466e1a61fc1b9c26a7196efee4fac09f5c88fcc935c84bc0a494c53f298b2a388c7c487be33fe9061b4835889c771ad41cfd938cf43fa9e89f44348a7cff935edd3f69fc2789e10c2ef06bb1866420793851a0fc7f0affa54b40d8ffef71d3078afcf30f14327d50c6a78ed4e42e1f24f1c118a5a5cbf031eb0fe4c146f083827d802595dadcf4c11321af4b6dee81121eb014f9ff29260fd6782cf582170c1c37a7260f380f3af87f51486c6152bc74e0c4ff83b9957744a02351e442194c185197f9e142190cf7050c07833cea881524a652e20b3c3a62714f88b3c4fdf110ec513c28ac3c6e7e24e5f3c4537a69f5983971f9e2ff73c857d05e78e232e64f5c7afec465fb1317d49fb840f9131710fec425e74f5c70fed413c99f7a0cf9ff08722ad89c05fa6c16b8e5e36a8a4016df39627d5fc470ff3738b0f20d3736bc08fad4b0f3cecdeb8f7217d0e767f45a5e97ff1b1ad4fccd11ceff8dd14dd1ff67716b2e0c07823f5cf4c93baed710287ab90562afee25f2a2eeed2c9b7f45d925f2a3ccc258798b61651e868760b6ce8967007fe2f9f9138fe9ff37782a8fc206db69e7037fdae1e24f3b477fda39f3a79dec4f3b13fc4927913fe9dcf1271d2cfea433fe4907e84fb48c3fd12ffe4421f0ff1748d013e668f4c688017dc29d7776de11f98fc8adc6d0dbd9e1ac3c86e1e16b7c6519bdef25e489a84dc5c35062c6baccca2b23be1a43afc332f67558142898c8793ffa7afe6f5e23962f3c3e9ceae7cbcf0fa7e2ba88c1f2c58baa8c8e97546acc42d90586ae2e4457843210988f0f8140acd706bde02b2d3ef60881acddc33f4fe8e358a0e149ff4fa21f5271a50c31441d752a51ccae1b305f3e7f5ecb0bb5e86db125e6a2f00aebffa60a95ff312e882a561e731f02454c4a1ef3165de2e6ac3ce62cff24bafd67c1f27f83efbf6805849dc5ad792ac5c3df1e38e64b95321e85429daa80b9c926dd3b048b443183598dd9c8ebbcf5126fa648f91f732b04b3e02a86d97907572ee69d9d77be54860aa522ffffa3a050282818f7734a653cfcff349ae5cef4845161e1cef474d1e9f9e1c2a8b81f31403a3e5a7ac2685b9affbf4101735846901546b3afff9b139e8c202bcc8d42e572e5546a93b5ce504ca5542e4bc73f9ef2f5e564bd0e4fa9122b1f11583955ed9ff5ff2788214a5485dc55a1fc3faa62ff8fa71413459322fe7f4ba5302c043b5544abe1b532ff27ed8457b5c050e51275ce1ced88f1b07c3ae14b6bf0a60412fe5961c86990c5d2c1fe6f46b811e112096e1840fad295120a243c9e24c71ba4351290812086a89d79f8a7d30aa087e615f6d737762f520738704820243fa688719a8280294884f0a729abff67b15e1f96e2aea3710a9629a939458a142950a64830e5a328d27ab1408d61fcf33e50ccad1668c457426018be44fe81ac28fdf3a6ac10002c04319fec838da010148c8fdee7754ce423c8878ef850942c32cb5e68a6645996bdd040c158a0d097c3a0126c4823252815a4b90c1d887d39ccae2c5fb821fc88f241390ab32acc2bcde2963099208a22a72848dc847003c2ffcd07ff371edc748003207038e243226ca2c0e10a1a46a872c54871206243881b26201105879dd40bdf0e160003070b54f860109c2390b8410f2041409823bcc00d5254f1854b0b30047243ab0f17aaf801dcdc7007a9b2e65f4fdc700342251b131ec8c28638946083260a115d6c304011932c48b032810d5a00436578b004151b5600734547a291486a90c482315f888fb051431a1358b1061a2ae01a906812c4901c401fa8c14c0376b2700044076a78e2431c0850c3444503234314683109ee42031858fee021004564a1c1879b0c9482b052070d60e448a4a696c0000d1e648023c10366625043880f381f48d182831a2daeadc104165743c3104204612c40355924a092c3143244a83145414917e20ad7111f1a1b1314a9f172c40024a0a0220036568e420bbaac404514f2a84a2019e030d21d8e22f860070e341d2019d5514375718941c8e8000358b34515d91446420e48450172e40ba3a9013624e49c214991253d2d688520004d119b141368287243164503e89c3c31c644a14875033465121e5c4550e4201a628527ce10396281004841840d1a109161c61f32a8c96110052144c806d8f8ac400414468432515c218208dbb9018b2e96e4f142e4a692409cb8d1c88b0b392cf1296a52c62b0706700a04d0127a7151c01d7f808100052f0dbef4818348469511900ab498675ca2472bb4e880081d0640c0f8aa230e31a31a0e469dd70c7d74808d939182454028001906fc20e65146102b8e7e70c404b4f1a20436c63944561363e084403a8836906c0e18882a20967c8a106b50511056800b0d897c405a112ef1b51184128f041176644490009e0edc114eb12474028a46c2cce0014bce00400745f031031a53f3438d1e82cc6084176054928499a119ca086005a9e364cc0c2650718219df87c9b588083aa12971886b025150d0008d2a805c3d60896079620d8f0b8c0762e6b06193c3d5011a62e4c0060d0ee0204f14a0022d00c00166610337c3939315504dd7a40b263c2801721fa454187a8208cc0944890d886183940f033824d8f081fcc737c513852c31890e387c3370f9f9831221263e2a558840a784477c4c48d0e20c21cea8a045071746542a71b85a4f0001200c01089281567780156dec7c49a435491893724234815549c9042422f9e2b2d8d00e69610d49d660090009309ac06cd0c14ac3451a348ca86007560a68dc6029b052871c29bd1103b85518a2800c7e548203932a86d688c141cea13086c21419e0112863b2a1114e30e3070630d24506431221611f8a9840862d16d1a6dc053491010742b02d66988832f4f4d0811d6a868e90c189f7801d36632af0da000db18a024b1e5e15d81f165420d5883716c2811e7a7467bc16be6873c5153e10f0263043480d165cb458e121e4c32463e0716695801110d0598265cb6a88bb712b4959b0aa893491e286127aac486bec20d3a4280321382e10a191063c4944a8003344e08c22210421941f3102022670b943480a1836100201961f7a0726590211190e80fb19dd1f1b08230b497a11325c32884a87233a993570781181382ae825d8914724475060856f60040a9cb9c128f32f3ae01a48490ce13c68e921e7113614e13e7bd8e103243ae11c60f1da83e7089e2dc8181e68632bb01b020530d3c56833661f714e7eb22d8c3696cb333a72c61a3b474af540941514da026dc694d969c112bd802d8cb2382134b5eb88010c41c00733740a871f8e1029e4d0104c2a2e22864cd1cd014918d522580f9d1325887009217f47c7b92084891ede019d4cd3839a391e883c091b3b3cb08556415e23c523e5083479e424d8d43158b0c128a3118340bd31c923198533ee3801a4048d200e54a21ecc2c510a1aa3043229288d1c2008089f2b5040a4a045d0cf14638680e2e791a010f020c3bd21010da80ae9e38f2109548da8b6d8809045525ba4a0ba8108057090269150b5c5b5c3d12062c0a0caa0888a02530008c4f0c716310028840c0e3138804915461338c111c3088198294118c823861670b03cf1528315314810458b4920f994e68e1cc8e02e3c78a589a25f3125cd03d248238395365b0830482b8d668917267e802193e6df70d5e1827884c68d3f3c40e4f403031a02e0b0250c1d0cd48026cf280810474f8626003d682508428517183a7083c6102f302b3098f104973069646009188898c233012a131001c318219e5062052a0a80a1842636f094a16487338a5ca083008629083823010d3eb8353cb9e1cc0e455b24c9429867bc0cd100483a60f3e5cc0652289286932858301388216378557400879907e8ac8001d111589831d24e982292b011333c681049640a1433cc5c3089c9688b4e2db3079b0bac1185a6421905d81108ce214a1865c009788ffc8e1a65b64c02125f87cae078e142a469162133c79528909ca105116498a80483450e60481b32db0d11850d349003992c01907c91c1933ee6034f8861e68b9495316a70c9b4aca1842563926882021475e4bc31068616acc0c183cf69cc09705862a5ee718198465c6fec0064c08298300ab1c20e2311dc10f3c3cf227c04712085981730e00800b6c862881810062082105fcc200265800f0e3e491e410314811e2c4e50020d1b806cd879801440408102d2b21aa28badcb094027302448c3864b84307d74e0d5edb1850ed38048d21882059a2f61c4291b68808c0d1584b9f200221ec0818c4598264328800c283e4f7eec90e2103e813d42f88162080309a29861e3c72b216c81f180ed47fbc0080aa01a61e3e70140c11b65cc60bdd086187f58614655f382124b7430465381cd0b41468098d010d37a410518485e40208606309068d0442438583a3065f8a1c100ec013e002304258bbcc0c8242a30629638010329ac80031812380904e203194c7c49440a32e6c0e31218be78f1011c0d303101802f3a40200f452e7062fad2a5cb1019f44ca08b2f00508014af3b9448c227101074f0401f3c0df0b9e2031e1104d82af029428191471f6c58f1d919828d158340b17d2cb862cc923200145ef418230a1d6cd474e0458a4ac2b8609b448b97af4b4390232209bde02f3787b02009245e6e420f889e93e8224708af295c4f7cd0c50083c41082235968d0451f32c820673882a5cb0a7c24e0c3f9a2e58224367869e40041645c48635f393388970c5c40028c0c048c1507b870a6834364e880121d179e0002c41a276dc4c185112e58ac3ebe2b5cc000438b1302d89071f121d741d44725657079e1830bd4b86137c1e583186e68e246ea881e4208915124808dd8a3450f296cc14514687a6a6032852240748d9e2c43126084070d027a4c8bac61460e1bba6ce123027974800336475b180089511a6da2c85bc2341fb81e7450c8162b3a4a2368a099624b043b00f1c8135c42d052471c76a800800468a105011b7cc143c6fb40cbea24891c4798205ae84e0ed9f0b243164be67002913d6ab8c9c28601829490a58d27b20ce08b2eba80e0db208baa8845d634c9e249962857cc6c51108108f7886cf3810ce8410257460a85ac30c249072e080bcc3088253d9438202c1e91c0bca91c36840a52a09492c0828811cf91050ec005962ea694d800844b07b0e4c0881c4cbce021002c2e7830481e812c02068b061008c1a80b225ae001a4cc20414958791e2bcc6809f9b0791e228d8c0f7b44f13c3a681cdd36393c4f054390a0a0fdefe421461a2220f1bf2345f604212dfcefb43c50428abb85c890ffdf2945a1c22a5001be841e3de8d31c3aa81e7288440634d61cd182996fe1c9b3c0c8a4af1158ff989411d0fc69842a41df8490ff4ff32711285037be43ce0d70122187ff9308ab073a89b0a5f5faf849842aefcaad9308524e2238f92fe2df490445fe4f2194398570c763a710be789dc3bcd2a7100c1084855c888f9f770a61e814421714847d99b340b03bca61984f2090f17f838105ef05091040f800e8c33f73fac000ab31bb56381a6f2eb8b140fcc1566376612c964e2af56f42584619659481731b4002045b9c3cc84e1c0c72e2204c9a4937c0c2d6b9e5b4c1b8c1f8ffa25136c46903281ee8526d6ee475ed8dbcb51abd30af70da00e7ff65c0030791c9881b300460bb4a59c2a0810376686025e88d3cfa50e38495422069a10c251040481523ae082730848c03a85145933416c1c14b05175884028e5cf91191e0c34a6800223ca0b132a4b8dae081ab3c864c395a63001ad962064b046a8865403083142db280a4831b8215521c4da8c18027c8b0f009b1c3255d82bca8a1e3001210682ab018193031059a282c902175650455133c5849ccb863043df22064e8f8c0004e907281cb494ef603217cc871830306091a4458fc20c4ca974b8cd823ca1b2b9c50e5032512a046070538faf8b9b102354646c10c1548a54e1ae09313362727588c21d8e980b144a9d4ff959598c1ece8ef48d4115b429fa7bf23311f6516df5f11b85920a855add7b7c18fb35447a16af3495fabd1c5f76905fada3da994e9052c4e395ffc09a79c2ea79c7acab15205c3524141d8286617868de077c2600d8ba5f37f03c1dfe0dcdc7c4b97b9c2fa748ebac73d3e86b9a0e74f1674395950c64de9268252044c989c2a08d2395590a3e54f1494f1281696f9e8e99c2858f3ff28f1d59d28e0b2277d9d28b8e0ffc5fc698209a0c48c49e9c1504722177536e7e26af4c84c702568628e193feee94f478ee13f4d10519d9aac3935493d6a14b990d04b94c2c5bcf23935b9f927e22278fd90985b975c3224e691e58ddf6b48cca14e93c92b10d4d1e2630fd813827af78069f1b187953fd01387c40cc40241309cb57b54ad304058f0942f2d7a5e38cf7223c9bfe9e6e5b9b22a9b280d954d11025941e2f752110d05617c357ae194233ef45ffac07f4992ff52241e38732a295194432a51d994af080cbb959857a7920b5b8979cb9f4a3b4c2aa5f906f5a4af2baa90bb8634c8aa62945b57267d5531caadd35f2266d67371fa26ef51095285a02a28ec4009927fb07f3d625e81e161968f0f81e36bcca318a25899952d39f38f42a1b011e42c91c589522c4ec459aad44acc2b4b9880e098e262c889788885a8959857e20634f05f894e255378d260f12883e1bcabc4234a86d82cee99f9c4e41db07f3d5ec4a30c06f411b9190abe4c1244e5038c7caa908f9e4a7397b882e6441fb8e3ff254123490b1f852912ec81a2ffcfcbab2b57aea452dc0b47954bfc40954a486c7dafac1a3f2e4efa820482312e4cfabac237c873a58ae663542abe415dc43f95b87b78c8b770d087c57a65c963de1c98a274812f59608fcf63c662e58b05829d4a1cfab27b267d355285111918c9b2089845d82c12c206b0060ca0c8218a78ff4928d2e47f911d940213a644705ee56a852b4458ff5f9ec40535a4730786a00f9343b8281d62f4433a04cbff7f6688212317b9ffd21dff253b6af82fd5f15fa2630e394a7114a208bf1245687b61a703a58ae663547a86966a86d6c6a4ec9e1c663055c617a872b5c295cad552b95aaf495f4262cb9481179e01842ce0ff7fde44481e24881f38821fd8fa71a30d423810248a17a407419a047204c81c8010d1a68cd21aff25359ca4f15f42e38c3fae30fd31c49bfef0e14d7fe4f0ff4c7432df92c30ce69fd4fd0b71ae56b885a7c7d50ab78c202b0c2befcc7ffe4b6590f147e1f8713bf31f2f2308fe8451a952299e1f0d4c91011f78b020c52184b4418505aac0a001131c9a743181a90b3992c0c25053fa1cf102041f04f9e2040c7ce8c204094858300970002eb898c2010e0ac0411c783800cb0d75000961c50f267dd4c00725260bf460da83cd1e388226f478630426ff4df4c0ab37e5c18688c7527980792c950708586a102c85c715580a8ff11f4be181e5b1141e39ff58ea8e3db0d41d4ffc63a93b56584a758714d08b044bd9510696b203e8ffedc0b0d47e531d5cd451039632d5c1011d6de8a0630b1d4e1e4bcd71c53f29037300c9a1084a0cf9c85be087a576de793970d068e159ac179938c6080a7a1d06c90830c64ca0f485042ca0a60a601105cac75998952ad8f65a51a04480b492c03f1bff252fbaf837e080430c515c6cf15d74807cbe9ce9f9f9b265fc40355e08ae82bc88f151715a74c284e1bca8b0fce8900123465546c74b10e7fd0d40dea8e38d32dee0024be5964af4302cd579a81aca6f44f158eae7cbcf0f87914002096fc0f0c699375a5085aa155678a3c93f6fbdbeac553c7c891f77631037e0f87fce52ad545ccc60911b59fc833e6eece0c60c2856de9f3784e1ff770373e3db98038eff1f73876146d94b1b54fcb751c3ff07f5bc46166b3cd1460bff6d3c51b5f198152b56d818c2268d2f02599df502c9087d01b77659c6d9d4369ae515155cc6956e7e7795946f7763520338ed6a9de59d595679ef775b198e222531809b956a95f38eeb6675a6f3284ec1699d55ad665ce20d04bb057038eb99b67a9b59b7e1cbbbc7caa402b8db71d574dbab7559eea616a5e0343c29ddbdf3deb1bd1aad09e0b44ab5d53a9bb5e67b33ae45c169d556ab7395566ef86a4da1e028ef28e5da669ea5dc349f08e0a4e4bc5f7ea5e4da866d9d07e06cb67d629a6d93f29b59bdba52ca4b7a82d3aca6d6e68ecbae65cfb4e804d7c99df3d69a4f8da7969336c1719e6bb633aa55d9b5aeddc904d7d96dee3dbbc63b4da7c62a959d06e0e8cd329debb5f7decd62b9ba72090e6b9cf7cc5a7c7957e5365dae5701389aabe4b3ebbe75186323be52a92401b86a73ed9c576cafd497d2d5957c646525382d25df36dd342dd36edfe40038da4d2dcfc9b39ae746e9a69104c0d56aab96dd6d579da7e5564e1d52125cd63bd39e332af7ed3deb5657eeec4282ebb0ccb28db9edeac6e5241dc175da6a58e3aa61aebb66edea4a11dc9aace1285a92115cde3793b3d66e937dde22fe5114a9082eeb785f49efdef9cecb2789088e6ed966bb5cb36c777b66beba9295c33944cf1dafb7726b27b799b6ba124ff9528dad8e49427077eb24e75ad6b65d35ef1d9e9256152908ddf1b6698ab76dafd6b9665f2a2e86d9a5c9d6eba33ea4355c46f7c67bd77177b74de23ae91612101cdf34ad67b2db7925d5c431cc2ed5e6a36a67d726433e6e2c907ee0faad16db8d4bca33bde1ed035bad96b7cdca917ce07ade66a7b7d6fba65be26c14bd2e1ba9072e5f4dd3b6ceadf54ceb3a3e728c493cdc296fd775f9da6e6eb766ab2b6bc8bfaf470c6907aef22a35bfd9e676632de78ca403a7bb4dcb79f7cdb3bc75bd9ce57a0165a41c3899755ddbbe37d67ac5545b5d69ade0c0496dd334cd769db3a45d0d23251e5b9ddec065fb6aa9c999fbf56ce06cc7dbd4b2aed9c4d996759d9dc1a0506287adcf1995500d5cefd26a5dee586a79bbbc73e5b0e87b5169270d5c5773d7699a6619573c3915b7ea734655468ddd2a9f984ed93bc65ab7f311d771e614638db5dae1cce28d8a91ee322c65763b4aaddef44b11b7d52e6f1dd62ad7b68d65df9f57f4bdc08f88ebb89e35677dadcd5a530cf6af477b5f06834281fdf354d7e78caabf66bd9adadbf16c6e5b27b9a6f99c51e991ab986f5bb659d749aeb56ea9b453e42cc6b7ebeaae9a7653d3737555a237e5167252ef7ab74c77b452dbf3b6bad2aa722854e5947706aeef3cef9d1bc69add7be7d595a31157a96c5e4df1867915c6c5751dc7595bbba736b37bfbea4a554675408e4b6efb9c5df7bed9cab3d5952ad59c2c16d7dac765db5dd969d6cd6b37cc2fb53e6754685a1ca5b2ab9d76a5a6aba6558942b1585c9b61717d5bed623aab9557f7a9adaedc9e8abb8ec6a34f07853afa3210f539a3e286b85ab37db5aceb33cbd3561464a894d3badd7ef5de72db7656893cceee6d6f926a5d9f12679d35da3d425ffe41a15028d105aae4f539a3da2b4e6f8cb1a55bc5145b9aef960971b6eebe49aeb7ed6a2deb74d2d7acae96475baf8f8eadde75cca9c6b7ccb9eb249796afaed42417ce6995eb2cf3ababd6b7dab7e65b058eaa2ba96c2e5f9d65946bdc333cb5beab2bf7f70243d591664673f86e5c52b9b7d45657cc6dc76575669a6b33e3d3de6df69ab99b2fd7b36add7618e7d105aa3659bb0471badbb4cb68be16e3ac8d3a1bf19945c5553a67bd1dbd9766de757775659512039737e536cbb6a6699b96f35d5d39f19686cb584a9d6787f9762bd6f8eaca51e455a2e1b2662dbdd84a6d9b36abdbea4a15ebd5ca64a6194d2ae4ed60db7a7d307059cd6abd9d96694b6f27f3ea4a114ba1cf1bdace70b6cf8e6bb9ab92f70c6f7275e5c759fbe32a6dd66a86b39ad6d17eebb53dd3b3eed595992c025d2ad6abb5597c5baf6fa70c97f5dcb0a6d19d37ad77aab590b1724e29379cdd2cbbf8ce39a7ec6eb6f1a639d572d737de211f7b7850a8cd3d17a8512854969f27990c4717490163389d4d6b7b77bbac93b472fd811bd7df972f8bc5a20b4c89e1b6bdb3db4ddbb46ddabee9d595630876d904206ed3b6e35ac6bacb3a3933dcf98fc8c7a39dfe249b2f96abe55d2bf4eaf87685d93d6f7d67ac799dfc5ad9f3832bc65dcd5cc31ae769575752175e98b4ce29b78a6f96596ca7a458676cbbbbf56bf9b6199d920b182eb31d979aa6f96a8eb5c67b839bc45f38d965ce70ef5b97554a6b5f5db9c1940f87a79e5ae6aab1d66dbaeebdc16cf3c2ed8c761befb0c5b26b5a73b675e1b62e534eb1d6bb8aa9e5747525cbd5f2549b877925e2eb0297b36dd2ae7bb75d9bedbc7352e1c2e56d675d36f3bdb69bfde695786d0f87b59678935daebdebbdf38f137115d80243979ca92d5cd7b56673ce7ae75db77b5d5de902c32d332d5cde92d62cc3586b736779aeaed49c48058e7466e13acef9b515df8ee79c7559a5bd318fe294d5e78c4a721cefb49a6937674d6b9f1685a2351c5d64b0703cbb9ada8e4a896dae1b0a85abd8d9ea7346258687db986f9b6bb65549afdce8ea4a292dae62a7f99c51653b1cbd99cce4cd19cf89335c5757621e823c611ed3ed68f2ea70b9d36d65ee995abd7558b6c065bbbb5be3bd3ba75a66b75a9f33aa1858e0baaee2dd6d3b77ba35bd67efa06aeb7285ab925bbbc9ae75adf2669d15aedb3acb9c6fdbcc33a393aeae545169add49c88f654e1f0963399d56d6f576e5defea4a950ef3a4fa3b12352c5438af5def36b65b959a56f1eaca30af74268d525bcbd5ca2da33acb3a4dabab2bc351c4d5e78c8a68e370ede4d6499be9cab3a47b7565fd40160f0ac53f90a533eb73460503e632aee9d6aebd73cfda51bbba5235a51d5b9ddacb751367dec98d5e7deb26efeaca4cf20d7e5a9552c7b64050c5daa14a2541e5aaac7ae7cdafd45bc392afae1c43dc82c66dd99dd9c699d669b867bd5757aec4974b1582ddca2e94eb1ab67b4fad9bd8e6abe1d595aa8b2747ef95dbde74ebb9d3b66b5757627925a7f58cd2ab5d8ebbedb2de56571681a1904ace8ce3dd6ebca9ee9addb89e7775e5feb29cf7099096273a4f789e0015e59f4ddeea73466566050e63dc3bbd718d37c6d7da27405a9e60cdc7a0509a135930289dddcc2fa538c35a76bca15028d4d4f814b951be697aef9c72525957575e1a2485ab5db65a96bcdfbad98ce3d5954f80b43ce16a3cf1f504a8cb13d5e7d9198eae2a01d8f0c5b3d2bb4d2aef866a11b158284e4f592bdedde6beedcc32d5c6472f2453c756a73c04758f0a9c96d9cd3dcbb7539dd16d47e1b68eb7e41df76cd377676d75e5d8eaf40990962742af8e6341a1c65647a178086a313fc9e6eb09d0932c3f4f9e007579a29a9acf199516282af115aa541ea4208d726b0414a40a640981ac0e4ea0d993144c8052021412a460298ca0820859085140c03e28c183199ad081ea040e660bd42a1eaa5472c8d6e9d25cbc1b1ca95426006073950a1ab49ca84270cc1948cd867032028b46114cb64e2c674a954ae8f356625e6118947001eee32b040b8cf84aa5d2a2d702a1821428103b98608260e88104433a772c16a842939d79e841042230912a2ed4010452c4915eab0542b9992594c2cc59fa0392541971105e851fa030d10589b7f287038589442aa0e0ddb0828dce77381b4e079d6fdde520550c69d0c070c64c193263c40085f979c1c74b1717b8f46cd19285c3c2b3a373d3c20d0b57fe66febf0a97fce9a6c99f6eb0446e14a1e0dd68001f3a3f800a469881b4812aff240d5c803406490c013c49e39a800f2651926025b1e594c4161deafc3f1620b4dc80c31a247ac86982e584c44760c5e988054c80039e8c78040a04fc3f01c496f761cb187aef6527f3eff3302a133b01fb38ebe3ac7a2a020d51071ec1e22120ff9940e288310fe63f90e14f44644c3630c419ff3c0bcf3fced77a1cd6d08f21c883238387b37a30ff3842fd1f8777f9ff26385b85a33fffe3043dd862fde3a87062380501654d114e1a34ff8f04107ce0c0f0ff4d70cebcd0e7f1a13caa58f907ec079c930f4a9c7600e1a443d0e720c7090729271c72e60d7adc0087993264fe710000040404043406470c90680368c3fec709f3f3ff02982f2ee470e1e2e385cb23727471e1b9fcff8bb8cc2d63cbd4325a195a6696f9c7e9996ad85023c6162d59381c2c3cff383bff8ff338f57f7b2a9c1658e069e932ff3791e05cd184b032a50a08537022d06ef4f080ca3f4eca4802461130c2c2480a23041831914a6918e7fee3d87a2abae31f070a9d325be11f073505070356ee4096967f1c2963985dff38016812be586036cb5bf11cb1be2f79ccfb1f47852847dc859b37fe71a0a470c487b279c435ebfde3a08063c20925c011f221ef03bd8cde278612a552632363065ada68e42cd66bc703f5e83dd27a7164400d6af087cc3fce1313fe874230cbd1ceff8d1c25fcffd1ce4d2424e08cf08f83e18cf08f23c23f4e08205c60ffe37c40ff713cb0238e36420a84609cc22e6e1ed97cf34f0c0e071f58088bf5f2f9b21730a398799751e71f678316f88559c68f772dee856b0f1c7314bec4cc43a00ff4bccf6b6d1dceca9ef6f68b0b27f284789795e6409c488fd1fbc4e456165f2ee000e08f3e9d39836906262d6df48fa381930cfe714e38a61c3930b8b9248ce6448f73018e05ff381550f0ff4d2ac189a0121c2638105482835309ce8d8853aa0487b4bd918be225bf2d49a5b44a2aa9a4924afeb3e8d0219dc730917056973f3160401f56e658401f238eb7321759390b8bf5cae2d219419f2f932895621db13e574b8c2b87409cc813411048e43c8bd0e7f9f09697592f30fcc48c21e852e38511370bdcc2595d1e419fff1b0fb058af2ce22b1c3f319b7f62fe6f3a00891e378f38f2ff250ce6186f0d2f8cff6f69236d0c1d23ca904a6944a9d4ff4d239891bb88dd40d580a6084d6496f9bf41e4907f598687dc90423240881f83f821c89aff9b407e7b807c9b29b48410117f74206bc70f3ffcf0c30f0c6c9647e6ffe6021ccbff8d05f6d8fc0b8ba5c7e62c561efd0b8ba58387f87f73c7f7eaff3776dcd4f1dbeba1e3ff668e0ff419b92852a213173a713437724c1a879515c0706847397cc38dffffc488622aa551a2546a6a9428959a3da914254a83afad1a9d57d2488c0a0c5574a85345f33157c0fef588573ede25043faf158232e4d6958f77e1adec6394bdb4f297c7bcafec9eeda942deed9e542ae4ae97d8023fbe5be0278ae2667964ae54d17c4c96efd5778ffeacc46398c11c6d70670cb36bf71c6d70e7ca9549ab4c2a66143f900793825f55f0942ffc4a93cd233ea4d9d7f6267d050172d304137cf4c2a29d79f87f638025fe6f0a704300cdbe26912ae8064509e2200a96ea60e721141503be135d227e693e46f45c3aa2e7ca421f6fe54fc572b53c31061a1ea808e451894f8a2406094e6980f82a22dfe0774ac381962b68d440d3e5e3ae2fa0cf098635679cc02f336dccc081e571be9801225122fec55319ecbf34c210efc3894c14aa1c74a0a1063250fef3281a9dc6f0f1e018d61829ff46fbcb406d80be0055f07f23c4ff4d10ff376b80f8bff9e1ffc687ff9b1e78f8bfd12187ff1cf21c72b5d65a6bd5344dd3344dd3344a29a594524ae79c73ce39e794524a29a594329bd9cc6636b399cd6c66339bd9dcb66ddbb66ddb368c31c618638cefbdf7de7befb5d65a6badb5b6d65a6badb5564dd3344dd3344da394524a29a574ce39e79c734e29a59452ca2ccbb22ccbb22c93dbb66ddbb66ddb8631c618638cf1bdf7de7befbdd65a6badb5d6d65a6badb5d6aa699aa6699aa66994524a29a594ce39e79c73ce29a594524a2933b9e16bab460318e066cebabd7d76d5ca2969be00a7b3ecba677aa3db9669335b80eb366d67797b27bb89e9cc7b705da699a69abeb5c3bdcbac07577bb6592bbbdd76955ce63cb84ceb5a6e3777adf9b6c98c0797e9ddb1ccb6ac4a2aadcc777059dd5bd6f149f5bdfd66d90e4e5bdd65df6cef757079ab39e72d35d9f1ad753a3839f3b619b7ddd2de6d3e07473b9ab37c6bc7efc6319783d3b6bcd90e57cda5ee57c7c16d5b57ae5d9e6939a5a6b70a707267be69b2ab75eb4dbb0d0e4eb395529dd5a9352c69dedee028cd7ccbb8ad9aad58db3637b87927c51b9e33bbd972bcb5c1e16ecbeecd5d6633d326ded8709a524cb1b59b6eb7db7763839b58dada5dae499be9aedb1adcdcb6ce566bf56e586edaa6069731963667ddb6b5de39ddd2e0f0ecb69c699657aa75463734b8de6599dc74b75aeb8ccfed0c6ecb6cdd74df14f77bf7dccce030a67deea975dbd5369d5b199ca5dc72dae14d37dcb9dcc8e0e6a6f54de74cd22cdbb0dc28c0ddd9b156edd554cb3096db189cdeb2945ab6d53ae5ec5d0c8e629c6dd56aa71be5b887c155bbbbcb73d637eb7ddb60701ab5945ad9dd7b6fa57b025cb76592f2cb35df1867fb0b4eee3bb9964dabcdec728d25c0757ceb3ad34dd2cbb5d4d80b6e4aabfbcd73eb364c7787bbe0e8e4396b936a92ef6c76980b2e6fbdbb3cf7cc55e76ec35b7055db9cf55a75ddf4de174780db946f9c49cdefdc38be580b4eca0cd79bafcc53dacce22c382d6f9d627a7bd7f5de2a8600a759bc352df339b5dde18a1fc06d9bc6f5a6946ab3e64b31169cd6376f2d4f9d759a6d135fc1cd5b719658df8dbba94d6c05b7e94dbb399bbbd6ac3789abe07495f2f22a65ef7ab3123b809372e20d6fb74b9bd99e612ab86d2bed7766bbd352c3196e006737def1ae6bdd96bb4deb0ce0b6b933dae7bd35eb6ade53709ac6a5ae35bbd2dab9ed057035eb7ae3cd6b26bbad6e05705477b6f78d73cb6d9e5b0a0e6f934eda6d7a37cfae4e00c771c57c6a7a37af9deca2e0f4cd726f5a3631be5ddda0e0b896d9deb3dd689731dd10c0d1ae719bfbee5dcecaf13d0037a5ddd7f22d6b999df6de27b84edb79ebba6d5e27b8dcf54def5d7967e5c66d131cc69ace73a39273d9676582b355d335eb6eaef8d2cd009cce7a5eaa6935bbf95e6d090e671995176f38d3b6566901386977766f3771be1ded04e0e8bcf5ca5e2fef56cfa904276fed1defb2da5d4efb00389e31b7b4ead94d9eb500388ebbbe1aaf99f76e7649703d9b5be5936fdde16c4382db99ca6d9399b6b599a93c829b96decdb73bb5a95d6d044731cd18732de79cb599dd2238ab759dde2eef1c5bd989e032baada5fce23bf3d64370b7bb1c4bac717b2304d7ed2ea5dd7a6e4dcb346e6f82e0b66d2dd65db6ddd6f1bbedcd1aaea398667ae76c4b7dbbb63740709de5527659b651bbb3adedcd0fdc9db672dd499ce7e4b7b3373e705d66f7d2aab5cedef4c0ed6ccb72a737dea7b43ddb1b1eb89975726bf9d29a6daa657bb303b76d384bddf3ce36dad9cddee8c0f59d3bad4a6dcf8c5b6db337397033d31bcfa89dd26e3a377bc9dd64b6b186b58d2de1a886b194dd6eed56acb54a38492fe61bdeb4c3bb7694128e565dbbbcd672996d9f84b339ab96669ba6f52bf5fc0077336d6bb6f6562d379d92703debac4dbd377d71ed32126ecb6c77e636eb9b67951ee0b68eb36cca4eb74b67ef00a73bbced59bb2d2def1b245cefe89675d69dec12cbcc3ec2557bf9de68a7ad9659965947b89e513e6f969de33e39073889a7ccb863ace13d7123dca41c5339f5a4bb4adc19e16a96dcf29aed964969fb221cb795f32a3b29b7e67b039c9d536e1deedbaeb7db34c0cd99713c69d5b44cdb4d11aed3ba27af55eb368d529e08d7ef9552eabd5daee7e48870d86ed66a54cf3aa9d60fe16c9675bc5a3ee9cdba6e43b829a5e5d66e1bbd999cbb10ae67ddeeabb3a9b33abbce00a779cedb36358df1d66d1d219ceeb8b4d9be5bd7b3a6dd20dcd5dbce9a9cb367dba66d827056b35ad536cdb4a6d5b905c25d9ceddb6d7bb39ae70d03c2e9d95d3cb3ade3d9a5196ec36df66ab3cba9f3b59dfe83cbec9e77cb769e7552ed075771dfe6cd7857b5aedddb07d775c6b85e4bf14635bd7c7032ebb34b4a71c615530c7012d36e4e39355e6996f6025cdd5c6b9d77b5f7ad336b018e4e9e4d5e379bedc9376d0f4ee69cf3cd7ddb9c655a0faec3b94f5e6f96f39e95e6c1ddae6bde32de328b6946f1e0ba6dcbdde6bcb37df67e07a7e58df699354e33cbb71ddcad53d36ecf32d7cce6590797abb6cddaf79c950e8e6b5a97cd6c536ceb95b2cec1758d354b73d559958393756eb36b33935d27b7acc6c1c92cb336ebe49654e3b75780a338ebdc527d67957d6e38b89a7bdebacb6f3625dddd1b9ca6b5cc25de9276ba6ad90dae6f5aee76ee2df5ec66d6dae076b73bca7bdd9da4546e1a1bce624baba6b7ba33aa69acb1c1d92ddb8d6b58cb8ede8bb535b8ca6d2737aa77c6ddcd575383d31ddd709dbd6f3ae3d86a697099cdb8a6a5961acd19b61a1a9cc6edc455673df194d56a67703ce32b33dad13c3395553383cbfa7633e3b777195359b532b839338bb3a665536b8d4f8d0c8e675a5727bf39a35d66a546018ed739795779c65cf32ab531b84badadd652aa6ded32d3c4e0322d27a677cbb0ddd9ee61705c739bcdbbe96aed963b181cc77cd3a6beb76a5bdff804b8aafbc4dacc38be5bbf9b7ec1d53df196f1ed5aefa4d554025ce61c671dc699d6b74c3bea05277b97d9a9efc59a67bcd12eb8aecbae7667d76f8775b6512eb89b6d9ed999ed5a33db31dd82e3ba6ab9eb2d67d52cc63402dc262da7db9452db8db74bb5e028be5b675cdf2eabc49666c16dbdd92ceb6637e9d6ed4a21c06dd4eedbb5b6dd9a71d7e803b86933596bd57066afc627c582cbf26af3d69cb3997597f40aaed3aadda89639ab97ee8c5ac169ddb74ceeda751dd6ba57c1757de20df79dedae5edd1dc0653d93b3e22df32ebbec5470d25e796f2625dd5ad6f106709572996d5bedac26b78d3380a399d5f8e59a66e726279f82dbb2e34967edbaacab782f80b372eabecdcd62ad6f762b80cbb56f586ab8a3b566754bc1d94c6ff2ce7df3b47cee0470d26afdced96d996f36eb28b8aabb59b58d6b7df3d67550707a5e2aa59675dda4951d02b86eeba6be99ef3c3b29bb037056cbb6eb18f3dbd5aee627b8aef2aa75daada59d9ed9096eebeebdd856ce35d6786b82dbd9ae76579bd1ce4ebb31c1610dcf69bbd4dad6e1ba1980bb7bdeda75d699d6f2dc96e07836abac1ace3aeb756e05e064b7946a3cbb6eab966d04e02ecd36c59dc637d678632538aab9d6b36df3eeb9673c00aef2d95dad6d6dd6db2f1600a775b4da8bf5cdb3e3dd9d497059a632677a6b7c6f9bbd13094e5bce65a775eb78b7dbce23b86badbef96eb976dded751ac175deb7c41bd72e6f18d7592a82e373eb137756726bb756678908aecaae4b69b34c6ab94f3a4b4370ba6a8bb9de3aaddad6739684e076e7b3ab946e5965a7e52c05c1c9de6f87b78d377af52667690dd7b36ccecd6a6d6fde59394b407072db9dec5dc519b67cca59fa81eb34e318d7aca35b777b364b3e7052f73975cf324b336ab359ea81d3b6acf14d777b6696e35ee281e3bdab756a56df3cf9eca51d38adf1a6dbeeaef596b39774e0b2ae756dea9a6d97edca4b39705b9d9df2a969dd66adf525dca5566695de4d77b7c39d25dca5f556dcf5bcdd3d6d5709b7a7cd7096f3a43c9bb5a384b31adea4d66bc59d76ad9b84db5ad355e65deb366dcf1fe0b46de39d4a8d6b2dd32e4bc2651ddf4d5e8ef7a6379b23e17637b7bed979ebcc3abd7980cbbdb3759392caaeceb9c90e70dbe476d68cdb9d6d77af8484ab53bbf56edaf9ec32bbf2112e937377bde16c5ed9bb958e705aa3dbbcdbcdeea69a57c9012e6b3cd3f8eacc52acdd2a1be13aedac56ebce58d7de51c9085737cdaac45ad357634be5225cddf2deb64a8d67adbb2937c0dd3c79363bab7937bb3ba50638ba51dabb9e657eaba64da908a7e14de9ec66ae9d96d9948970d46e35e3184f8e2bb7994484cbae86ad76f3a438d33293877012ebc9b70cd78b75b7dc102ebb7dcbb49ce1d9bb4e79219ccd327badae74e35b963c035cd6b3db517b6ba51ade9b106edb6dcb95d6aeeb8beb1e84d39ddcd4de9b6d5addb42d0887b73e27cf78a5dae64e07c2f5dd513bbbb6f5d4eca601e1a4b69ddb9a7bd786ebeca5b7d73a2db55beffee0f2cc946b75675a26b399fde06697338b279fdb66b96d7d7073f7db651bad739bbb6e7c70b6663ce34edbac96718b31c0f5acd36ebbdd33d592e20b7074b3fad2abf7ce5d66d8025c86b9d5baa376ef8daddd83ebb5de2eafb4fbcece563db85c69c73467b8c37367350fce729a352d77de6456b58607c76b97498ab3d69aec92dec16d566fb593b8dadd6d46ede0fad6328a6fcd7570f46eac7515535cb3bc493a38dbe56ce26d66b6dacde7e0b8deb66a69e63a6b9d66d9eaca2c4f80ba3c11c75c5955999483937ba3575a2db59d99c63d0e8ed3ab51a9bbed76dbe67aab0057e7ecf8ec776a9b6f9673144ab33e6754694870f48977c76bd6abd639bfc1e54ae9ae5a6a56d32496d80d2eebf4cdd9ccf8a5bdeb6a6d83e359ae1c7775674eaf76d964594729be75d3f76a6c27890d8ecac937be353e2bd619add5e78cea87b40667bbadab755259b7a651cc5f6a709b9c985fbce53dabb6694f83a3597627ed7aefa8b4f26a121a5c97e93d71b5156b9c35953293cee09bdb3e77966dd9d6bab6ba52b3976446df19df55cbf2cebd0cae6e724a3e33bd3b5b69af1a195cef7abbf7e69cafbe15570a7092f7cd6eb2d24ce22a311e83b313dfed7619576eebed5c0cee66dddcb4e376cb1c5fad85c171adcbf36a6bb76cf19e6070b6cf69a7ceb8f78ef9ee564e80c3b493bce3986ab6ebb674b9c47cc151cd52ad4dde3b35e25b425e047699319024c061b9d15da7d6e5966baeb50791bce02aee996b2de70ccbadab952b52179ced59bd9d9c5aef9bd6d5cc0527ebecdb66bbae62dbb5a9b7e0aad62dae33d39abc55667475257e02a4e58966bd302d30cc8242a150e1285a2152043899c96c3bab2f973a5f2d06850243aee6033f14aaf5faaccf195547d282cbf26eadcdcee6daed9a0537b7aa69b961ab67a62b95aa4d561204382debf6a6bbec5abbd169b330e11846ca1b8e22253d80db2e9f966a1c4f8dd2be65ab2b711f5f9b88c351a4948405b777c6bb3c67b66972bbf3aa3ecebaf2c805eabaebeb09d093aebe9e6c937405b7ddda517cb77d6ba75da6cd1648567059e25ef9c66d576d77a5b658485570fb76f4f63cb72d778aab740097e9bc03a074a864f0aaa6a198a41432840000a8361200900003130030482c188c0583d1a0585dda071480024f966eb646984ca4d130475110c430c810630000000040000186a0b41100a84f064edb105d0b793131399a8b74cc223ae3d69f0073ec8df9cbf13bdecf59cd26d2ded26125f727564e1f444f85e031698ef8445a593a5909fbe4e34405d1a4d02d2ede7eaafa48ce6dc1fd6d43a12429448be9e7e84a2488256d25be4f2b4e271061857c3151399a8874b3c45b49f58b24b86dbfbffa50e724c1d1f6fbd577750e1298e6883db17481b467da271eee4f8cdbe69eb5fafec70c41ba775badf13b680a907ccfadb6bf83288e2435323b50a13bd0faaa2141072be38b87cacd442ca7b191b0702cffccf8e8d0109779465761ec25be97a0dc9bb0d73ab973a6e0c8f7db6afb3b880a50e28826d28fc0ab3a21e7b4c0f8765cd57872120bceb71b559d9373b7e078fba9ea2339b705f7b71faa3ec9315a80bf3dafea831cd382e4db6f55dfc9312c48df7e54b54d0ede82c4b76f91d15f5a1fa2725198a2c89d9b594434abf8e70ac17def67ade689b46469be92f6898fd30aa29342584c3e4754224d2cdd56e27d52714552df76ee2f3094238948344bbf95ae3e41386910f1155ac574e20813c967896aa5c9af5b70bcfd54f5919cdb82fbdb0f559fe41c2db8df7e57f541ce69c1f1edb7aaefe41c169c6f3faafa26e76ec1f1f653d547726e0beccf114a248925da4abf3f577c20d06865f8e259b9391107b3e35b87eacb84d30d225e21554c24ee6de7fe8243ed27a98f76ee17dcd57e906a9a487c96562b9d7cc238f920a22a3489e9e6882792ca12c94adba73fae50f549ced182fbed77551fe49c161c7b5bf9d3aa92f8a58713475c5ff3dc5b70fe7e86fab6cf5d83f3f663aa6f7eee161cbefdb0131139003d2777c22cae4be51bed9ad641f7dbd4558c26063351566cccdb684295b3c67d4b1a972bf42e6628b50823d7a1741067cbc0f0b06a51a871d6cc88fdd55d977ac1bcaf45aba283c4dbd4aa220f0c48ceff83289e91a71fa2339f6cbb2c05375468382182ee1591245004ddced938dd257acf72511b06b42dbcd7cc5c9757b97017b623c10020db63ec15d34ff3a70487527f119a0e0e7f927b6456ef440fddc74a4ccd8063af5f6da1b3826f2c9d1a00c7593de32c3862562a63b3335a8ec532809e7434459c054bd9c675df3f240b64dce48ee6fc2703b22f87b26f654a8b00591936ce8dcd42ff068363792390e08044ec88abd2c2cc41a8d6679f2d2d4b4f2edeadd6147edf4b20c57b01d1e4cd8a04d9d00ca5db0a6a44f3f501fe694d41e4fb92745058d05b0c300fc77ad6be383c05bb5eaaa935db547d21a58c5a27aa56b37642267145578a58eacb4fc82cc621ea47675a04a8616bfd866cd742924f03917e93925c96029ff0acf18c5690d8cec08c20a06592b9ef79ed8c7b503639bfde91499940a2946a10c0042a0da05395091c4e62ab5a85740bd44fb12d52a8217a272861e64b9636a82c36a5d816c5215c04e3f919e566234b03d23500f0506303076838c0008d074e80c652636071bb428018fca2b9447dbd03978429e60741f42101b11cb280580c19215286d25fed3bcc6387868ef3a224489703824859b07796127cd28a4ee2520ba528c7bb0aaa58b7a68aee46f76f03d0c3a7ca8d41ed04df1e8e616f6d7dda92afb667d3213c85cceff1a540bdeddf717fddc84f057592104185d5a0ccc16b5682d8333bfc5f5c009b5e6bfede6ca3736ab277687be4f3c70370d5f6b2a8a7c8c893517760c6792ac52bac5ebe7ff6f4ece7ec81249585d0c86856b0ad7240192b0d425daf85d6d0f6a47689d7b6d1d71d062d00178acb021437250da88f8582201258525546c662596a1fb8de0ab92f251b66d51f801e2c8150fce3fc01a8d706a06a30e82a595b0a836cf1e994a016177492e953f2c9af9f1d15a2efbe15a67133cd45637f25e0e49c9ca7406969a83331e20d1850566f110cfcca4ee3338a38bbc290843d0c19203071f7c15c0971f9c0dc94c62c2b4c50d4a1da2d0115ab0651043a974629b3d409e6baa08c015f705810faeeab2bbb6a16f2afcd223ba1fba120b3345f7b7bf66a6ec7467e59b1951b9c08f2a58ee7095256e12bdb162c1085b9586b819f1ba9cf98002768be53cd458c3141bb131a433c7e523ec2d83843d3209b3be53bdb8fea632781143bb52a0cbd398bb18020489811a6e2d5495b5ed523acc69861d35630589943277d4466b729e0477e350ceac633d26eb1e1cdec138b19a84addddb5bc649b08dbefdffe33f818f143019080f25a898a406c5d32e99e6e9f90a71be9443a43acb4fe442fa9aaba68c089577e82fdfac75df2b9a9550e3c702da81d17426a5baa8f9c0c868223976f58cba360fad4f91ca112a0a6f4b7a0bdc183306086c9f122e7b7ddae4e2ddc5d9ef13ee149fa5bf16db12781f1073a0bf52d210d2ba3dd4241a1690da267ab615aa0af49158f4c2f5651d73eaea80bd0d09a494d3799fdb38aee8b4c6dd4694b250b4c9b0f1c002fb54070d8af243e24725994aacdfc6b5393957903b5a25e30506274d303d4328dec9856e98fe17cc122fa8b41ff5aab83cb300422b6d9416d6503b619762441216569998685a2a6db56dfb0482c849c1e0cff2a379a83771b807d340b6067b3ecd53ae9345b66fd0bc0937d07aa678a9652baaec54d20e88d272a47165098a11341230fee27355ed5a130b6ce4fe19375efb58e51b4caefaa6e1b7bd7d67ac79c757743538e2993eb21784e8f1aae94202fdadd01b195ea3810a0068eccbefd77a05bc8459006c0934f418b222201f50a939b015acc2a39dffea1bb78e0010cc0cc96fe21cb26d69208dc5d261150e66f40cf63663ffc345acd69d188b2f87d438da91e05943ecc52461a4305c28546c9fca24162061f32dca3dda62aab9e6f07da2508813ad491d7083e184f854368214f2d6f968408498944de58121ab8562f40282a6ab59e4780a830932aeea09b522309d47b1691e64bf9fb78205b6942d0984c8701c67fc9e83e0a4c63631706004284a5a719200141d970d40f2dd8883aff3c927f811cfb77d542fcd86324e2e3b7c25b006a30faa5148322d432376d7c74f592d8985231d31a7c994678628e8007695d69cda5dc299f2a1c6f067868d74ead6e43d0ba4b46eed8b995e742b1a15e5fafd971893ecfd227677661e9d18e47ca6486886214f66f41b47d7f1635ad3a77723c88187fcda7e5a5eecb7d304a95cc81fa114279a0ff888621c1307fd2b91338fe6638b1c1382465e8f7b765b6fbe4204c146c239acd9a27d24c0b5f035ef7a96333bf86c9533acb0c63ecf6665e39dda1d649b366f63126f777bbfe7993d035d02e98de47a5d150534d87f506c02d86747ff8893722011cc17610e3dc8b44bc181218a0dc1d41408d4636e1243205660cd1d218a4ca2cf9701230e799ebe794b872d124fd26621d28f7ec2ef626a80d86379046076b130a3734c8ae82b3ba3b8c86d55cd602cfb4f88002ca6390a42dc8dbe0eb6503a7bfd633d2244484dd10ee67f0169fecce4f9ccee423874a8a21155604f487384016f2c648c92d173c9e17f88a552304ef546003b10a12bdd0439f37ca4090384e4d0dd040cdb24797b1ca2313055092c137d30efcf7d243067259794450fa30639a53906a3edb0f0885349f1f70bc593d1f025b715dfec370a33c70cbeec4fbef8d9180c8f50b8c38dc7705fd81c00bb70c77f49426cc8dc2b4f9a269e6b63bf0f4b37ae56f5a5194c70c592c9bcaf0da681fc3a389fb367471ebbf6aa6069d77243c66c7b7e47690273e1e00a47c73087e19460f168b862f3bc88fe0c25627d127ea1273f9cb29d937f3bbc5a684b27465aebaa78c3407de98f4a6a235b9fc6407e0d6797f9002ced1df50284a797874290bfd8285e54c39ea1ee45bf7ea0e3941f86e5b3f6ecd9396710ff8266c7687d78cf82858c639033abd7ff1630f58000d7334af193adae6e8ed65dbbcf184c13b3e5feb84b8a0ea81f0e8db8237e9692f3b682b9bb8d5c8b1ac9c61e61d6d5b7eedc00a62e04b5bbba83c00fbb27db3bff4caecd58ba741e7e9f8c4f82c1b74713acd02077ea690cfcfc3d1988bcc195fd032cc0c793a1abf71dbecd82ce498ea8a0f59d8445d7062b536e742e9ace3cb63c8e33465441b75f882c5cac3ab8d33a127b343c7f67843f9f5286f0fc05f0a80aa5e5eecacfa6799fc3b9599ee35ba2c802d0ff9411119a7c44fd970a2dc3b36df032afbe67508436c6f86caaef641d25b6c6ea32a6ec250e36a60fff31d1d2fd89b01a4c799bd06a11d9a9d8ba4aeb177a83e1e40e717460b735d85b7c554c752af5b855e4386c2b1955f27813d8a84b830ad453948e994346fe63bd886c1be1b69d7af50ef7854062155e25f726c4345ae66f0fc65dcaff0a09687a08d4e8b3e9740f2487caccafcb0c53d6fdccb029a7535c37c286c94494756f7fc08036a250cbfffcbf30907b3bc7ddc2cb0241ecc502ed711ba7b7f0eb48acab3fa5ebe350e278c9a419ef175a34dc3470ab3605bd63b85bb3a5618cf725bbd77cce97cb6a5e57d5598c376cd1bea6870d941c0b2beb12b45c3d906194562a6fc77694c69bb0e2fd010bb6f065ff8d50cfcf9dcd71f293b20a8e7b303be4022fae9c27439d65714ad624d11a4b5917c92a0dfd7e7ea5524ff7aa86ed9db6546e9f1f7fc4bc43f9e38c4e087ffb07a001a04116210c3b2fbd36cd32eaeaaf0fcc224a9130ef38410ec861312502f85a0bdc19d7e53589463fe51052ad0a0dd5c9a395df2b1ea69d3f0eb26494e17e743b7b063e979448be503e3df81a8ae0ae08ef727c1592bd6a7bcc7c3bf4befceac5958491324f0a7ebd1202850f452a1f9b7fb240880fa20e1cb56ffcec9ea0acdb93664ef69bcb49dcc2b759a83b1a9578c9bdc21c90fbb5f83bf2cbce78f255ce4f2ce6f51ee3e3da43f0d1cbdf7c3fbcdbd9f62aa3932efc250bd2fed8ba418d61e1ef8a3bf6fb11ddce2d7984cea7e94b0d5d479aab6b1864dcc5cc0e3962f6a282c953ed9c3bb2874e1dee7d5e3bbfdeb52063b3b4e400ed7b0aeadb4dc90700da3a330e707978eb53309cba8c326e2f527b8e28297fe90ea9f34a84437eb330f90f0dccbc077972e8ba0ee5674328d4702bc169d3fd11e582ea0586231a8afcfd8c28bdc73206c86865eae998541151b53ae67ffb030061aabf919f377ce37379a28fd42a7bdf870782098a492b3f4e629c3706837c24f62aec21dbdb75644f5a661eb83a25226ef6c543d33c1a3d0d8480b354f569f4dfb5433776a32bb9b93f2dc592ba8332cb00c6d182bc5c0a00695788311c0e9edffeb2afac896fecb847586f4402d80720e4e978dc96bdd0f05dfcf7092cd8c1c7c379bd71f6f5e106b095386f50ad85d1100803c807a41e392b0fbe9de67d042815f7b88fa110678da3174726f28ea87af1e6edb0003569e6729e812e90845ab0f6f60b90925363f345f362dab0e30de574902915dde25fdc42dd65cf8a55360c62339dd8824b55046dfd0da75219d32a27690c27813812246f9d4b309542ba05687cd7930cc8f34c3410b3477c614df311f52ab4486954e788601d6f3785f7f7a9c19e0bc155a14c73fdac54c9b86291d4893dc7f9d9f33c7c8736f00783af8ca85937779d1c4d064044bcff3059ec3d58e6279beceb10e777b53490aee47d357be9295ff70dca766ba84c2ea8f59361050d7ac7c4ec0f9b0021eacac5a954470846d39c72b7f7791d28f574c619c3c0438f53eccb16f2c77aa7b310f9fad31272802939db3222204f47463a2ba2d88592d19a994254af629e3b3c173be93a355b49b219586824b07215b30d61a77a3233a699232339291bee5618865239a4e6b3b3224bc921950b372b1e561051664489c11f94a9777a3ea538974ef2b7b62982879d0ec5089fcfc5877816eede4920ea59fa968dbfdd0b736be67d8eeb8866a07a63a1297a85e5a68a1269c42804dcc1b0791e6bed355ea58cb7e4a16b35165353049c470c342843234d19ec5f8c50c211e3bc0164db7054d2bdd399d8b1bbbd6b1d894af2e9794c1f00753eb58cd753be90958a2112b311bad3ce423614b68ea0263e8f41a5eaca813f356ca7383e710fc30cc1d033aa5c63cf680ef06d81498f1ce9ee2c0fd2692fc5eef2738afaeb5f680f3c9aced0799643cbeb507a70db022bb8f112073c82f0079306d61de771b2dbb9f3cb6f3508d49f5bad62313bdb066849d0ffccd277f0b70f80f983f422f88e3ab93052f18231fc1fefb535a0b7908f10eedf2c0350a9d584497e8e442eb07e60694bb6adac2f03f0aa963753777d961456f7a461c95dd40b7b70cd75efe0932e53e63aab3f9e16f0cd9139cfec5b51f434d26952bd8b75f154bcf23dc85cc6c422bc5df6eb1174a021accf49b72f81584e707b464209aad63d54988662be024f537165149dc90d0ab848e97cc4ab748f7b282c2b4cc90c94c9c29667fd549d7f2a0ac33bf8b88ae418bd75086bfda5c64dd2ca213fa6d3bac9ab90903e8be0ae74eb4a26180349513d3d0710299414ae391c899643bd8f7d8ddb69a84ed73873c28c5af4f7e9a5c1d43fd699dd49c24cf2844d7de803eba26b6f03129eab1add8102a75acd8a9043796def1a0c76170cfe02b4247094a6833c94b0492a4ff2736f395b85521b2f3561f8e8bd87e7ef65dde94a6d5dd118ee8a97a3e134465623347f0330f8fd64f163410cf7d67ca30ff31079a18d1a08e7c1e63f41da4ddc6dbe1c752fa523a656f2964c2f44a40e05ec9c433cab8977bf95c620a76ef3b1351ae4d90010b16ccf0136c5089a9e36540b9e8b098d60ac22f47c55ceea8986bbe424bcbc662e897ce9004e5645c09ba48187b9e9a269e60ed013f46bb7218de6d443f7e03f3fd6e96536763536777bb41d2becb3afbdad72b208bbbe0885f1f333bbe0c4996b33072d7e3ec0d6840ee4586c225573ea0e54fb424cf69a6bca3e6daa9cb339789d02c39ea40916831dc17f6e210fed7428b61f1876d3b54bfbb36133228162fe54470ceee6efae0660831c68fa69e378a38fcacf66efa162333fe8908fcc40d5d3595d291cd3e0ef4b11f1b270a1d1958eee069752eebda237d3fa1c6ab5bb406bad81deffd126e94c9316427e68e7d9f8634cef75e294d7f91a1de5dd08cff1289331a8be7d8f0c7d2e5cae67a9a15aca19fc807bfef99e87321f82448c7cfba651bfa9ea1f796613b7b095e513dce29e4e76ad0c2fcffdb7aeb207bfaadc5bfb01800dffaa2dfef8576b266bd9f40f2ecc287b29be57834fbd2a39a2b9800dfccfebf1de9272f524f9e9e46160ca3d5f5ff4a65fe5b40441ad5fe21c70251f3be35d8c86b460bc2eb2155c0327e055b91af8ed4128bd93bc04a7e3ba693580d6f400bc2eb2155c0327e05598bfcd39b69e409fd73c32262f81c63b40c874f4a93c5f19a6467ba3f51cd6c8777b405f3fe456d633bbd50ac0c974f4a93c5f19a6467ba3f51cd6c8777b405f3fe456d633bbd50ac0c974f4a93c5f19a6467ba3f51cd6c8777b4054b474a97092dbdc12d619b8fb5e5cae631b654d7bcb796a99a8fa995ff46143ff2492c37f6607d7e046cc76de907c749c1e02da3238fcf6b466242f7af90de740f978135389fff71db711a3aa071a5f079cba84860f399919fd7fd23a030db8b6b7c0dcee547d86e9cbe7e681c19301150ba1aed2574f2d98773a6d93373a265f361a33eadf2a7115add60412b9728c2b784f5dcbebd3697bd648a7c576a469267d6efafd384e41681ae8d89a8f1beaed1235df70bf8cb883c6de4b20d14463e66b9d10d5c86e1d0cb5dea1533706b05bbc18665270c134c3fbf11438a885513e3c376dcdfe54b48cacbe1d9da8a39bc27c366fa0795424fbc84c35555436d0ee68b76ba347596425fe5f1ef86f518e0951a00a2a0ee7f8ae6e0a87e51e5d65ccb9d117e7d228578585e8721c0e4d3b44ddf7ae4808d954d7478cb196fd2c7daefd884f7cca906dd403df240410a9d7ddf430b7744af340871c76be5bf2f2d35c3fa2b03c2ec433a502a48bd27da70db081a25d857a5c22447492fa26483b25bafc5a78cd400e0f5955c6ca54bbff5a1f30fa5c7bcc74ace93b959451baf420d967c8ff20260d2a0532d4d09fae3d99e54e811d41293d1d08f2382755d10773405613c407a572d9b2976b9caf907c840b2ba061662047426cefdfc8b991539d30ad44eeff2f8a733bb5dbf978f88b8eecda197560ca0ab914ee6efc07dc657707129a0ede8b27dfd73ecd295af8483de39b3aaa1a01489998fa7d05b16b7321e24b654fca8e3aff832a7b11b6cf6451b758c0367e8df767fcd810b733f8cf2761037e11df8978a0db311bd26b2e948179d3e1ebed69b4cf6e0044d7d5422c24c50c1a1efa4ba1445b27a88a71a64afade3dc19a6138b7ba4c5799cd477634732945fa1daae70060089c8c42b06ab2aad81e9d97b477b581e8ad4330244a3e19fdc8e3101d07974ad0af7c82460aa28d56def2a13a6ea98720a7dcb4afe7127cca4dedba9b6dafa6575db1562e73840c1f54723f25d5856f31daedd1dae2e1a88d823ffe15dc46d61844f27cd61cdb330568236cb828062f049cef86e381b1036d5927e35703406c5b5c24673ec7df8eda0bd583a4b6806432845b794a5975798d3c1680d82aadcdd143672e36a5ddf40219f22bf1b0da978e54c322cd95461ef80eadf349758f71108622b9b77df16e16585ccbf1fd10614a952be44acbd1cd2cf8e2a3974ff264a0800ad9e44703aa0a02684b2dadce19bc8eb1e45895647530785b010eb59e362c278ea5a04ad6b9f52a7880783751cb83658bf5a644a73ae3149e8c4b8fcc550d3e7189b5511b50f837d37637b990e9b2b08611ced687b940b52b903a109bf5b397b87ed3ae7a15c27aad144be162818cf48b7f96cd85e88cf78f5577e39544c07a49d81a9dcc0e10b5ea437a894d8ce19348441d2836fd0d1efc85f190dc00ae3fd43fb9c827678a616feee4fac0bf77f5415858e13f55be29df0231ea6b0563a9f79048751e4f29612a40bb0d470741628b3bd7f87f8e3dc0e859fedc222559099382dcc958912080c32ceb2055c8b52cfc16e8ce32a29e5aa591845b0ba727565f69e06dd225975bfe9c44f12b903bb73c9feb38c4eafb8b80587de5a9627cafa6c0b3d7f1ada1c99de151d4c4136025b871d588415a0bda204ad1524ebcd8641648f42c1014bd654c5630c3dc1fd8ad1fbebadd88d316d2564ff6ca5b62610b41a1ac241c1fe7e0285d25136152734f4165212be3ba69aa93acec6507653ec416fd9eaca4c36c228ee7b4819594a885e234eef5bfec41c85dbdbbc117cad704f6203e3af03d93919975b63277396f8bf5f19197216116dd644cf3900495fcf831c70b7c0ce83f3e8e78b8a3f299f6d2139ff517dec292c962dc4fbcf44c433a8ae338c243b4b4c8e6ec0e4d9bd854d1f8989fc4f8be4aa8563e15d47c0b81fa6a9edfa6fe88149d25de6988b2bf2e72854d20be5da58bb7f64351d3a893e6238effa9d4f26d19d9673857613b158a331b492bdc4d38dcb2cfcb6e045b83a41a79aa8a93eb89f518de329a0bcdfebd0fb7cf184a95599a2977b2c5e917bd58cec135cb0551d3f487f971b6db3c170d6ccb36f02e13f4877a0778d6ed3359564f18b83567931cf18a0d3dc5573f9c2c498fcaacebe869de077d0dd782863830c5c2cd40e5a3910dc2e159dd3711b16c08e9dc3223f08612bbb84111d6233aa9ddc0eeb0083df6c560699c151bca4fec73c967288e6c516bf985657bd2d5ef3e36ca94eb6d42c2881d3ce1151f4fe3def1ea967cc7f433f66e12a6dac1e1cfacdd9bc779e7d3cdb503caf9b660d3e2e2ee74824c2523786c280ddfba7d7a6a21374c918e20e3a4c103b2814e6c57e9969210fc9614672d2d8e3f1348169e45cf984fda7f3a77277c3bf1b4fcaa9d18fc7d104061a79c05cda510c38fc21cfd8f68499e4ae9d81ee27234442c0108653216558cb8f81ca6ecf3846ef85d5f90fc5fb9900087677de698712fc69f26b39c4ed28b97ff585c73c39cb4a86b85605261fa0640a33b0baeed4e2223f0ced9d83666a8382bf43421bb4fdbeb5246b8c207974efdf7f8d2fec94defed6ccc5ea2817c94bfb1d4a46220873937b91ec75d85c557abe52f8e0fc4a64c79ffc8b54e591e93a97a200230191ddaac18ef905fc0f9bc285c142289c7d8294b55dbea8ad15f2f0432effb7fb645559ac964c3327bd06a8f9c045487fdabf86cd12b5e34ad001290a5ccb1368eab8f2c1d2f33d45febb01735c1d5ae15e6a9c3d0d3b0b51281df8bd695702196246a07f2903df4b9db9e6807dae1c0cfa79fcb83e0de8f4931cbf8cb85a65c8861b3b25edf04f295c2a7a25694beeef896e7a680f99130a8b376e3437aa6bcdfd429bbb65010e65bd47076ff9c50d955a60c3979f2f9dd69175760fc172b6fd9cb464d9eecd598ccb6b9e5a1c4c9b780dc5ce47b8f2dcb9dfce4f7cbba97dbe642d4f8088edde9553f0df0c5cdf80da6fc25be3025a4e5aaf9df6e377895043cbe0373e787977f73b9033543a3290076068ff86f68cb78c79bcb1ba0bb03454f800622b83bbc8f0ee3a9a1d62f65a19bfcc615457f6a4b6eb6fc502dedec439df46b3c5076b8c839cf642fdbe1132fa23553cd845423a84d1711d58cf951b5d2946dcdd1ff075de4de27e3c214603b50075b163044ce9556c27f57067e748695cc9ac842e53dfe46ad0ca68484ed7fd8763224b0cfb799a6e823d1476fededa66e666fd1a81f66945376c60d1473809f98f5a775e5f2843b8dc861c762a6ea4064d677f808528db338b173729a775775e7c6853e0293a15f5951479a37a75e098644515d3bf7249ca4b57e372bd5f553a95697038de3146c7ee34ed1e738e0eefce67ca434c84beb26b83b14e6f9deeccdb5ba104dc99e9aa46e2a893202eacedaa789d863de835470b973949f5d81732236f91dfdf944756477eed28d138831c3ea23dc1db3f70d92e43dab245177e69bc84fc1935cd5813fe84496cc86aafb225325a5bd45b3c1ababdac80d5e86cdadec1becbff3b5b8314ef3d6c9d3b7c0b317a8fc0560444fd6587af82b83b5c97b28f37eaaa98d1f3d460b73942aa9ba28f25eca02c7dde408db1c97976b3804afdbf69126ebf3ace95e596db028c86452836f0bd9e1303eecfb3a24b27e9a4c6d254b8dccea78267be378fc50992f30c77823984b1c68cecb7259ae0e9da5aa392680241135ae10c0f92d0d7c9d436e377ce1d6fd8b6ad160c9f07ad9e3e82fd562c0325c9c61f8d07c60ca0fce42ec3f073bd334edf03fc397bc25703229ae80c2e4dc819f095e29c64a5ae9db16a04686d80b8ad66614abb605c0693a7a75280cd74664026dc4a6f5d880cf6a84d455b3d4c2149acf6e133434676e3c497373d76adbbbc00aa0019753aba70761d1783da4b53f434f15fc7ae3b2a07f1bc2eefb4e31911f5663f42b41cdca81e453ada9201c503e1331fc8c358ae137114108aab2a1ffd9c40b0bbfdb19e24dc03d6573359c6d435f506339077d83c3dc283a6517bf1340e885bcf5ed1e6c3324886f5014b4e69f23d23a5777b2917ab25271dd214c34f876f00a51236131ddd09e66e4f957b6087029cf62b4079d2e41e360a4c24cec4c721e494ed5f3b29d7ff49d4b291fe749bd626c8ed9ca1083e7a1b5ea703ebc3cae629c7f379839a6484fd36efa0d27b000ea10de0242595f143233068821d4f62d274681935e1d3cba477acae75dc7591836487c76e8fff2ab81bce2e7dbaf58fb7e9914a3a34136d01f25e9fc252063958b299fe937e5ebfdf070777c71f5c444e98afce9157559d6c98488112bd5e52ecec74143559c8b1c1ada44a02baa1a9b3571eeb297466ee3a19041c3cb0519551da78d9d548bf7f871446119ac9004688065bfd4abf4a556250af0c94131841e97fcac453549c5348a928327910c8150b0cd389683d99706267174e70b0f571ca84b52ef6add159ad448576dbd9bca8a3b0f14acd12895077a972eb22fbf3489731bcf54632afebab5275430c17c04c6942d6aa31a25ce6885ef0fd312b7777c77e331da6c1c8bb445c447a06d99eb26fb603932fd421ce2a2d18d11b191b4554348bac6392fbb6bfc8fd6d08e6586e7435fad99a335d69dfc730709e6a3c3e5127edb10bbaadc95db2f84e1a42e40cb75a9581cfa9c601cffbc7ea802b58bb0dfe5fd62ef5f8ce70d2e1a31e1302d38bca8fab351f3fca61270eb3c05de752b6e4983a5f97e9497dd1f3f96d51edea57bdd45a1bce555bdc15b29f186c20cf149c399476935da374bb874cb66a5b921cce295906ab0777b0325b9a32f423a7bc3ebbadbe90d441bb6193873d91b0be85a4596685bd3aff4c8dd9bd97f80f1113956349c47e3e7db77e90073b9e0c3920a380d0379b826a3e3bf265c724cf738347a6af6ca93faa30410cad8c0584ba2aff31eaa3198132b30fcb4ffd5e3619a07e9a9745be82fadabbc2a75377ff4b704f494ce56a0ccc01a312075efbc5d98cb3199f3f7e0648b1cfe7d797fb88feb770aa07f722d36f9bf5ceeaa6ea352164cfa439afb8dd78db2939c7ca90cfed937b2255845c870201789e9478a0987d4340d5966a455f39f486b5a491a593c0160188a4b0492331a45178cf76521e0bdfe02cb6a7c755a0bd391c34e6cbaee909a36dce531c8fc11003e82e0b7ed5b948c1007f5d8ca22a39297f9a479eaab66bd7ae368aacf814d02faf66a042b9a189a3ed2bce584e5c361f8e2cc4ad2ad9c731ff4f152b8c861a6686e5c117724b3fbd4abff82108f92f49aaad76534d1cd1ebada27940707064d46ec2fa38805267144f8ee1cb2d648e669d6c8b601bb324773a2bed1caea6d68a3ccacc9c0076fc04db2864d7916d9c7011f114037f3973fed5156ab8a05397a33a063324c31c8a905cd658f4d5f9dbbf7c04b403557d3415a6ae8a5deffb90d6e6ed98be9a47b6f2a7a40d1377b25825dd6d1d4a6b525a646a000313fba5b1afc947e28dadb63663f7213ed16a5f6b5cd7e0312baa83ba1f5c5f23fa88bf0f098aa6640bbcf639aed67f83ed6da0b6b31f20c24a0833a367ad36e2fc5a3eebae6f200e77cb18b0acd451631d1f8e72c28059a3b2fc27c533cb27f1a9dd30a5acf213607f90314b9d72ba30ba036c0b223a2749718dd4b869e9e4b1c6d387b2c9012621f828aa5f8210ac97e0dcbc29726e65ac8a3a6a490bdb8ee427c619852306c3ce7c72fa58333d75918069b56b9be6fac612436eadb15173dcb1f9bd43d41f70a51d27aec41d8b6862de44672753a70686e57b17957fcb6a4a334e5f94a3ae95c77c78789a539fb9adc24dfb1ec71bfe6a84542fdb0a9ff3534dbe6ba68f47809f33071b2ee608468acafd2b1f3a7fdd07f9affefee5403593eeb90bb79dd02135e9ebe5b24397146bc4cd1040d70e5ead0728014d026e162a794286d5135f3e69b021e4eca00d2ab04b0fd2df409b747924779eec44a6fea3f9b0e49651ae253d1ab629e6fe7edb2f89c6aaab9b4056d05a0e2d460dd0068499910b45cda4e6459251adbb62bf7e89a95f1cdeb40217d430e9b0d213fbfe44e924be1940442e9a9361b9e622df1aeaf40116e8d0587084b0b679636d9fddd2c9b1fcf807eeeb0d22921c5f111efe5e42c7363edb8b39e940fb24708dc9c32a67a91231e0ad9cc1177e35833f3fe7f547ce0648ead9cca98561a0301cad7d11eb46d1049c20ca5693a29fc80e68e0324d6b34c366c27706608801e1bb50373c56d325e5ee899b4291a39c20a74f963ca9b609a333540d9484a57e26bbcbecb04c5a04d914caf75bd9b1cc18aa1f18ea79678419bcee7d2fdc8bc1a3c8e24c25cbe527c58c10a67187fce8b13c35faa2acbd402d85e8213c9fdca4189c1a9dad28a1bbcfb2cb33546fbe935082b8a50e4ad87cb81368be7829465b3b0c56e31338acca17f3751e2a8d012ddb63b3fdc971272c6b5b594d726fb1eae7cd557ff7cead9497bf8ffcd40c9c386a067842dfad36b98f2bccb96da13714ee754461328b6cf5ea9b83cb6c94d6fb5e61015b346f28a7c9418781a5504ae00826184fa056821111d02d5846e9b78edaf56fec6932d6d2c21d8f7a071686f0cbbeaeb3117b9576c78c54e00da0ab5f511ab10919e353635c0439c6f5b71513d05e04d237dcb55b2bb88773e3ad5db6cf42beabea2f1f8d745bf58b8230bb97127efa7f3f28403d8443387bbdd7c391c606e9abedbe690e5e4b2e3de47bb7684b1188608ed3accaee66a27d05be7cdd20b554daade65135cdf68ea0876bbc9a5eacdeff5d542816a484b6a65a2338da8f173e6b5a434fb02faa141d3d9577347ed9dd1c6931eebcea322524a6fa140800dcfacf0eaafb07e59cc2ddabc78650e72d1c894e2c9cebd9a0f08d7115efce4b6f9e2f61b85b3bd613362eec0d2a066509ed10c863f97422f49c3a80642ec16ed4d2d6b386f856bf7574c2ae7f705a5640be2fa2d48236d0179d8b5ba7d63150a2121f37804e349af3076a85605feda47b11b96ee153c994dbebe664173dd996f6d049876a7936c309f33bf32ee8be266f16ab1bab3f2260f78a4b894e26841dd0f43b3bca06bf31cbb4f81b38e408f2cd9e44ee8c82e1adc067bdb441cd82d957262d9aead55ac844f7d6f9b60488520d5d5dca02da59233df8ae0ff779a94ffce377d87b30200646b29ca385b23d3bec850de659f18c07c65a52c850af41318d40a4856ba88aa468415672cb2f3bfa3079fcbd942ca7a3b8d1d4bd7173a0f4bac1a37a22e25dfeccce29e82800cbcb25f9fa4094456c7131618b5a7f00d83fc66d06a6940fbfec01b0b9587159f821fb3b36b48da94cca53e767e9e1b94ce4dfd7cb3fa07cd9ccbd122e1e2c6b0da46563024a6da0a706d21f98598b7cad58006893650e74b57556cbdeedb51a4f7b27be54c7c2d9a68e65824970de3bb1b3b8d3d3346c2df136c6f1f952b7a41c99b36fa978d2faa1904b3dd689e2de88d780ea118856c3aa0fd13bb865f35f86569ad386278db07d9174d71da5769447f816740314e1ae89df6bc1ace8c31ac75efb0226c598d032f209a3e9912e66bc9addf58b5bc6befc2eb68f7ebd45e5614a7ab5d9ed21bebb67c20a193d842b29c38a1fab2e58833ebc7ab430a1c43b4bb010c9b11c547012e76ae12b62b11ecfc1e0354adae7834f769f6ae2a85c642cf759acf26b5a086a6aa4937abba9b6dc0db97f2a9a71a9e56bd8c73c39a99bb4d541cc0810f3b5a7b6a1d7b53abefd03884a0d689ac9a5dfe758372e66701e7a6fa38f051729e856003dcea9b16c616a0a46beb497196ac5e21cca220de1e34d570310c207e8c817c441b7c3985f519547ca55fe130bfd159ac78acda5fd89c775eee20f81b7b28072bd7be2d69b66706aa06719485bb9ae83173f71f368ddbb11b0d6788eb3217f959ca5ba4b73a64e68cb76fdd100d8be66f0f82bcf21c23c16382f48879a144ccdbcd5c5cb9cac5f78666cba514dfd0f442e7f7ba64b3defc2033a541a4ac55deaa4f9c4271520aae058f29542ba7ea6e4777f16f8f714d928726c8b8f0bfe7778dbc45a13d8f4a049c75c14498acb71675f222c5ebbe310c8518885215c0c30e4c1dd7c4341dca7b5115716f7d643d6b402ce683c4baea2f119405c43d0489a1eb1a39c5b60fb3fed4c2f3e391adb3b21a4e0acb003c9afee8a207f109b227b8f8d1027d47966a45b8306a722b188f4a9c3ea8eeabac1edf0179ff632aa0b9d7da1c15af98c070828f3045e55cc70655e855f38b1b9658641705118b1bb827822ec39be7cdddda8f000ebda0cb7229eac79ac3d3b3e4e2436a8974c015f147e6437d39edd2add2ce0f8ed6922c76ee86f22e9df24ce289b54fd8b1ec7de2d085b93bcb66df30eb581ab849fe71a8f8248c9d133f3349fd070cb1bbbd5099738a465f3dbe1e666f2cf5bb815af6d57435ebca6a197aefa96b887ae1b336efd16d0f60d74adc801b12b900339949d17ffb484b99f815b76c597e941bc188d8c73dce8b4e09019630b84bb7c1eed71dd7ffcdf96338740fa0e38c3a5ce087d05320a572450bc60f9be1b956be7bfd82b0656b3666ef33e4e5f463ed5884c89737561e076a93166f0474d9d0e778abfe62320be5a3dbec9a15dae59e6b954ee2bfda7b7ac27f7f5b8ed612871bf46cc60813e09afccfa85973142b9df22331173fbcd7b961164dc2b60c473f264ebcffc0fd47d6a561f2c1861af2b22c536e48e9433c6247bf9b9710c8ec630eee68a2c2426c6f62d762a16f9ccf78a3cb19ee1ecaf4fb94fa2881227e0a10d32738aac374c98c2b616b215770d1b3520699aff0b94a6fb413987771a3a8f4640ba34aef1f580f7d4bcb046ce3d590d8ca809935019605e3c5c1cd697df70cfcfa993117b551cabd74a4501c30513377d0033790b7f19288ff6c6e1e3509d875c2e74a664de0dc5f9760ca5cf07b91fac089c0952b2a385193550a22dab1f07717b971b9350e7a3c3dab216044fdc63a1ffe3827464c57de949966122e93f750ede5d6c5cb232ef4bcb2d5257d288aeca2938374ba85345970ec8de5486c01960a81c1bbf99e16c6b0159a320ce364524b1ceda394dd99ec57db30b565feeb12d8a59e05b20d27fb6d6d72e99994ef9917ba17463c3bbaf8aac6362ca5d8a9b532d1297130877534466db5076f0216ffc99640f7fd133cd57889c18a0dc96eebd7d83666a49903e79dc263e1f01ef87b098bf7d62166cfe5da411c4108eee4ba4d4592d738d255aa0d239f3a19b336d08472eb35cb077a4791607da72ea6fa4444054278f97305c31501d6d3ec62572d26cbc9108fd29871617532893e4ebb36b70e043b81435320f34fd0604924b885dc9e3d91df478d99873f34f4b5c87f801df7af9076e3649c46054929549f5d3178f73eb94d99aa930e0bd39344da86425c17316219892f40de9fcc111a4e53ad3df04ff93b556f8df939dcecf0796f117242bd17207fa6c6d263119ccbcc61facbceb726935581c418a8ee3b2467f2cacdbdb86d1b6c5d3510dcdbb40fa578e40f8f868401cafb510af478bc03ba0b3d83bea60c3a2dfb058e94c45428d8ca400f99849cc4622d0c661ca0c5f05a888f798ffb2ce16ff587193e9dc85e21f58e0dc4e65b1c5ccf642ea02d464d7010f763903b224cb5b7f6b8b3f1724cf40187e1f148e6bdd0ca43b89f9b36f0c35b075b72fbdf8bcc676d9f70e66bc3f8fd69c90b7cc445e0ac72f0e521e40ab1e3300a17656fb284b0e7e020c4dd8c5cf4d77178056917c27b5fb33243c273700a4fda71e6ff2b548e5c0cfbb336a970b28fff2e710632c795a450921608fc42108bfeed10b79e853ae21be00f59d2e1c36f0e780d5b361b3a5b9a61b6acacecc6b32e5bc1334d97ee51de83d44cda70e4eec0d8553b840df612208c59e77517c556b63072c0504964cec4d884ab1d44135da69b826fa7d4e6bc933786e75d8b8194e5bc3a995737c1d3ca7933658caf9eb87a11a81fd94d6f53ce9cb8fba9194104c78a4291902cc185a6f540fba631d970c0f12eabc48c3c73f8a9e0171a3cdb18ca514bb590ba8794e3a7af35ca16a1998b3e7a339c469c70d42984e04a8a6a340f15160a279d939265e9b29effff76d519900e866358af705203ddf9925d507863992fe3699e7403798ca88ebefb3e7a2be8510b4e057fcf177f224eda69cf8c5b9b7294938b066199edd569c368a4ce7984b6a2c4843a3b46b4b60c0673cd65b17798445a0b1b218d0e769c4f630b9097571c3e520fcd300675fc5da73c7647f3dbc9afaa9e3ee55de84c74099cd2e75811fa922df7cb84c862bc09106fb4fee4d20e9b2a858ab3137c921b788d56ea85cb1944f0a15fc0fa5ef62d607d8ae9312bfcbc9256259532cc8a0de5572258182b2fce5c505a7d33f0ea124cc2b8cccd2a81f0ce98b275426c224ee5bc9668e139d4931930897bdb09221e702662e91a5790147ec682667553653952badf1a487546bc501319ed10b583f6d8b50c7894e9f09c1c7f1f3e3d74d39657b301880b985361692c327f0d2e41d74bbc9eecc68cf470b4555ec84d1b95891950e16d30fe8691edcbb485cdfdebefc0f0135982ffa290cb27454040634c4bc9667f25bcee0308edac2ba9fdf328244cc0e44a3a3a0f254d823c46018dd40223db0481043471250ed4de1e14f40a2185c7581cd4e4fe7d7b0dc0b34497e755d32c870c22c70065603e33e81a5b611db3eef2e07d28197ccfbf77a148f2099d5b1b60b72b5ca4c59328799640d0c5ce64be2d37555043eac57b922d1d8dec3f04cc6e7f2a251d4f6f803202df30662b6a907da6220cb4065a59751eca9344a8740b54b851e710762fcd4ed6e507833cc4c91368e014025b23a1488093e26587603b7cc483a02ce5ba7514eeb2bb873a9251343d8cc35342188eea69bbaadd4646fa137269ede0998ecdc19ff20b0a5d3c1d8b98406f4dbd03fbe606f60b6372c1353ce40ae6f1c87a26306d9f352fb287e8706aaf9852119080b26ee6ce168e3766c2acc076dd06dcd18c783d2007250b4c9cf6bb9a430ef014934acde48b64bfdbc7378ce81b3a9334519972d95bad65373f9eeda37f1e008a292e8a518cded91a04b082346093078880294843734f466a6db7b3a71dd4d0dec391679372a29f2b8f4ac3083fe7a97395f2112b73bb9c2506e37b8ec724dd53f69eb50341267dec89d31274260745911776ac1d93dc7efd198f55f9bec503762fff93b352bea1b00a3285bbd90c17bf3b48ded0576c3446d4c7eb3d3131990126100f041e07c33e9be5cbe3a064dd11f0548f03f0b84fbf856e5c0f9838d5e3262d7daa31ac70e5d63149cd7e97933498e70648466e166d4c3471831b046c0130e9cb21dbd799062f30880b9ce0df100b8e6b21f9109e342ec0164160eabd9f723d5713bad1a2ef793459627bb0b7c56a21ded0a6026b1118d41a5a8ad14863b704c9440ba3c5fdd9e580ee8f17ba665e2a130f79363749f829dcf6d9a13095e3e618e193e4585ca67fce1ee9a690d7b15ec0d7f6d040027373db908f2a3498d872d34fab8128f8d563074ba0630e5688935a50753a2c378bddd927609b9aef24efdf5e5025f9970b4a49e293af2137a20dcba94f08f419f126b763eb5d8c67886af3a3f3787fd1b50d9a2623f66ffd2f23e1a9ccde5f203ede6bb1becf4a1a89071b5bb3d65613054872c09aaf6e73ece29bffe5b7ed48ae454b656ebdf3d88f23f6fb5d6caac04d1882bdddc790611e6ed17a0cf7753f20bb2214772bd65cf97e7793bbc30705947fb9abcacb1f81754d6a967cb6faa9ef5b1f25a0df01df68dc6c532ef56cd596b38d937035772eb97a34c41fec71cfa394815984d652864fa90681ee51b81b098a11434d90f9bdfb48c21b65b9966f5564208cbc2968fdaa904767f8d854b60a20d80141595b10a088723ee153eecc28bd345aff03a1258b8e9557860150093c8a5cfd839a42ce204f8cd2fd829b854704ee97d862f7dc62dcb2d61937072f9870236d2db75f3e0b13d3ca2747a27b83485037fd543eb189f6e986b74aebd379477e090ba7fe23893e72a583f7179cc6c89c18978f286692bcfe6df0eba8c2487fbc801d9c9bc03ab7c0031d972c217a798c97f83e6deebadd4fd709ebc12586bae2a9c47829c1ea1c9bb578886b5cd0e0fdbad7538f05341fc9f23320464e4ea1c36d5ef0cf7cf1632b3f43cd612f9639e4e9e40b85d076431a1f34ed9b2cdc47aa3008a050af58c14a955bf5e42cff7e3e311244d1290b49bb6107871f672bca64c47f120b438b54ec23464dc2f8c327dbbb45434ba3b382650bb1d2ed02bdec188b0b7b8656b93db52c9eecb42ebfdd995335a6fd3bd0cd7c55c46b01f0f0a7740e93e8b9bc6bcde581d874a0c481bf00072c0a022c97d98b58c84886989980f45b4cbb0fc133d51a940fcb657539171957a4b373c96ab0b0145202828f3977bef7c8ff5434d56f152133c5e1d223983b7494758afacefed4544e388b7657706731ea2bbac0f816540ea5e52e5abddd5d3832068dd396bb7dceb9ebfe97d87f0e83e9a0994cde615eced1b1946d67eda628cd252ea0985a5a89843b68c5424e542fd232518b9209a0bba1710890535572272ebcdaa748145d9e4608e41dc39737b166c39862b37ad0126d0c09e24f43490c95a6b6108b486111643ecbc1d85c1d4513e6b64df501cf0c7258fe12bd5684686df9e20604647b8a210481df81c0c0bb794571617f6d9cbbf5bb40b6a795f46c194b0b6ec3e9bd890fb3767044461ce1eba5edcf25040a4dd0cd3bd6de43722ef2932fa4ffc942d2e1f27ac982918c3c9c60374830028c31b4f03febf0aa6a0b0f755c60c487feb6117b12c84b78c0c1e368ef1cef5d07e4151b96627a4595a81038fa7e6faaf552a294eb885a442f99b1cd010bba4d88983a1e97414117e6efc1c076ade9fdb7ed5857c8e16cd2893ce846914a5ef87140da4584f56ce6f9a794f948f37de0372c24f1284ba0d4f7cbff456e68a4ad281f7877d79b2b769ea37371dfc3fdd88b76e8a94f8951795aa7c659fdd393b90608a9fc20a006ab935791f73d51b6fc5023f959f6ffc19e01f589e88069c0a2a3f0d3f073fd776109b162fa042d92cdce04bb1e913ebf117f073b5ff0802c85321bd16ce6ef4e0a1d65dfaa41ef43a9988427b98ad536e7ef990f45d5f68e7e244dbe68de06cadfca721f40f351e22407a103c4d88232b314fd88f4cf74be8861edf8227e95695cb5958ff30de78921f67bb5ebb5a677bf5dcdbffddc31e11e1d24b3b5c1b57f5b9e1e28faf3d718c476dc10744711cb88a6f8f3529a0e4ab81190c82fc251bdd3a63a878d8eecfee3177db7a0e938b5b6d6afb7da1331a361f3b0f70e0f5f917d0ee90c5f53964d9d9b272af4cfacc123783867c53b688555eb268b20b5c84a966d9950d333ef8d619466b9a33dd66cc29c7eae736e3e635ae55fe0e721a4ead3a6f2049de47c267185fc720d46dacbfaf8d1ddb410152734add9e6dbdde993ed3a3692af6f978adcd2a7f1c11f1ef54442ad17a5027f76fdf2719cdf57882403cda5acfcc45247b9ddbb6fb3ebac6b8d689392d8ed211ab1f3b6316192bd3bc5374a317f43d1abfa22f9ab3629f6ed849ace5fbe76b826a647ad7b2b6d186058e681aa46f0c65fe77f8aa412ab85443298d8754e8a54b7b7b3e8a0bd15986bcb5352bfd3de2c046eff1952759a5373d43f19b3d8f2ba6d25462f5834a9b975375fe24d8596dafa0346e4c0890f66de522f4559a925ea5fc3dfe7b54ac6d97eabf1c20a41b893aa9ede58e80134e3fcd90b465d590b57ef19e2df169fe657915853505b405317e199b7f00dd9486b39f3d98a745d399fbc6abcfc90d36d2fa7bce9cbfdb1aac2b6c55f4d9ad7b958530293874cfb008de89b456cf598f0d73b20c9d1a7228a773366a94df83d1b2a5d9bbb8cadc44e47587bf73dedd6de42a0570ffd4583133ea161457f3cc009d266c44e671647f8b7e9b629b64f26893cb910b7278e817af547c87486481529cdbb7cc6efb79352919cf87d9d348798ee7664a576161fde49a651a8730b83a19c504f1d61177642a32f01a4da386f8476aa6f904df80d9af1cfa382fabc75eed61b72cb928dd4c2a0250da55b38dd4505dfdf2fc583852787c081f302d1794b3eb212b59635ce0bead5e4c1b20d88ddf6b969b0facced21dd09df4d14df4cedd08493c858e334b14d84bc7763209efbaa34dfb1f35a04412bd0c71f93a9ebbf12d9702c66e631cec35d4cf51699ffdd2742df0ad9e86dca6e0efc10078ff0b72a6ee6566529801e3a1f91c0fad0040d0347bd3dcae827b6140cbe11f7e7ad87a3c06f6c222aecac2fd6a07d88f5aae217fa259a72c4ca7f29d3411a98d6dbd92e203f3ebdc32d226945cecaed8ef59b9be19f6f42457e146c2f0c83923bc163e46ad2dbd3259fd7d0e434a60d85e6e6f53f3f2907f761a1d178d170ddd0cbf3a38d0d6cd5e780c193a0be57a5fa083d5c20023cab6aff94c7437e44f1e6405a2dddfba4479750e07a12536998c4019e512109ddc9ef505ecfa72dfa4ad4b13bda76a97dc4d02dc5dfddbe5de3dae8d49c0842b1f31b900ecad06b29b452d6d5eb176207f6e8907e97b6509cb4e3ae21cb03123fbbccd3247b94d9be9aa7d96e09be049265a3feb46b0af2a616d6c9c8fe7a828217dbf292dc8721e6ff00fffb9663058555a9ea14fa2bc5c3b2889ccc05f2e7cd8ce227667b91f3c45b2264feb0c9886e32f8dfe843a771ede012b69fd019803024083e2151d48dfb4763bc489b81fd1690d43e411de9fab1127ed89678fb2c3b7fd2e9b31f5ae86b5c40ca9128f07b2947b683f70b004d60ef782832bb71ac22761db267df9e68499e6572e0e8ae79f8b6eddfbe2a202a161e33edaa816242d6ac3f1ee0ab1bd05b59c5465648679b4738a308c86e085034c098d3ef7172f757c751c9fc72879f1fdc04095cca4fd39592789271603812fa2d5b57d14eb1a6fa0fa7308571a39fa3f05ec80acf819c873ad525df9a03d1f867d8f74a592d6e1ff5272157efbccd498b72dda4154d6de12eb1c325fe16754dce62610ae19edd8cb4dafdb38cfc65edbcb8b696c4f95072e4193eaa2226b834ae31ef4d0c0b1850b80a58e3844958c6ef4b38279791606515f55fe20f6bd803de2698f6d6feae19aae4d93f0c2a02d210059543afdf32a0370d6a71a6196eedf80c9808b955c30f88ccefd404e4ff6f1036a5be3d5bf171c6842c3b5929b20b0ec9859abfbfa69bfa284dd978079c4547dff3d3ef9ff8441f8ca1ced84627295fb8864f8201cc5866bd00f5a903d01c20f70a6e906f64c03ddb8daadc89cb159b3b3646bc451e5208c4ce2c2ab08aa19955eac7c11094b61aec410afcec7bb2ae20be88e24a50456aad87f8a42ffe399dd7e197210f2f97fb1ac358a2e753207abbf49a1ef5ccf4e0fd0fc0f3c94a7f7edca36bab75ed6e752cd4a72e5221f2a7f8c28f21f1dad862ebd4e7c0478ee1f7a087a709476121ee678fe3009a9f29d0ff7a067caf07c2fd0be41dde9b2e2c52353325b4db798bd914700f918d6c32c1278db0034e6175dce3f402993324537b2e03be6c59c56a926a0c343e0cc2c433dd6ed1232816555afd8d9626bde84220d94e02c9a5251a8c995eb904234cf7a7f8583c5a31012e8791d03ef388d3761c82fb0b7a58b900b4c562b4a71eb375145b3ca64a5bd3cf962c8b975615932a47e28c0e1e8cba2c39a56d976f35e8036afa5da2fce99e6258b1536c083c90e0c13e826708830f74106171e6993fdb6ba33f2c4236687d06f934fc3a327a367e95aa6f0f8e65e6cb5ecbc7d5b9490be1ccf6296ce1d737b475495bc46c6c5f8dd9b9cd9b1d7f50fd28c284036a983ef848954b3472a775d91fdac923451878fe91d3c12076f318e23c6ae495d259d3170409ef6b2253de305ac234c6e880ccfd204b481412e376455e78e0fe15ed0a900f64fe55b72901588efac596c82d2ca3c2aca517e500e1b23aa9cea8b3af12125845ad9a71718e8acd53f587b354c987030de590323b542732494f1eea11c4bf09bc59cfa49af15eab6696c70c90df66f7ec488c02f43d183fbb60d6c42369a51f07b65f52304ccf0b4d80a766ba8db3d53d24d1707179d4382de1bd727b038e2d54e583eb0568984a8193a19bc0a58af3ebddadbfacad2f812853ec0c063c3b8fafd96d16714b45bb7ec162ad182811e1f2a64fe826830a9d686cf7453e56923530060e62420a343f1d75dcd1dd0359a967bb8a4b50ca212b70f7ce93743ba014ccd7d99fba0dee6606a1fc2a21c100fd2db3fd275486a52a014a1f0cf74bd7a1fe32e1bcc9e2690cf3adb3fc9d9277a64fc38384fa00d9eee72eba8323a5b1ed8705eafee985973ecf60fe51202634b41cac26493e2bab462000a76785f38368b2e107da77eac5e46a5bb4699f7d4ded25005f48cb7e7bb9c9890a2d2d367e2b484c5fc65aa3b9c96badcc42e1fc2bda51526c7a710cf77115494b468a200d67a40cefcf8acb0e7d7a9a664e194b640c256b0c9bc8837fe2e96199e49aa002fec3da848ef00f49ac00b5d60f9e841be391ac1772ba36fa2810459c40eff8a4e30a4bbe563ca81af27820d8b1f706a55a3064820688a47033db08cd4f4dbb3618defae88e5795a3f3dd065ef7d63eae566f1b9ed7a0f70793d639de9214793d7846c53e1e7d771a7b00ac726d8ea47edad1ea00e496a02fefda58752593a7858a5c053f54790c3e8c9bc12127b0900eb18687ef03a1c988b8d4a17b4ef835219e75375897365d96b2cab52a3205ee9c8a2fab98cee803210c96ec0ba5eb75bdc7230e0eb9f839e050d5e1749684edec85c44387bdaa9c2c7781f0af89a76fae3d77e06540e80778f28970e001605074048098945f491c17b9f214a8759891e59086463cdc3520936bac043cd0f84e81efff1a7027b5674b8c561983f38a0475af65e3786b6eaa82661bc1f1db7f85350ca38eb2560cfa195f56aee6cf3e0b9806e81fb88f6e3dd5a62338e98271aa1cdab4dc32b567df90486091b47bba4043428ca314c795e2dce9507efae44c11f6a663fec4350fe55085adae0ad3c26f2519ffa102663984709d9a8b65606711fa012b47717afc664e6e51988ebbd98837e60c0a8adc7af4484527a6f44f8cd82db2352e705386003e2459b808a98500ae4beadbfc4a505192380e1004abc8019a9883a0eada09f7fcdb349b2cddad2560433d032d1675aebb20216831cffb29ea9021b4dcc40968b5df1281614f06d0c7fc28b50049a34d239b3e4430a4fa970000af8aac925734572409d87d15d633ce5ace38038cc6cb345bdad87816aa669f6b5c13b0c37da3fdd76c19ef09f5e4be4ac2e4aaa8bd028261de630a3076f8775ab1ff7a947cca659f7c6b84d08e050b8059db1850e3265e4f3f914f8bfdb14c1eda874dcdcc1d22532bb57d4fa3079db678f572a9dfbebca979f0e4dd36b3a5145d3ba5da082caa401a8636950fda65c6c35e5265e6c5e3e2b011a735c0240895eca28d81e0988e4948738c0f013d5ee5752392e6816def22350495b936c84b653febc7507684d93a3b80fbba53cbd283b9ccd1491dbc41b6d2b57f28d97a176fde8fb3069aeca915661d0a0652e8002c073112f93732d07401af4e05943dce3b079e7ca48a4c140088ed1933d059c7528f8af7385f001eb24cd0a1319d6ef740379156b83966ab73338e9ea5eeb05298864d4c0b1679fdfaca17e94b44b8f63d3d20999e3bb8a43e27c7fb17d62a08fdf440f5f748b0cb027f6d9918c0c626faa0638c15551b10f5bcda98f1ceec1bdc7fe66d07caa8f75ebbf310b7d6f6328e6987c0f11ebb5fa8fc9ccf16dceae90bee4cf735f1bd488d16fb0080c6ad786396e09f6411df2dba88ed890a9be9cb08aac6a9cc31fef31391b7a8b4289780193749664e40462160f70441fec5c6d89c54840c499e14acee9e09b39188c861d478492d0f0521bd0dd236ee8b5a40c90b4f9bbf4a372130ecda37a42c5b06aee2e7ace4408ef15f107027841ee4119836ea79a6fa7aee9115b93952ff43119a311bfa8cebd56b363e0626742e2cccb85e9ecf9a7f7f9ed6fdd9c01f7eeb0f94edc38b697702b7f86a4ca44b5a5117844a5db2e3c40ac7351eed9f38381e22277607f50d5b4ed68e0bf298cf29dceac9813a7e46d4cffa734556ff460eb67d58edf27e554c0a88925eb93a8a405f6cb08bd71c4ca242f86627614917a45e4f7d050d2bcd8564fd9fb21b51055126d2ebd7cbce30bbb3e94d56c07386e290d3fe11e6e355e981f1c7ac088f303b89ebe17ea62e7d3c89bc6f2e89b67c8d4ff429e41b80b4a6922e1f3fa022306afe3990747e1ac8d30b84e5b78eb58c2fd332c7bf3c715fe1fe16eac3d3fe7bf4ee6178be9c9d12afb7e2622315a4aecc6e758094ce0ccf30d317290cf43acc3439d7307d65ad942787925b687ab6a8e64e90c335412c5ef0aa212d0ef3987fda7e08be228d8460d1b2589eaa53b0569a3d811502b15b5421b0d75ccaf57e45ffcf8e302da623d84f5cf71a9f5580b12823abdba68420a5b901301200e03e15fb8af1e0a7c3dff1df0435b7f996da225571a6e6e28da00e6174c735f244219101514c88a6041168a0d552fb0112a608e5afe4a4d100b724ded811a9a886ff67f588ddd78bf38958322e9567ac18164d11139ff80e58341beff18e23faecef1d77c636ef2c4b53b4b135c0785cfbf8f90f606256dc9eabcac8f944aa868fc741e3377dee41ae12bc14cbef0307140e0f17a3ea5b1137c10f1b0876dfffdb0f32aff150dc9d39282d9c4d467e0ca14a1d8c3af99817408dc971eb0e3dd079cf4bf563ce1733f9923594ebcdc5eff9b209675429750f123f5a54b8c0dd2a7a7be9f12e9b92704b69927bf4fd71ec42ef48ad165cd9912d5eecc10be0303b9dc55d5eeb8279c96d938e49f5675dd6d8905683bf8f883503e8f575af5f3b85e1921d34525421b33208d878956e11bebc5a25de8e17d5f94621c8b7a20c7c7796ce658260c15911f85c73de4f6cf6da7e8cc89354c8d9a839bcf1936f5cf8a6654134e726d6876c2a621d4f8ed729042f2dc637184e811471f4ee09331c5fad1b3688c43904c4bdbbcc38079674d2fcecde9a6fd1aeb4e8ce2867315ac6281c03b4097e88ac3a12b9a5e127ea321ae94ed232ba90dbce7d26b0d7852b80716d2800e34a373d9306c9eca0bf3ecb1b7af44aa45a85803a182af2e321f09457bb55cbad0d375b0b17ec20f98b65eaf40bfcdbbe2b8031811b689d17d719390bf973f56a7d8ea77716c74248ee2abbb3db47581193b2c06651d4e121f4cd25a829056c4439afadfd1753f81c186066273938cd42dbb038ceb4f44dabfeb6b47d84c848c1a945f3932335703b87bda8ec135c5f85ab0b7bf571380835f12c34bef5bfad0c91de1bd57634bf4910f089862d9b724e8d25b5a5469336d25cfefb99429f186b4c184263bf1f48799c6a94e0e889babc475e7069760827e661dcb212ea3c011209308e5036f55f69ee799391c0694f362f3dcf8a3c5a7d99500788c013938119c7a1f445f0891c3f4dd474ce173432b78db066e7c2efc5d0d246bf530c312883eb99df7297e7ca28b4486ee08e5ef30b279208498704748d0e38e10687141f0d1f9081253c6cecf1fdfa0b2fdee1274cfe334275cde3a41011b34ba80acdc1a0d4b9a4090d5c2cfd2bd1eb97e324a20d644f08ae942883b2ec607fd0b3c99be57f33f107b3fad4bf708f9b7f47b74d87e4312652c29037586b4b8cf224d695257c767ca2d7b1d00ae36a0585d9e0a50ec7176866ab42bb41ad73522f2e39804f94d8194564eab123ccfb8a6a9899fa660a791989d8fced2f1fc0388cba6fe1370abb42d635bfd5930bfba52979be01f9e1606eeb088c77eece7657096410af87440b80ea823fc630a5f335fa677f5225159905688f0272c66e00a1bfcc553de04e5015fb4c4e63563ba3026bc056f36d100f551324b3879c32179e1fccb0e75471377775c19d0c9c2ea2235b9eb61de4192ea34a4e84ec94cb58c1b2df67584701deb55ab20169b8660fb0aea96750e492fda93462b787c686014867e2d11a08c04c85d4bd9cd5c2b1b50be0be398fa0b423a7aa1024f37194bf7e7f68c00a446fa749a48e5f0108294dfc6247ef94c8070803b89c2eb8c7da2838f6428a3fdf8da2b85c3e32aacdd98b7db4340ccb1978fde0de95ac2bacc7ee5ff32d82c12b69b0f3e07227c82551a6aa96bf19a48fed1ea461f4d44ff27c04f846c3b5a3e81beae7c980e695a1a52baecb4d70dee370a1eb287c8cefb8807b2caf523c95d97a4bcd42dc828e41bbc9273d0b3153bab91b2b195d82b4cbf7bc26d3c4929f7a2116d275c23a4db332d96918b8c4c196087f49feaba1f4bf7682b27a8b5290b66955a560730dff498f8ae17ac102c63645323e713f3da9b947329f50ffbf74f4df5b19f0fd18177e9bc7cb643fb12681ccfce90bb5d49be2b99532c7a4d3a0c891748b3ab9cfcdff41b6cdfffe53912b56468d27e2c9ce2fe08ed085b6c9bff11a90f5ebcfb1753e5ff0903f19761364422d4745f93399895579a9574642d0be5503c79e39ac525db8a43aa326b6812f8569188ce2887a36a8ede141f33488359d14eab7ae8fc7b5d3d5e036e8b77c17d0c616d2f573959b025bfe4c3a63281f259f1b68d66eefaa5b2e22bbef23c493a8f382faaabfc23c1ef4dcb3522b401f1f33d78233c9feb0c289a82b3c12652eb9a8cc55512d2546200fe3473a84ccfeb252bd8be2ce819e2d6d84c96bcd4c38fe1ecbe22ed72cf9233dc6e3790fa64ca36192b419af4284f38f8b84e26ae0bc7a3dac30bae2e46f83f108d8ebe480d3d5b9c2fb721ff8080f647aa1f2d66d8c5b5a6c2b4b6e2efcd67fe9031efdf4e3abe065296b3d3c0085bb637117be75dd607a59b90e37927a28f6f588c8390508abfc0dd651fb372b452f3205b760b04746162e24a63c0f923871ca84972840a26dad692f28c555d2cc7e2bd6757222532c564d2abbad134f9ad74b13f766a16a6d519f891f1327ca5c936b86a212cd8c486d8e021f323764f8f12ffb3a636d8f6b18dfd8afab6fb9e92f3e6366a330123cfeeebf8fe49a4cdeb61e240ff13a0f22606b8fbc4f0aa71c4e67f8c7f433da937eabebb81bb3b8bca4ca4c7db511bee48f7b2fd65eda386093b6e9773f31bbb5d72ef0381de285f44f170cc3da0b35731dbb5713f4704e30d5c2171f697afb1ae284b4ece5c7a87ef71cf8e1115ab6c177655e9a09a7c02574783dcc54eab5c3750b2605c19cb8e23adc7562c7a50dd8ec1e2d9af79996a63774dc405705da9d81306e425da68f4f8ee66a12e7f5222fe791d37ee00c6c95ffc314879148cbaa3e8e320c463bbfa5704ed648965f6995c3acc5f42e7ff53f2d453f3f69f192b3079f818795206b3b11df58ee3d73af915a49a5d2bc218b0821cfa0a37a97c1377a51f6fdccb01a5a38bd2299e7ef44b309ca463c102090daeb76f1655236fd138a76eb3d056491acff99078893a42aa0db3a50ed402411d46323e80c0b5e7390997038f1ef3579b373e4a215c8584be5ecb34c9ac435e65ccc71d2923f219438aff32180335efba48b4b9a11a09eefe1fa2c0cf0f644d6e5b5efa912f42661904a157b4d82a616171b188d0f95a6f29ae6a75ca9464683a7ee55ba28e10a755f68a0774def914bce2f9ac7078173cf2b239c219e0f65e71149499023ebcc44c0c3c60ae26e900c3300cc3300cc3300cc33074c4a0f97ffb0aaa0999929cdc43468ed091755d5e534a52269992e0ccc4979ece4c7c3a33f139ffffff91ddea0b7c0c8c0c076179e94b45445270f5aead0e7bde9a4324ccc65d868e314482d04ddbddd01739964224f758ba98bd62e972234492c5b0954b1b6db55225c8918048c4bf04393a12c904800b600c2249c78f2a9ff9f2553709f8b0f181800f0960e1050c4124476df34b42cadc7c1cc28319f061e3238b0f2db0f888442211c445a40cc40ee222b1002310c9d933764e13516f970b49cb9170ec87165744223c6ee8e8828b2e6000228b1ddb6365b977ac70b3aed0a4322dde9821f921010a7c7c48008b48a48c201221418e1b0bb891231221418e1b0964a900e30f49b536f29352f261730b49fbd0e28a8f08053e6c7c340087172115405c1c00d400861f92b47c3484a98ea7fed487c4d26ad19cd4759af990944598d97a68bf94827b48d43cf25cffc3c9b49e1e12fdaac23f3f95c5479793596c01b203461e924e88ee533206f129966e01030f497a2f6679cea8b8e778ee90149336d1d9fba1e3ba2b76482e7539c237bfa4e61e92c7bc805187247d516b29c65ac89047229148064c165b8080c0a043526511954dff7c45ffcc21d933cd3d07e149ef5a42d2ce0160c82131bec6db86be0fd965485ab1e45c01230e49b9dfacf23cd4dddb2f7a7b78a10347030c0260c021b1359338351f9f22436f48aabfea17cda3b7977543a26b0a72c388bbeaaa6d48aada1c83d4bdbf90230c362459baa7931522173dcb8103640d495a64deef79ce20ba1a1264e68eab5bdae3c26948eeca146f62a3a4bd684872f98c39f62b6fd73324ef27711daf6be163cc90182ee85ca63497c658860495348af6d6b30c3a32246b6652d2c365ceddc6906c5632643f5c6c4c25312408f5789f37ac66f33024a5f193bab94bd4074352f8ffdf5ce5bf687f2141c664a9ac3e95ccf65e4874cd1a7e83b6d454ea42e25ed89293e184facf85041f0db275664c4fb885e411a57b745ce7ff722d2496b69caad1475948f64af576116a2c24fdbe76eeb46f41db7d85045dbe95f543580e765b2131e5e4a26dd55263b8ab90f81ee61ac4aee96b9b0a895521e28212e9a6624f2141e4cfef2c97e50c5b0a495e4acc4c27d32caa8e427238db8caab9629590a190b82174c69db77958f8094959a2972a67c37c6a9d902c9ff9ae479caaabb609c9611e4586093121c1cd844ee9d799b95d4272386fd3f334632aa34a48ce6cb59552df87bf68121236bba66db9efa620242485d2b70c6e9d59c7232486d1cdd6d93c9fbed20849bb1994a8e7f591a92c4252e5187b3ceb8708c9fb394675a896ca9633842495829eb28b1d2124fde914eb4d3ae85f320809b3d12dc85e35d1d60021613d3da894e436c8d5fc20f947e5ba11a74d69d5f820b12e6ae80d16e376d2f42029bec88ed174429fc6f02071a4a57c9774c62463cc0e122daa2825ac4c3a48cc8bd62d9682b434e520e1e45874139b948a493848d2af34ddf4fcea166f909c7afb662a9dccddd006891b355b949329c5e0d720612f77bbeffabfb53448ce59ccc4e8b5cc20c94505d5a192bba74c914162899f8db5a71824fdb6a739b94e1e8241f2a9cc9c936c66aaf40b92cf7f4b9d34eb4dbb20c1536bfe4d328c16246fec73919e1a359d30589020f62abc8f1891f5dd2a12e37f4c27478b9865a78a6411fdfe759e56749e8aa4ec182ecca7d2a7332a9277376c57a74e95d74e91e81ebe2ee77535ad99291274dd69287929ee95aa1449a74f28194f64a8ca9222592ddf59e8b5ec54b25124073df55d59af427644911c847f586b2514c93997caf795773c0914c9315784febbbbcbe3f81b20e847b086de73200302a296c516200bb0e21389a9c33cad099952c61b7c010484159e48f6af5c42a7953a91989741a7b2e4a2f33b2712e5f754da58de269234ae99a69841ee6f0a0708ca911fc0c1c5170a38a782b04213c9651927ac742c13c9e97b2a54a89c4c4dbfa30788d9ebf8420b341e00088415984814297f6b5953fda9a04b246f9d8cf6b96d46b36889049d4a87160b2d246d0421d073082b2a91a4b5e683584ed1635e4a24e5afc6defe7ecf519944629996521ee6d5ff11840024f1df0041bbc003cc05200f564822293db626993bc6fc4e8a4452be117a93fa1883e710249282101bcc36d4cdeb472479c79ae76b7665b71d91246ff93a69ad0b3e6e2392b2bcfac49f8c419e8c48ec30da32ece2c53db9880421734a2fe61bf25adb1b8c63470434ed004014562822f9448474932aba54502292d4d7e575fe1caf5a4424a58f9f29a78d29ad7842d24072b876c08a4324e6cdf2a343e88a9e6388242b338be9d229a1635c88c43b99b19da5a774ac42d2fe03fe1fd8f15d98cb61052112b4ace6c78659101e3488041d634a5aa184acfd2c8804cf0f1f4447bb701986a49d644520123fc35bea6ced523b024492d04aaa6379fc87e498f1a1fa3c5ec6510949bbc1df830748086e78d1c63eb4b8420b2c3ef6adf04362c5a8ac393d6f2a4e7d480a0fbd2c51bd29899c0f494a9452225d6f84ca7c0f499553cb5d53ebce8ef49018cb64d5c4f56427390f09c26d3c8ae5b1a0f38a87e4ccca1e192bc8ad94de21315f662625eab143e207e1c13f954a76725387c4d0d3dc1525f767a143c268395963c252c5abcc2131a7d9f9d2dba5d3c942d23e8003c4fe039ef06361e3e3021f363e1ef061e3a38717108844767c08767017e929f09e238b48241289945d009060851c92347bbc7842655c37c521c994878aa8656a2ac121e1732dc3e352eafddf90e449dc754c64b57a3724d606193ffd9ed08fb72171647d792599c46294d89014d6154ac6590b954e6b480ca2bf59eb3e5ebd490dc9174f2f3dba892837a521417a6c6aa9fb28a32634247f564fb10d17849de90c499d475787c6d11c663243c28bfb97ead69b90496548502564485319eb3698c8909c4f552dc831ada692c690581de2229aa1b27b490cc91a36bc7fc758a972290c09fac44fca49df3b2d8121b192deca78326e28ff0b4967dd1d2b8df04abe179254d0a42d8fce73b1ef42b2a512a1bc3c2e899c0b09a7cd74b79c8a6ee8b79024df3c56fed8eb68bf16124505bf3f6d1bd5f5672139a45f8aa9b01a133f16923e297da5f19d9398ff0ac939368a6d0cbf75f7ad90f419736177d9c9b35f85249d3dc71362939fd0792a2458cef4ff73298feafc14123d2e9d4cb12cf96c5e0a4996151fb3a92d8ef0a390fc21c34566de67d21e0a09aaf36ce7c7dec8ea9f903c9b496bdefec94a724272cca72083de3bf352131253253b4d0d13d79f0949f739a8ace761a4f94b480edeb9829a3ba9bb2b2159764f29fd36d1b52721299d650d563a4b9c8e84e48fa6f2d6cdc354fc0889a36288ca237473b48d90201afd3efb62e9921621e1eb53547bada94f9d0889957544ca660809d6ebb9ef62cace092139a797d166397f6a198484f7ce9d25bcec72070849af1e6331d67cd29b1f24f57abcd471991af641d2cec86dd1fa2617f62059bb65fb335d675778909c5e3bf67b58a8f576909837738d7af5105d078931cedf8e3ef5fae62039c72e26d933f94f390e923aa7e0ed7baa7b946e9034a242c9147e64850d924ee36becafb0d942c88a1a24fae6ed4a0d16df6d64050d124c8a69a79c97a66164c50c12456c380bfd8c5d17592183c456150d2ae7b31c2ab2220609e7a57dd3d4659a0fb20206899f82761f95730a4283ac7841524a32cfec59a62c1a64850b1284f6b724aa458aa5202b5a90f862bf3ba2c4c7c790152c48d2b93655f896af584800abd8539e53fd0aa922b18434e9295a44ac291589416bac0deff2573f2a92b4f79eaed1eb1dfb291253fbf53573532475c7b9eb5a121eef522477abbaa82d1d744e9322e1d3dae2977e2d937b14492e22daaf671bb46d5124a9d5a7d0b90e45e2e9b0bb939fd5a4daa04812729643965042d9d99f48d29bb152bf527930db1389419af8f9674e297aba138971af9f266447119ee644d28758da7ce7f17cd39b484a29eda75c715398d89a484aa74389cfd3cb6be94c2485b3fce8154aff7b8d89e4f5fa710dbbb494f52592e2df2e74cef2ad6a4b24dca8b1509b32fbe92b91acdd49684f7147ba881249667b722ea9791249e253b2bde441c3bc2c89a471dbce20e66299cb9148d2aa55fe29fdbcad0c89c454626389acabb4243f22f17494eeca525a4f871d91a0bdf9392baec9fd702312d544fc3f656cda0f332259335ca94e21534e0f2f22b92aa875f5ee06b9614524ac59fb75a9a6998613913473428d27d3385a64442476544da6e25a8e06b903e6973fb4c0e2e3a020230e496aebf1956a429bb864c021e9d462b45119bd224a6f489ccbddd9563659ea7024f204196e48ca271f1e3bf5065db90d4979376e3655e9745d2c39d30ac86043b295bc921fa1b2785d64ac21b95f64f251756e966342d24aa4011f366e0a32d4905ca1622ac275b7e6bac16307028e211c5ff04003c84843c2a5b84e0b4a4d6b500a490309e1e1858eb2bd42061a12b46438af0b99d3ef7c86a47e2bad1e5aa7d469a320c30c09e2fd2976082dfb6ecb90d8517436fbbc66397b6448d84e19214f0651f56a9d20630c096e17639596e7b9cd90b4341b1f957c48008b8f2cb60039810c312468dbb09ef42915a39632c290a05ad40661a64bec8238824864419020030c5f4898cf6b9d2f256155b18323b802c8f042829acc35ffcb2cb153489a0e414617d299477db23124ed4e0632b8b0fecbcccdf5ea02195b482cfdc9b4634a57aec19034906f820c2d948c2c94b2f5abe8b45752fed42cb60001400e646021312c63e7ee988485e884a495c49320e30a6b8e1b64778a69f385a4fd0e1d2039dc58dd0a645821f14c45533243fd05654722914824725676699051853d77ca5ab13aaaa5ee8ac14e5467d827adac391b1f36f4f8400615926dc745b55f8a135b4f21c9f268f9ab2ccbe9974252af78086da23a834a461492f3e9f4f9423dc8cb18a4c77fa0c75f1189ec0e644021d1a27c75f6d8d02fda2724c94a5add6d4d7735c60949992ad6a9b4fead7c13122cb5a99171cd91291392475f3e2194c5969074b3264f4366b4af78a184c4f2115a47a9d5dab64948f8dee0b1cb93ea6e90909c35749df55950a2324748109e529fb9dd9cf4abcc40861112630a96299d8cbb97531112d6dcdfb26555ac58224292989c98daec595ffb1012f37beed2a24db33c4808c9e71e573c75f0f5980e42a28a2995cf74b659110321d172446d998a7f902093e638f517c592cefa2059364707ffb3b87b393d486c3ffd9744e44d8a4880f63abed06207060c063278907c7a749215eb1099e91d248ecd5ec5128d7d2624430749e549ee997fcaa70d472291c8ee909183049577a7d72d28517fc241c2f7789f18dd603e9f1b248f0e9e4b68bbd820d9a478bd5f8c18ab5c0d12b6b26c978ca3ad7e1e2091c8bbc92cb60021810c1a2485c71a7d9d2a7b076d165b807840c60c12748efe33a7e232481895f6efc35d4b331b83e48e959577ef62e729810c182428b1cf95f77e224e7f41920ed7d571597537d31d90e182c4b46fb37cea4407437a7041026b192d48ea8ba3945063e19b4e32589098e2ee8c86c78bcbd12a12536f5e5b3d61579b7d05b430554582b8ca5815d6c2adc3a94812d2c3299d5355396ba8484e2944cfed75dcee4f919441a7cfa8be0f533aa648b61f9556bce276435b8a84df2d0f9afd4bc7a563902231ed6ad0aa72f7b1330488e02bb01a63148962a3d492967b98fc185124d6c90e9522963eef1d928627462812743c26d54b9b0b1950248dfcb74bd32986d014e40338627ca214254e43e5361187a41d4f74820431389198f459523a99eb6efb9b48506d153a6ef6b64db22692dd3f7f529edc4c24c5f6c75341638b102a26923bce7c8a33ddd9ba5c22c92dba558eb92bb9e92c91b0c1c3c47fbab2cb2c24ed8b1d3dcc74c7f7e002635422592c3eb57a558a6717f522062592b6b2b54ce78ff3f2e8f13c78200073120715d76bf552caa737043124c1d6e7f5a0524c35670deee8a12346248c69ef635d26b9c1c41e03129a250fde9eaa317d623c22c1bb4a084fca2e67d82c80021f6780188e48dc191d3b2fc598d37b2169e3e38a372012897c688185d90f6234629dd19bb289d97c311b831109a3dc66bdbf37b8e6622c22413fe9acc93c6c87ff2611431189f6322aefcdd68e25212462242231e5f9aebc5c47cb551131109198efde82fa7499bb8f0122c62112334468919df4bf5529242d3fb4b8e2e36f78037044202f8b2d40024086188648b2978b1f65d6e13b7f861885483679e2834cf94975a643d22a1089ec781e63884188a42463e944530e3a056119880d225174ffdc47fba4188248b08a8f29e8c5921733c73a10231089a552acb867a9410c40682f1e33c58bbf43d248c03b1e99187f48d00e55ae56e9346d3e24adec438b2bcec920861f9246b606cdf1ff3db512f0a105161fdc831570b810a30f8949698977795ed1a9a285187c48b24ec1f28e9e52bd5524620662e5b2d80204005788b187e420f2378ee769e91163e82139df7d2a61c1c59387b13422461e92621ccd66629f73e5080f09a253651b317ea6c3e80ec9e9314c94e98eebbbed90943f27edaef650cd751d923786ef52df613a24bcbc8f860afe9a7bce81b5d8e017345c057d2412899c81989a952cb60001802762c821a9e3eb43e7521c924b45644fedc614730687c494d35cfc5097aa93de90202a98568fd1c22f7ae8e822c111810f1b1f8e838b077cb1e3bd98c0878d8f0f1b1f23884478bc8e2f7a0c22861b123b5d760d4aa9515bd23624e85c931f57c9b2a6d4b221395f68964ae93fdac715440831d6901494de18cf31c520cb53a210430d09a7beab9e9ad7648d4240de045cf0484352eeaa323d661be2b45088818624db9c2ce7cedbdff93f43c296c8dc369fd333e966487ccdff4c1f6fe3622e030140136294214925691efcd294494178f4c0c167ec6c2006191294bfc9abd4b3a04b1d92b638628c2125539e16195378e3064722da238618123e495f3df91d192b179276628421e94aa71837c858db9e1c7923061892530e1bba69a74f5c3e24f13eb4b8c218cb620b1015c4f842d2ab75ca56665ebae237760831bc90d451f3530cbbd183287521498de8b1d5a0662efc5c48d4aff82ed9d51d8d6f214198577e8a95f9378bb49028aa424d74bcbbf9370b09f24fd46e9daa4aba6221e943897938bf5c9acb2b2475eb67b88d39c8868d159244878b23b4e4c449b10a492a67d7df6f490bb15021297d7c3abb14f3c8cd4d21314673306d2f3fb53b861492c6b6e32e962c0a49ea2986b3f1cc2017141236a9eb78daa257cd7fc21d35c9ded67cd4dbba88e184e4334fd2abba4aba5b423212e11189a09a1d3d747020461312cc37cbc6790f711b9990f8a9a40875299408bf428010630989ebd7aa96db0d2e42803e88a18424a163904968dcb01ed3e791848453167a39b65e558f8404a1b264af259583cce908495aae4bbb36e5cfa61192d3becd7da877b6d417213905956a614622689a74eb88a587282506620c21396f30b124eee4e82a8590347aad627f3008c99965f3d7f8a526ed0021c167cc3fc7d99357152f74fc20d1bf3533bf443e48eacb0d1da24ad55fb4074932dde4c6645d7af81f283d88c183a4b2bff29316eb3abf8384d323d7522ead3e99749068997450eaf2bea1fb1c246936b72842f9ad577090204a7f122136ad8daa6f90e8d7a6dfa6f267ceac0d92635a2e554d31b3b73548527a964ebf7a72bb940649624288f08a4957e7cf0c9247e869b4b49ddd622383641122f45c4c423b9db5a3475d1189442266478e1ebc45881183e494d48679ddc8c9a08741920c953e9310b9717e7f4192cdaa5ba5f8599b6717247f8eb18ffe7b29a9589d250362b420a9c3829219f31fa7372169761688c182e4a03193a76a7c6dda0bb50f2dae8844de23912eca66b10508006c00631549caf406958325594d5e48dad95d5145520c1b136dd1d62b038c5424c6baa096ffd6f531848ae42cd61a4c9dc8b849c7e37b7cd1cbc32c8b2d4022304e9160e9a468146162bd02b9718341f63eb4b8e283c78d1b088844788420478e10094422918899e2507b27638c9961643dc02845e2afe88e1e43683395d58901062912947bd2125a39073ba50f1b1f5a60f111310331a30518a348f64afa2ec69ed2edab2892e2e952af3d719be2ef0618a148165bbd64eaeea3390b49cbd10324123103b13a33c000457292dd167eb9825d7643d244f03c3e91f4b2a2f2c59cf2d48743d2747017208ee33381e18924ed9564f0b1533a2f0b49c391e3460e04d289fc2e7d4cacce02290117e90c274c9a84ffc7cea1623abee80112827f1d190263138951532839b9b178537f81834f04606822c174cea961e365de4d960bc0c8447248cb0edef29bb26993050c4c24acc6d6da4df227375f22b1e7ffb4969629952d1fc091e3864e0086259252cc245b6fff3b6a2724ad12c916e2bde1d13fd6867716804189c4a029cc59759cdd749a44d29c0c39dd34eac208e14908011892482eb9d9f2bcb44c99b40b2ed07b24922a4fa7eca931c39e1e9296723cc88e1c90f8f23d9aea60a990b4127cc102c62392c67256d9cf3199e388c22e6393a6ff2169660ab073008c4624e7fa8b72954773dc2824ad24e6fd0c0c4624659dbda815df3c9a5cf0e0620466c7f3d8c11220008c45248cecd20f597379d55544b266d0cb9694ccd39e4d44a25765255fba0c640c34b490389b9ea4acc9decd310b895e9f736e6ab30834b090705aa4964cf1275e63481a0fcd038d2b24db6e6727b5ec20220b493bf6a1c5153dfe8ab3c2a1ca4be9fd945b77b3071a55d8949e93e1f36b44e5692fa0418584f17c7d7ee132a5cad9f828097cd8f8a8087cd8f8a82c3e6c7c14043e6c7c14161f363eea011f12c0e283c614126c64760ca62f888c14122b79f89439979b6e2b23684421417fb018751b4a897a43d272470f1d5cd0804272ace66c257e17c4a54f484ab14eb3599af56817485a82861312e7a2e7e5703a98855e12349a907432e6946515db92a9e431582e12349890286a94ecbe0c253534a3b184e4de28a6bbccb2059f43d20a0d25246e093d5db2d5f3b7a311349290f06759630ca3a3c83c4242d25bba0a212f692ef1ef11681c2149f4c4d6d85d52abee1a818611925c64ee2cafa13399b80889a2e49b66a98e69849d55a04184642d753961fad356cf212475df8bbeffb8eed7508186109284fb966ecf3d08c9222dc77e110d1012b7dac36c7f4ce79a3a3f48eeb7f1ee9a51963c46c3078915f4e9ec1662214e1e5e380848177a4da307491a3afe77b6fb8efe19101a3c48928b31731e4bd963ee17a0b18384d3d9da2f97455f2aad1168e8202944edcbc9281089fc8dbe001081460e9233697adc98d945ade6c3868d0f6fc0878d8f48a49081060e12b793bccccf9d9e5396bb038d1b24478f93f55435259440ce7044c00211d0020b36d0b0813d7739e9fe8fada690b47210f8b081810f2db0f828e56c0d68d420f12ea664a926d34c551a2429d369fb84d87e40630609f22be328e5393d78ce073464909439658cafbc52a9550c92a3583aa1162e0a7cd8f8a8097c48008b8fbca1a30b2e763c0e1c5c1c25d08041c2e595cfb135b22a684da0f182e4ab7a75ddfb58614e172459505d4ab7eb767b121368b42051357a0c672a8dd506d16041b2e99a6fcf9a625a6815c95ba1944a327809575b1549272eef661c99f308995424e7e79b699d6c0bb584a4e50466a0224929754a7de35e34972430e314095a721bbf512c37f7a648b07c1bd3d539938afd952229cff6655fd59f976a1e308314497fea77ac72ce1002c9e13b8aa4e06a156436796d5d525124ac7b7fe84e8dd1608e1c3a407278862231650f36e2c3858b3a1bcc004592ce7f174deb668adb84a4290d667c22b9e4dabd06d96226a39034901c7e6430c3134955967539429fa628091ec7efe844520e0b1754eadd92bd85a47d6162308313496a32dedc5b4adbd82612f62ad9a59cf3e81b2487230c666822d14a93fa89d8ac56f290342f3291f8e1df9256d3d13326923c9f0a9b96e3bae71289f33aa67ea753fe7521692039fc5c30c312092a99184fa9520e3a3d9548bc50b3e17e955a5c0e49d30192c3ad05332891a4b6ac5296e9e59b3624ed6f341b0b664c22e16aa388c9399d636ccd304312096ef963e62df19d5f8c44221110d3199148ce675f42861191a23121696f8224c10c48248a5a8836f7d98c5686a49d11cc784452cc5a28f15bd5a9bf90b42a2798e188c4127a47e6ad8b4e1a0b49fb1bcc03c4404280b8e851b6e3439063c717331a91a473925d4d7953379d1149dd2273286171943ad12212b6ae343c28bb905e86a4993d6a80b1198a48cea235a6101d6ce7737ae0e0a28b4424567a865aec68b983638717bd17988188240b4bfee14d7d0ce286a4150f661c22e1cd825e8e3fb5a242e0cc3084193ef6b3e870d6c9425c1ae42b2c736bda94a285198448f678fa84b668f2da4637ba47032291482412b9620108c28c41a06e2fa58a921ae40f49f363382c509629982188e49c6df47d55da0fba0f4973636b811981483e37d37fd9964999199236821b39be40aa0510892194ee24426d2b7a5fece8a15fecd0c28a1466fc21d992e6a4b34187db759230c30fc9de371e97a63e2467d56b562aedfd69c487a4fce9b9fcff524de6ac1166ec21b984c99d13a69485e9f490982fb86dc6aa320fc9d1cb6eb35f140f49f76516933a2ff996df213969c9eb1237ca72ba94812c61861d92ce532c7ca63e211e05cca84382d09a9e848b1eab14ccc00c3a3442a886ea1263ff86a49d197348ca3146b6da9a4add3a0233e49018ae3246bfc3452e1b92a61f981187c49b51a9d2834e42680e480e3f26980187c4f2ca74357af45cf043d278600966bca1dc9060a3b51eaf3742e6e2468f1c38ee4630a30d49a2222e68d2bf502a6576f4d0d181196c48d00d72a5b4e51c3dbf630d89a636b7fea67c3766a82169332813d5f3def47c481a17652098918644d39d5410b539c59423c20c34249c8914117a154ea45198718604a5949f909b66d7926e86e450fdf1a37cb2f338cb90982bebbd97c7e89f62a5cc2043c2967f72f7f92474e72169261261c28c31247807ab7c42b73a72ec504024a223c70e6fc0073e8b48a40b2e22911962481a556e32ab2a7ede38e4438b2bfc06175ed88c3024a56813611ba488b6e8d9058e036680814ff71973fbb4f885a47d2131b45a37fd64cce6a09d20667821d9de34468fbd9829cc0cf8b0f171c5878d8f057cd8f8483e6c7c28e0c3866a1712df55b37d2ee175f1c485e40c63dfa3c22c4fe52d24074f97325632c718a50a66682195533e939eee2c24872719e3422c735bc54272cea083da50a269b7bc42d288673eb597be82d2592141254f3199ce28329f6c46152c6dcbcac12aa714538a29337abb5db80abb9bdd871657981dcca042e275d8b8fe297ad2610a49d30f2dae78cf61a3c757608b0733a6907023cde4854ababa5433a490a0424e35a6be13261714cc884282caf699f1a2af74b341980185c4dc0e6db1b17be38d16c44ece7842b2e612535a1ff27e2f663821d13bac64ba794dd567485a08c2c13b1a0407171ff83efbd0e28a1e3972ece8827f478f0a3c173bb488442299c516202298d18404fd14d53ef8dac50c26247a5e66f7d0bfcc29c7c58c25247710d272c47f97927266b105089aa184a48b8d717ab3b369f9242468bdbcaf9637c9c51a0949d94b7e974cfff1527c84c4bc0a1115ec354282289967eac13795a908091f1efe59de7acf2742c207a5d288fcf3b9740889db5f32db161442921abd993cc53008c9d984b2acefcc2c2a0442b29ad4bad8a23bf45e7e90947d74ce6ce7830411f61b36ae5d4dd983a468af266ee37fd618e341621a9d95ffa41d245d0757b3b00e12f36ac7da17d5d9aa34302307092ebb9d9be637741a0749aa42abbe798ebf79832475a35ab529c472f536488ccd25a754cca43a5a8324cbd96629e5d329e89c0649f23c9857b438911dcd20c1d2a767c5b93291b30c92dacef2c566f37ca118248ada1cfebeb3754e2e0c92d2cdb68592ea8c1724ff5d0e4aea65190fca192e48b0be98e4e34f7b8e39335a9024449f9299cbbcff9a192c48929e4dcfaee550e16356919851835ab098d12d8eaa482c0fb23394be97853115c9a54c73ab2ee7e0a2828ae44e753bcb683945f2eda6ef4ebd1a231653247ad850ba44673be54a2992c4fe85feaae829f72345528a51bc2ca98bf182378a24bddedc175d1489aeae5d9a39e6dcb68522290919458d322b191b1449a133a90b1b663f69ea13892e4a6492a9844c0d9d271233c98a5529c46d0c071720df83cd3ae18e8c5bbbbf6d23f300199c484c6225f32bfe4d24fc893f8ddfd5ca18d544926a6829b9f031648c3391b4de1aef73867e3e0f2612b3f87525e9e125123c8c341d4b755f9880b130698924fd9a2afae6dc257141ea3eb4b822f163614a0f199548dabb78a1740ec2372d831249325cfd7ab4289d278724c88e1e3acafec89884513b6ae524aeaeea0265e0ce17322491249355eba5cc610664442231b9464d33ea9a269442229148a4b4900189c4baad6fbf8b3d22d9b5938bfbbfa698b32312bbf38d1c6fddcc1c6b44f29dc69eb55bcaaa970c46249575adddc808f73c1791d44977c43fba7790110d3214919cdbaa3e45116b639f0f2db0f88844400c2f11096a46d59a9bdc30eb87a4b13f20758848cca2d4c7ced70de1fd19641c2241460979d1b796b4980c91fc22facc76f363aa6cae1009af2983c5c8fc241a2112bcfd457e7aa5eeb866f161e3069194feaf2bc7286b59418620922e43fe76f4fbc2d74024f66c12b929850191d4374249b1ddb4c1ae48e48c20123923b8fd4352b81513d9799f5310fd9058fae64c8a8f506d497d484ae253e8cf4c69367632f890ecd1aceb3d7decda7e0f8643861e923cb7df8285f2542ac6a3040c810f1b5ffc03743004b2d0c15de0301979483669aae7c3b7621e914824823c7070c15f788e04a408f0684024d2052a810c3c2466cd2932edad9bb22c023bbe871711f8d0028b0f2c1490228064907187e4fa77ebdcf39ea4694d1764d82179bff336af4ce73ca3312dc8a843e227b3f27e95d3ef7ee1820c3a24c5e4f7415fcddb624c1664cc213176ff2b898ed5fe17392499b4f95471330670180017fecb5aa954ea86016c21194c56308da9db42e55a70e35b43c6187e1747223abee861d830802c245594ad9cf1792c24970c2663da89e7ebfc0a49514673bf7b66be85ac90709a7f76b2765afea942825bc88fc98390ddd95321e134d8989c174d21b14bb3697a897ebda414924d88aad51f1b39134621b1e3668f8aa9ce190ac9b9726955b8acdefe13124c8cba53db4d7dba1312e54c7c3d868a418fd784c44e7284d2cb1bf59b094952d4fcd5aca84a295b42528c8b8f97f22b21795b523ca82b19932167470f1d2618401212538db2f1ca9befb208098929ff097d1d439bab7b84e4719bb93fb9243c3542b269cd9d64ee8d29a61621795c4e567f5eb6ae9308097e9727674b865bbd0c21d18412b1fde661ac3a21a4d2ceef6d3b6cad20247d30193b664148130261004048562b21635e0f9d9bcd01fc20f1bd3d7dcee3bba8a70703f04172a6e6f75d3839ed2f9930801e24be7a4ada1ae36691a990b4828401f020d94bcda57f9eea5cff0e0e0d257536eb89c83330001d249a3a1d742fc8dbdbe8e02eca007250b08cafa61f2a963ae1201db467f9a47446e9fc0dd813793a98fadab80deadf34631a5ed942a8067b30153fdc69afdc300d0e739d3aba8232f9f10c8a1f453bc9780d23b4039041418bf9f8dd55d93b8018a433851f958581495beeea144f78d2ed0b0c66a56329753a6c5517186ede42866d533a2d48b8a8111fc4cac33a0016f8f1f7d28cd00bb999551c36585c853cfd5115e69a13fafea5c25c2a79a891a1a228a7d2a8fc3cbdb93a056b2a67130f0b993445828a31d7bcaf45c5684bd1767c85eb3fab8c14099b5bcba2b58c9e7fa370e6eb3eb62c840cd71045b2dcdf6ccef1b576ad1ddf830bb3bfd12b861aa148f4cd5dd61fee2c37098ac4acd92d2732e6ef793e91242ef365ce92a1349e48b6107ab3d426b1fd7d27923dcb59a9a034e61c744e24d7754a498c369942bd89c43442e8d724b65392a92692cedbdab4726bff66870a353291207ef76222a763ce87807b5c710ff8b0f1612b851a9840869b9da97cd4d225924525f978ed32b7fd5b02954f5e866bcb551c5522c92e3b3fe78c7ad4a004236e3bca6b335fca933865b95b57cab0efa2243ef1b349c5d8a1ae48a05b5314a1c3a75421519041e9cebeabe5a87a3ab88bb21cd4784452f08a5db1f98bb95c1d0c81bfd15ee450c0b182450d4724e7d49df2595cd228eec80162b7811a8d484e95b3b8a9f775ac54a00623f8fc0df1a431af6e42d2bef81080e860bb321ddc4529418d4530fab2d3c466cc40729cba1a8a48ce6059c572c9ead9bc711ccc800884f4e002039148952cd448445210e6a2b973ceb6dd35109130da32a551669e2975882fbbfa8aec3c327a84234716050c350c91b01ef3945026739a172db0f888448c50a310c9765254d8d0d469e552b0062112337d8c9752a325a11e3972ec1844a2478b16d325d1f84ac261763504919c59abbed762928f7319c142a146201284afd6767cbb54fabe2fd40044e2e7c80eba5f22734b5da8f1871a7e485041940aa77b653af49034335d408d3e24e83d5777fd9e0f49d22d3729d5b3d032f790d89db4f5938ea2126ae821b152bde6d17ba533e47948aaa4a62e7b41a8cfa80ea1061e12af46b35b903ad2e3e50e4917be2e34bed4886d764810b5b6fed5ef0e6ad421418356984b5d73214e74488e39c6bab4179499eb7348b40f179ede6c3f942a87a44d42838e5015ae1d8c43929ae94c5f55beaa3138a8018744379d6ff684eea04c266b50e30d0916cab4685c891ddf0dc99934c510bb516c64ad831a6d48ee1f351f3bb3fda5320a35d8909c74defdac9b2bbba5ac2131e5e866e8dc1999a61a9253e8dda421e1b3eb4a776dfe90d190a0ddbd95c1c488967e8604f9a1a2698b69ffe1364392ccad2ed3ea79c173ca9094fbaa3776eecaaa890c49619a6468a98fd39a1d4392d26159f53b848e2bc5909c254ce797ab4fe66718925774d0b1650386c41759ba2a9d1a13b3f9429236f1fa58ea72b4d2bc906073a5c305994b23b52e2408a1c39fac8f594468b890b827bb41a9cd1612fed2849a90d79e64d442828e77ea84fa11a15dc942b298284b11f116fe332c24655d9296332c4da5dc151264e54c3a088fc9574d2b24a5911e9f528b67279d2a2448739359d5727869900a095a66e6559be55e54a690b86fe15ca46e76084f0ac931549ba95259a14d1a85a475abd8b55da190f06e632a9bc7176db127247b5fc69648cdc154e38404f5e172b9fe0611a969c29ff1b64125128944eeece8a1a3a4a0061312845d99071935335bbb418d25248e90d164f8f12c4a4a484c6e4a4bbc4871bf38098917ef496b89aca6b320212993c71bfda6cbe4a747481acb7556ea9f726c23249ebde510a73bfbcf5b84649169f496a8933a1f2442721293a2e4f209cd5d1942e2ce8a68f470d9dd8d109254b5c9504275caa5ae2024c954ded6a3ee7e39034252de303556d23e8bcafd2029e6c609cfb9a563c93e48dc14b4a974f9dcf6f33d4850eee25ea65b6749850749e365e2ab37b383e4b31b8d166e1656733a48badc64d237c8ec24333948107e5ba542c53848dacdbef9983b374812b3b931e3558c5d521b24dfe5b9dc1c27d4a841b29be9756bbb29b59a060926f3930e9d9387fe76060963f964ae538baeeb9141b2988cf58d956947b431483061214b6c954a1e736ac020b13df6bb05539df5f11a2f48ca14d7d53c8852a73c355c905c42f594dcce76d1548d16249985a8d70b335722afc18204ef4cd3d06c3122e755248a7ccb4e9e1b4766a68a84cd74397f3afda8234c45929fce4cdd2963e6cb50911cff39db16a4b7c7d829923707f3504945a648debcfbbe501fd15f9622c984d86713234c05d14891b45935977149890f975124c64a51398f56b936a12892aac3e5cc6aa24cfee1e9c5c7eff082478e1be8a3d38b0f10bd5024ccad9db4d3cd3ebedd08ce093440911c224c788c53b31893280391c88d2012e922c78e057c81d03e913c1ac7dd93fdfcadec89c49c9a6396f84daa9972837144e0c34627923bc36bdc4e27f75424020d4e24fcabe998dd6c2229aa8fa6ebf7303e1f4d240759a9396a07e155aa0422112fd0c844e269dc4db5f1ab714b4c24696fc5b2fce125923283c668237ae4a6c512c99e9ed47aced34cb18240a312c99d5247a96ce788d8299194fc472695119a44c25d8ae99d4efffb4c92481c3791e67174ca958b9148501d841895dccf4aa501894c7d12aa4306dd69e52392e6cb55b3887c54113b22c136a71026ecff63ca368259f712e2c27821698506231236ffaa635c994eef952d2251b4ab82fd07d56a15452478f8a6ea6e109d3f9688e4709dc2e7d1d37262cd80062292fe672f99305d3266cc2192e2a8fb18757563474324c93f69afa7c16455104f2192fa53e46b6d7912dadf811f5a5cd1c30b1d17031a8448fa1bf9b392a53b5a1a445252b37ac94678b2d00b224925a59fe71d26c2cc4024e694c52fbccc7ac78a16587c44223d10ebe84206340051ce15ccdbac53271d59593dbfd79d1493a1f1874439cbc17cde2e080d85a46dd2e386173bca0f4939b6b69312fea24dbb39a0d187c45c5fa29fbd69ea530e0e68f0213986ce49eba51c99c68336a0b187a48c75bae59d620c4a9bf86f6c0a68e821495baac8a6161325d379481053b2b6fc653c2467fb9cdb43b977485032c7a4e99256161db443f2da58aa8889ea195d489a832c0d68d421d9ad3f9afe8709af28d221d95236a1694768cf1c92429aa65042be482412c9620b9012d0904352a611be66c2c13774b433c073d8884392858d7453aa3579ce095816d080c3779e52c5c60d7aa73222a0f186c4945ae3b59ed208d95d41c30d49b9849c7db598b73246a30d891973acd09484ee546d485a2954a0c186e4334b6acdc533783685a49999800b85028d35247dfc8e231777556e4c017c81861a927295fca714a39c3a73241c5c5c0281461a1244ad72ea8dbf8814e1c8a1aa041a68484c4294c937cbb80c3b248d07173c70e4283333335b3501ec80c61912bdbc746b18533a0a628a1912654327d53bddfda613920602f25e9432051a6548aace7e298c6fb6745f481a081992323e654c73356eae86a4ed13688c21793343c8de7443d2ee9040430cc95671b5b2c80fa22c672d0e34c2901c459978efedca5d1d0e34c090e8ada393afe9bcafbf90687a336ab820653fa5bd901864e62851955a1b68742131445ea47f0c4f59e242e2c7d12f9a5b3de818b485c4984264afff4c278c3da8242ee643d2501c0a85028130100c0a9aeb9604f3130000001016920763e17848d495791400045642324a2a261c1e1e141a8a04627120180a03c2a140180c0683c2604020180e86d2d21cd47e8711bfa02112f9f428e6cebbdba83bead515e25eec74668d33d28860f9b1e8258070f9198d5aae8f2f40e0e8acdd75b9f811a710c019e2985bbedeebd58043b035b8295183acc0874315f4e1ecc50fc7538003528fd297fc8c0e741bdc3480bae1487a863106ff9556bd3018e643c27dbf012a03870c1c5b7076820304870f7c80b011133216577a137ea717d0ed00821f8f1c35f53b53d2ba2f8fedde3ea3f317d7abadf461e8247511b6018c0ea620dc185c3038bfc126f4061669f53851576c75c498fbc78952622a982a1f4db891b8323b6d14db17dd0f4f7d130723351e453f50a31b0b608261507211fbe03e8f4faf99c5887d8b465a3fcb4d6fe00ae5393883fe26d21ea0cd7670962db41f5e2ea5d40a3533b1e3e62e42d6e9d2a51c9fbca65f0185baf2e87b178f047483d792dc9add7dce28025811fb52abb1a707cebbfb4884ce139d27608e6e56e245b4e5e6471bab6a486f4152da2f112132ed288ae8ca487863fbd6776a4ad2a9243edaaf0ce66fefb5b864e30078a93aee3146c84a00f0d284a93bd61d89b432f2da0989de26d90bcdf68881aa105f0ea306c9de565c4491cbeb9245844622a501c72ceec0616b38067e210f93319797137189dead2dfe882f61c958f37faaf5080d10a361c066a56e259d5b55273e3ab849102aee39c4f79d45ce9e54e7cd27943632472c303fb45406a70d26147a67b079b4c67892f91afd50fc0e8a7fade3a424818c07f086fe560fd2128c010b5c0e796a4248dc49fab0fdb7c65a306512b140926b99d2b4d421c920323996b029d40987c5c99724b149759c53b97af4278c788e27447592e8681302fc60ffcb042c2144a9edb21ed5a17cb2cc4ff9d522472c79c8aef196abcb22c251b91e34659a5d40b15e660ba2b8e07980ee51560ca5ef81b98e814757ce38c66b50a0e289205b8f59e8f31bd15eaf06200b0f78c11ee3080fb6f74c8922f25018f9fbc065919e4b658a8e25e9a6ed044c0be6b8390e1005d29d41283d9820ba9623669ff9c63a5f1b32b2aa4d39ec24160ad3ea9096204f9ab7e0307d6958277bb3203c736cf18eb9490046afcd8191a1918291c22a0aa3fd9786b52ded6304fb547b4410ab7d0c8b074aa84791d007f15b7cb966fb35057e8a570cc02340a77b913725631b359e5e4270b72a2c5f6ebbc9daf2b32047cd61e915fa4ddb12782e3f8edac192a8196b5574d8351b95cb1856ebc6c48bc0996c86ce680cdf33f436f0c69fd1e0071a25b953d4b7b4632094785bdd5627e496a5ea47561c418ec7e0b136c6921d7e1aaed30547dee2e764369801df9de57dc7abf88376cb15fe53b4636e5fea08d300a58dd54cc514ba4e123e1004dcd3dca5259fe6fa4e630b64b26d923581044497136a4f7b153054f7e5c8107bd2b1c9133af84505b4554cefa5e2935f2503325d41a8bc03eb2821c19279435ec789d797dea125b521cd6ed9ef1ce7a7a346f9da0b162dafe39bbc366b7b8d55a46db3573288e47f6df3ba4e5e31333498492f025ea9bc4244af37d4be7d7ba35e6f5ee8bd9a7b35f06ae4d5ecd5d0ab815713af30fcf5442b37d724f6dae885969745bb5d0437b6e0c4bf6469947fa0a5c7f3d07fe446486a7e24934c5f37060662f9ffccab3f3aa27ba4043bc96da8bb8c4df25fd6abb017895fc5c0d27b25f16ae995e6d5d4abb15703af665e0d5e79bcaf279ae3ae25b9d7b617dae375241de972a1abd72202b06bee5ea6f5d54724a743ce33c27c30e2784df7bae775e5b5bbeb7ed7a6ae4d5d1bbb36bc36786df7dae6f54d5fd723813e1b929769af035eebbc4ec8eb384ba3f2181836e0f94ee5126df9c36591af40578dc46f4418c2805c9a9ccad20ed388da89dfb1c2d80fa22bf394ddb463a9ee6b85ae1aca6a419a1bef25250515dad078747f013d52888b20897a4151744bdba41f5341e0a875f690657e2410f9179426b8cd4b4366f72dfad28f3262617819e583987436c0933672a9dc64f3b10b54efada4c08a48c6a6887521c78074e5ccfe1895d15ff3d239782d7f85d009a57c8f1025230896dbbe2aab14634e6d5b03cd7d835053f73ecd2e04562170847c080c017970330f3a431f141751f7187764b5b2dd0a3e0c18424b2f17d9d9034d531413953c2f8f2965908c8d3b8c5f38eff21fad1b2bca6a8f31874567d77b901f3adedf2739a7f9e48745adc7d34e7c3cee08f1dffbd65d5577d37dc3011cb62fd0d80b87e70b4cc7b3c012ab7680b4d96eb08e3a359ee99b0216d8286be809449e55b66f0042824961a70a331c757df6b82d6852141af579f997e7e929a6a2d96bca5cd5bdaa4db57127d87d39c596cbe223352e7f67af42fb8025fd03f5bc3528e9ca0f5388f5fa83426e469ef0d5616a7db5d8a123c9b465ce44644473a1fc2712ee990be40cdaf4f35b46a5d9872125d601caa1af885f73ff8c654ffed36fae06142fc08eaa687afd88344248409d872310061a45c1a6be35213ed5e9d30ce677d42e66f24f2f62ec4b0cf71176d6c75473b81b045b1d2fe87adedc474fc31d963487bac2b91515c2eec39e1abe697c7596a1b86a8d8a30d26725671018b0d74582b11c1194828329b58d98186815693112a5aa26cc4481991aa2685933f3912e2bd11858eeb30aa1cdbfb5eccc10163d37e94295e04d97c9e68e2a295838946f8da3dc0524343890e17b734cec52b70c6b9b6f31f492c65c1d3989831bf42e2f81745bb314ce56fc557a0c75484d3a379ddc303e8656e4340909f4561c7dd5185a14dcb82daddfc8f56975b80e2d1fbaa1545e7e14d1ea59ef98efca731d15662ecbeebf401e03b06f0b7151a99f943eedb7c41a1273412797451317e32da398961b1d313e40264ead1df5c8f2e024a9a936526252a40e98906a8d41aa587dd4741e251751277a03b39849e18c9431c2e4205b86593a3bfbb122c8559de532a23811d30bb1ebf30c321fab5e1e6c3eedb4d0a92e5410691193e12db0c0b62f558c780f1e731e703094fc9fb7cef03a3ada5bbeed29c9f6440d0b15c31363063b1e3234a3daf839802fda6589901ad58a9d218980462c9fe95e3fe054ea9913eb2dc29032a693a094fa60a721ba03f1e2739806144e9048e6f08d90f8450d97479ddab9fb22df6e0514d77742621853994328634c35c4fda6843bfcba4fe751680ea214b38582e49254657c194f58a89c7e742d82f4ad643398773d0ff275a7c9edc9278bc2272a5392d59c74882549895bdf5936eb98eaa511596d12065fa876c6b122f923a6e29cbb1257ccce7ee51344225d6a8ac5d6d60cabc3a3dc891fe440c3ef739a59d48f2222d75464deec75a7b9915d6163a01b8f79027d4a538a953a4b103116f1744e7007431ccc6a013d237f04222de23952799f40fdc12e3e4a8021f032510114359e346f835e157f028fdf91eeca388de4558c1799ad8e682fb67bdbda08cca953a224bbbd9c8d5db7380a006855dd291b7561dc3819d5898e467d50f3955dc6d9f482363d48cab463d0a5a1a0fc33159a9e92d71b59fc784db73eec55270b79e80719384650b4e46fcbfd5cb0e5e763d107ef5dea5175d4d82a79430dd407a811314118962ff8c9d2ba88f1a140b5d359bb828a1411b4bdfeafd67828fdb37dc5e338cdb37513684e45629f5c975bef42b2ce62a26f572018b0205cbb8019fd7aa7773cf28369fe9f3fbe2d5e71eaeeee13e07ae1ecc72ad98f4b87c20af19cc5e1a3b07411eb6a88fc9e3103fde5091f2b1a4d7d8c9cbba5481a4cc6cc13626f57fdbb569d0b3b6ca620a5ed663dd43b081fb6cad749bf12df880a03ba0e78f4b374e20370f5dbd4f900d62f650624fa0f22fbb51ee32d3d68c2503b7d1b9d66cbfd5e7f7309b4addd4ecd9beb6b7bc0299e3ff3d6d5d269744c7e150c0f9d4f859a405e304d45aab362a191e737fd7f45f8d763555270ef466681999df24b910a503ba41f47f5c68005460f338cc11c31437ea1ffa204825c293c0376e2690711619e62acfe9a9aaea3cf45738f76e5f49546f2f12555fb649c99b24a148c969e9f22d1dd8c3e695adc087de1fd1bfbf67fbab5728a49e94414fb9a8d2c685cb1ef0ba2092fd7c7d422d88b4ca6d4057094a4d05670623843f769b2234168ff9eef4a50fa907932c0e4c0d27761ee398094251b77c18d2ea3c53dbf8e18711d1ca034c23282ebfd199c7c2e2b6e891db9d20c15d562b606093e8a4833589b37ce5fd6a69788682de3b6527a113ae44cab750fe9ad3748df6c277bc0042b8013096b877f17d30748a99b205e40b2b889ebe81bb155aa11805c550f2d26e24ad16b31e587104ac5a83b9c1d61420e3aeddb34bd520280fd00a1a013496f14d0dc5888eab677ba57cab7a853966bd835edd965b9220aa23ff0452b6435a2f397f98d313ef4de5dfb1925826d51c44f318e5fcab4fd04073ec991cae975d9ca021fa5c72b9977ead24f5065e46a0755124d401f859cfd1183920840319504076f252f098350ea5d77dd23f0f9fb895910fc8fc0d769c98120d520f618c048951e913d3eef388614643ffa247fcc2622f2c862755ffd22a01641aa0cda634fa755a6e142646fcef14d73d2b5e5db1b99660cf1040200ffe942f478235fc6908d6ea56300e136ac3989f9cf63c1114a383252097ffaaf5aadc6e5666db56c4cddd4a788f31f472c8f02957f64d638eaf2ac1e7a9342822738c22fb2ab14ba9cf82328b1fc12b8c1c6df798febbeff7dab6b2c6b9b04ce3698e458f7681bf0320dd77c76245ab1b6383d6993ca07701c515ca23601052961711f76d69a9c646b4260383750e4a485fe3f1082d6e1ca74e4284b36fa405e4248c289b37cdcf11b2aef5abc1143161687adc9582855e12341c0ad376fa55b4c6e36c2d61d8d71673779b411f37dd246a3cd7d430052ad5ec10297d330d90667df14ce7c3198b38fbc6251165b93b3406305e24637cab9b4572934e4023a06a22d07c661a2b53485375dc07485560ec062da0e7c35686ece1e0677d3d99094adc45cdb4e9e40a8b4cf4d235ec795cd811c7195c00e702edaf3a33153d8392a711b89900dddcf4eceeffa1fbe1b9141bab124107905904f1608730acc47a738297c165451aeef620a7162f478f8202fbad29ff814c5ce6fa674d739c96c2f109686194938d03b8129f7d5bba5a2524fae0b875695f3811405aaaa4c51245a5bbb193d6ed98e312784f606beb7cce6a4412ed862c19450a9ea4e4464b2cba95a3095ceaf1bd6ad7be3be5c641ce0068388cf20c7729102e148b73334cc3faf4e6cc40857360df46326f580c761bf694fb75396119fd5ddcf03dba0bf3df6807152ab7b8c9da92a6ed921137f59fca4fb2bcf8528a7d507db10b7329ebdb2ca2ba547a56460af4478d6151d6aa90e794fcc3a8280f03963e10799daf9f6e8320026be84e50f9eef877f7432a5b308164452b85da159241255983209601d36b8dc7b686b1f4a5432a9430d74d881a2f50b13cab720a471a98e4c6a9daeefbbf48c0e06446a2e2014aee9ea4a934daf17537295bf90095e33774968cb2e62106be15420323ef671d355f5447213e2c0e1b30e202f5be206befda42558dd6fc7ca8faaa6fdfb4416a5efcf58e5551e82ccb507d8cb2dd8174f14d0fe6b62add66b4cbd7863bb9ebbeb82e839028147c381ba0217c1be6a09ae0beb38ebf730b730299590c0d232a21abf315e5d069340a1e0bbeeefb1eee09d8454f6fa69319cd16562b33b23449b63449d8f55a853f27d8e851a8738e1aa8490dac607c60d33f6993401a0a93eeca3af6b21e9638c241769e919b554a99a2e29f75a94e7a8cb7bfee8663a477fa9d21a0df0ce3662ec1b8967353c685315ea5f599258f3c648ceb8301528e2c0528196899ca2a4a3a8d9b6b3867ee437f7c1ed11ede35a1197bfa906a86a0ab89c555428664f8710d3bda35adaea98f70c36be3f297ad09a5ad31986bd89ba6c5baaf8b5f4f34d017defa327c030bd5f6dc9423d0907ce049b9be9a5b90192d1d8c40026dc17795fa2b2389a530a49b9cfb66e44e173b7e4924a9d3fb3e869b354cb9248042abba8ddc4af9b7b2f7d65bfaf9ec645c4e33efc38edd1428afd5d8088e93bc72de8d205de454f5d8e3de77113ea7af725068e308bebf82136b193f1e46720407a2bf7f31e072ca52a72eb18a540115f1db0a0b024731f8d05d23c3b5898b24d5cd421a598828472d4f3b7cd61d20d0848d49cc2e215dc13214629e3f3a50a4c4a0241c1bfaacd3c0cea11d2d84d98c6ed16c9b2e9094050fbe1b854d9a461840f782ca505043f817d5c2250e703198212bff0d081cc4e81070f30e7765cd5ea04588ed02d2baaba9b4d335352ffb8f73e6a148216e4584ad03180bf96cde48168a34c9397445dd9566bad4d5e6ee5fbd5769ba420caddff14427fdf71dce8a46edbace525b6c91b6dc4db8ce9280d93747883acb365062f51e6ed87e0a9313d0dadc6bda4be55c753c700136dacf8d04b2a4551d968f3bdd4eca1c76a68a39d74fa06684bc6e98e48508b4f395f1f878c1e0583d8f2f1ce721bdf12ccf96466b0806989acca2a56080f01f63690267f07f56235a3d62a3303e6fd54d26fa6999f25c12a476130b8b3e1b29813e0d4d24f5c399718854c1aaceef4c307d0651a05a6c272d2ac9ad14b4b185f650df4ef114c0ce8604f910f7e527d9cb7e3f1845293e4342295f410c1e35e049b09690865d76bb40f6591a901c813037a55b45742f8d9c9a5f90666b909be043c0c91575460f8853e68b47e63f51cd9081ecda79c25d0b84d0ed1a9175431693626fe8d06fa7572b0ecc9edd47222b27e157e6309d1c7897928f69833cfe8b7c4821db5766cad5c188ceeb80e532f6d1939b450ceec08ec041280b861346966ff13ea568847e688fc455e2454a0f6e02eb23a6b95ac04636f80bc86e9de1996da700fd3808c59eb02236bbc34dc6e51a3727b45060f79530d3b76afd60c6bbc2526a8d8d2aa836c8225b407e40f95868b56a9fa13d38a1b341f22e58abb5511528059e9761c5c6ab9fe63cd3055bf981cd25bd6940e125a6efe9abe34a552d7e58f8f406b948f963b1cb612b8c21e9d8097063fd53ea36ac9777360b347050c24d75cdef5ae69c995737e4e0542f546d399c838f8cc287d3ee164a67cb717821696d2308a0ce62c320dc8828a0397f7d3754998c8410ea37b9a0a6f82952c029b767736307262dff20cc6909f09259451f355227bf96924518d3cdb4c89147363dfc7abc74c28f6a50c15090f26b27d4a6ee60b759910a8cc82c272aa3c959ccd1c95e9a543912e8aa4c4dd469139402ef31c583a3179baaeae44978dfbcfeca37ae3568260d6242ded79f00bf50e3fcb11381a7ea61fcd34cfc7148c31f1902d23bee6c8092a61c92415cde24d1a330f1c13b491500271a6aa8b79ec6b4e443f20203c4d175affecc1bf29c92ccc06aed21557a37bdf785c25d7fc03f6eee454987561f02d71bb98a7981f868136c73bca60b6376e8789b9c1065e481e3652b51805fd8b4dd6e2ecc99d50d508f9af32d4a55531f2706c8becf3567126fdb83db7fc28ee9cbdb790f0d87929ea689f1c7a32c622ba78aefeb446e005971640d248d4ea418c27c14c3ff78db05f5053e85658d2bd24c9e3ab5a1816c508244da3701bcc139fd4fb8be13b50964a4666cf5a22b98e05c2bcab00f7c68e9debdd638db466bf7a9349472a2e91ba2c047a3e84540968bce209afa11c79a69814bf47b798ec8c7d64a95909ebb2bc69e1763a3ba7effe74fdd9a572bdcd127b41eaa2a9bc4a7127c767ce699d30a62324d370a9c15a2d6e4e4c7c0533f020adb3aed659c77dd28c528fa51f810ea0af89ddbafd7c0d4a96d00f18a306e2e92161ccb9cc5de6707355f3b9796e8af53e8d85f9be891de7827c024424680cf234220d52604eab982396854e9eea57100ab470268ac803cdc96f9a74c293a85ce01fd7bad9959d4f2fad381d309b537dbaa062ee7a9aa2f49951cac2ee20d35927fbb190945ed484a8d4f361270f3233a60ea2c993ec150010d76da1c180af03dbc4ad7bf6de02c6008ad1181a634b647cf78dcae98c1a8a5933c9c983531cfab4f85965e57a7d89bb278b4db1c2edec071c595c6098d8515248ce34337f9be637cc5ce075ebf1828060b49821deef52c33500dfe88948f16b1525219093f42bad306f4c3d2659b47dffee94e9501dfd0a1d821f2b7f98870d98633b1039ffabbacbca0fe657c51b03e58806031c2c558898f392f92597fd611bbb931f3f563c0ba808d4770baf1c8a23cb23265a3c4bb25b46a703a78e8c8ec4e6ac87d0d7799924b7fb1bf95f69e4aea5425e4365ce0dc647bdfbc53161165e34edd28fd7f4e561381c5a7ac0c4963d9895c1dccab5467c5cc34c8b0ce5ef1f44bc869fbc83bc4207540548261ce628a11752850e9c81abbfb2c862ed90aa02c5be292dbd53d7c4941db7d419ae87006ddadbd809669531e2b7a236bd53836d3d6cd3652e97389651772e50dfbf7e02f14843c29c40be501aa6cb80abd3a5f68c24f86b01054a4ed83c03388ce66f13992016ad6e4110d2105e8285df323652da9132c549b4c395e686c4d4e7a187ee2bc48511b57b9736df805fce2e846fef32eb41145a650fa0641ff91eb6f6257d7b5fb670a419ad23026aa1e0913ed655d41069000902aeaf96eb1009b30ec595b58f3e0203ad0e3c4636a290743bc05697d185e4510f27f1cd840fca61bbb1d7c5d8e35e1ff1a2763eacef9af3684c1dc0f651ab0ca675a5ca730cd5b4ba8bcfdebe60e9fd5e3b2d3483e1748ae4619a327a99ee470ddf619f72ae0c78a63b2cdf70e2e85300940a22df9c68cd86b94c1c373cd96a09ace0529fb664db6a18429666c00cff7d93397a81be3a7f6b174de297291554296f54647b0a3f5eb3f73b2350f58f23560c3c5c1594f417a53b997d2c2147b05dd967424dbba657544ddea3cdc6a028d6becef57d557c4363b01566947f397669d52bebdba860dfa090c405d74793126fb70706eb483bf1a11aa34f9a7048da48ad4096098885b9ee175d6f7780322ac91cf0c811b3da6e05061499f6edf0c89cdb69dc0439c3ac46df6b1ba0193c8ed1d837891875b31f9743712bbba5ded65acef52f23ee86a4724046f18c0218053363eed13aa00064ac0ca362a07aecfa2587999e9a8dad738f56539c92ca14aa2b9bdbc97d2c8c1913052eaf2253655a51700c0e06342668e7786263c6387a4da85e0ffc96baddb4687e09eaa10baaece191a0ac95346b7bfbf52f1527eb8184c6c963534db115ee538ce2668501353b507a3a8368109e6171b4c2a0ff1597ef281bd623d9ac4e8cbbdd601d3ac6361e528932461d7143988fc2183ac30b00e9552594e0235879037312ff7658712c71eae96e8f63194e18040520285b5985dd13f0cfdf652123febe22745677b9c58a5e200704c7939bde60e37cb538f2a846c8e64dff2062cb8869ff244abd04218706b8215204cfa7d0d48647429417d5e76cd3ddb50022a4f2e85bb6f161e9c7096b2b43ddb98934013dc842c0d5c1d4cb94e8516af8d0457b2d04d8f525697227ea07ae9d8d8b75b1b9bb06fd06020d441808313069606460d6c0b081280c362f33287f2967dbc1e2778125071b3ad591f4f486650335de73e967b10d9b89cdd65165e2368edeaccf274e06412abe282965b91629ed95aa51535eb7a28b85f060433f7e86cc5fc1bd9ca504cb4bf78fb9cf1cd3da9d80fd032c05dc8d008ce4000ed13720ef54ed2f3eedbd70dc87c10001842dc1ca8424e00093296d182941ddd23a91cd1000f71280694122cffb36495085885c44ad446588fc44d44a6486c80e91160810296993a89b584f168de2d237a701910a3263a1a6a41b8c8280850b1a970798ad280337210760f42c033b42bee1f4dbda81af8e8e64322745eb6903376cee30fd3031d881883273f5303581d43c6b7f7a28e0c64e4f92b31373f985f8b26102c648bde3fe7c3c1d9ad45b556676026e69f18f8fb9002274eb1516b18b27b6e442feaa694b4f3ff52775d81c18713a59751a3728d3cf3aec15c80840e993b74f25521427b683f08cf1a2a8e30cedb38fa4edf14728c480baee8cad3b57bfc120999f0c4578a35e7e38a8f98466f98625d04958d6d3b68b07bd652ea75a84bfda7d0d31021ac196fe0af988c809d4e64d5b500a5a87282501965c9827b57b8005eb28b7c96a92783ea3d3971f581b37322d69910a8a582a8d50cad0b922ac4932e07ea82a16352bc7e4458168fdd17c22152dc5b07cf423c7b729bdb5bce47faeed0e5ec70833c7be19de42457db7a1358c8693be362a6d8e87016a4d828b4d455ec2cddccde2dc8739e892d3d5bdb16a420c6ac83b8274ab4f6499b31b4f89f18f891c3aed015c2786515af29a8d86fa5623686ebdd159ca92cf5c86f6734af45439a5857482b1227164c18f64f532b1e70065ec00f4cfb82d7469ff93b1862004a313e61285471347e1ec2b9cda3def4208cf905ac66b58fe9ac8c479f6965eb772ecbcda2f9e4089183fd74e11a04206e883b21017f972db7d82247a66b6d4ba5ba545f152d44fbcf98e95b999aad592438aa91213c617608aa20a157027c640316a5ebe7b4d3e5688a499fe3932232acd421f6a326d302944ff9ecdb924abe3ce910e5534e0817cb2ba78e5eacaace45e2e2a354db2c2afb9b39db80c4e64b7c3f512ec547a43bf602fa4aba25879b67d07a87e2a3d4d4f3b1ff75554c339ac8ad32ea5f75f0614f9008e99359315519631cf075435de564651fd2e9c31763280da110129257491cca783bb7e8515678099345d80d8d569985b60e31a790218909f180a38ca017042bf1912165b1f4c4f67847284c3077e936b50f51243eaf241ebb12bdfb93618137930be353e125295940c601948e3385d9ba116e7d4a62a8d8cc4622ca32308eb0b84cb58b95252915a8556bdc2dfbba81cde3c703ee77b4117049cba053bd66e63f3888437eefe471b12c304e6ad8c1bdc4d5f8b8d39ad998394123fbef4da3e36b42df736dcc6b69ae63f0bd5f2ba511bc96a6f8e5b58438a64bcddcd096be2db838dbb2164c229a885425134aa40379440dbe81f35834bac9409fa8af5a16058da6cb04db5128667563c65342e1891492e31ff1889dfe8880c5801f363312c85610e284077ed54cfb2c435aa4ae86ada7df5a5569ea274cb1dbd9727e3dfdf4e2a8a7e4da5b719790152611819530ab59342ac6855858e2851da50bf52f26ce5b5e69aea74bd33b3588325b3598af4e39ae4a5b3dd89b30c769ee9ebe904f6ec143bd8d203216772baf812cc5bad767ea23d45d7538ababa4b27d444aaa80bc706aa666674a875f8cfe476b33a43dcf11a20673ece18dd9ea8dc1031faa0ef7fc21c9209cd8793c88d43c042b01c64c8fd5e511aa5c3f1ef185522ce660240c8322f5d9a9444cf95c4ce329735a643f3c7710d80bff1f4af7fdfab47eac3d6e1d1dee0a142e90aac1613913c12487c6c531133bd881677349f6cc122b7284368b996b9aa728e966b99c3aadcb7314b1115612f8955b58bbf4768e90480059a2772323c1f4488bd1d048f3666208a28501945cd23976de567e1cea134abeb1aa92b46be0db20fa783530a1714a44fac8d5f2815d7c9504967ad9f4eb9bd78563cd1d212fc02d34136068d06503a213ba40d49b0366441fa2cd493057b5bb0470bf660c1f5b20b837c9f5c1199056e7ae11b31fc4db83022b747091a0252760e014057fd001c13d21a6ada3f71c5e3547af5008cdc1bafa8f5a6bbb0ca648b4cf1b1b8959e0675e2c5637ace9d454fcf0c92097221276449fac838992227724816a4838c2b539f8373fa599a5e324026c8899cc922e9202364925ce4942c930e65ac33e39c9db3cfc2d3ed8c7d269eb37366525f7926a30a5b66649193ddbdef473b2d8f7d0bef35bb37fec093eb073c85b59bb1de23704026c5b44fb4a935bd537a8a4ccab49bec85ee12aae2f14c3b8dedcc50675ef8eb06bdf0a68a640e2a4f24b6d4209af44327e838add12bbaa5df74868ed0da7aed7e6f675d5c3efc86e44cd0291305a29094420d149dea512f2aa5c2ac61529aece6024ebb44460c3650b4960efcffffffffffffff8fd2887c6ff27f2b999294d58fcab9ce94924c29a5c87abb0580000000000000d04d4484b42d80080001360c380c950c32be6008595bbed76e5cb23bbc604e3975b2d27e51818c2e183cd488385d97dd846c0cfc21830bc690d5415e4eab2c22af438b87990f195b308fd7285111249d3a6f0632b440b260f89cd7ed3d96b8fe32b0608a20d4e79a1094a896878c2b982cae44f2d8a7ab94ead01e3f72b8775ac1e82b562ea7f3a7f7fca71c2690510593ef7e8a4b530c32a860ba0ed3edd8bdea49fd2106be0978f48541c614ccb93f07b17de1397e1e8c1dfd053230c8908271cc5e45ef9e8c387a13f0d8b165901105539989a4bbfa74aa5aa0b02290f1045390175fe36d363b270a64384146134c5931a922a5a4f5ff924306130c7b5a3dabd7e5d07d410bc858820c2520c962928c65d58ae21626e7eac8f7152cdd29305ff48e1f6180816424c1a45dfcd7a1c5a3023870f0e88104e375c4cf6939c7945a8c307af87030100bca0b328e60102968c4ce53659915e204194630e4a477b671f2928629a308265192f26d574e87160c6410c1a493f289193ad2a1555e206308063bfb522ab25ae94a76ac005b204308b6984e6b59e2a2436b05cfe3eb9220230886f19222724ed64ad708328060ce733a692dc98e7621828c1f986329a5e517238aee8b0e2d1f984e9d854d51917f97d2a1c582771e3b7a603e4b27224fd27aa1ffc1a34d904390c103636524b57f7d0786f1bc9274d0ae25c602820c1d18cbf4689113ea3284ff62e4c0604a4cc850fa617a2f311fc8c081215b12722ed3e31ec8b88149743e7e07ad7568d9c09421f53fad5c1621bf1a982688f4707d8f9997f040060d5e1391b28dacdcf2608b5998f3c4a986b4a85a57ebd09285c15eb5b7526e9b66c8a3e8608b5898622fe5c9493c5a8a970e5818a496e7f190b34e8e1d71b0c52bcca9cb4336579f54278be10af349af9b70a163b32e1d5aa8bf08a315e6170f22e56bb7b2a7fc8f1d3a5690e3c79b8015e652a2848cbd0a5ad1bc1b6cb10ab3843b397d395bca35abc2f49f42e860fb2162e55361ea97d5920927edc46936d8021526b12c1122c9ddd6f33ab4c816a7307cca99543d97bdf42800821c15d8c214e6f9b99bb0676d396ca53059cfacfcbc84fd3e91c238497930d5151a8541eb25191f2c6ce6e4a23067d97e086abbd65c120aa365e59ebd3c16d6c4a030c77d6b6fa79f982d7ec258174d84890ae2ba2e9e30db998f70cb35298bd60953dbe92a31312d54789c309e6e7a8a272e5c3db50953f786d7898e2da63b4d984d727dc82e52ae2d970993654c1a1326d74aa329a6730953522b3167ba92e59825cca13b9fe167a9ffe54a18fc92d249e1514c871825cc62db9d4a2cce8a8b26613075b9b5151d7f594918f5e285a4a265899d239130ff8e8be452da729a14240c9e5c256ea5f434e9cf91b6c5230c27afa146c8890e25f5c3470f31f0b6708479dd46ff8292fa398503c7168d3079578e9714bef46584b153b810364a7aa560151c3836b0630337bc78001eb6588451f742122161357b2b3ab47280d1c347ca91e3c70e30767c9156ea070bfe40b085224ca63eaa8d2a199de3096c9108f3a7ce218e85b5dc35025b20c278fbb12e4eba849c637708938ab519b3f5d1a1f562545d952d0c6108ea42e70579218c372a7b50f1720e3b13c2a07a92693b1321727610467d2d1522ab4910262dc95cd28de7b69502611011abcd523e01c2e4e9efb3e45c349dff1fcc714d474e426ab609bd1f4c232aee7605f5c1a4cdce93aaa550d17e3e18430825673fc410fefd1e8ceb414c6d248788caf560d2ac0ad92d6769d9511e4c26e952505126553e840753b635b5aaced9b27eee60eae83fdb913c97d01d3b18742f8cac1362ef731d0ca77f3b588ef85eb8e9602eb92d2741db5850f61c0c7af44fa8d0feefbc1c4cdf61fe34662f54380ea6142684f59451eb398683c92ee5c5ce65926bc26f30a42417e487707254dc0de6f4a01e414fe47c396d308712af949c1637b46c30e5f26cabde497554d7602ef99175749ad09dae1a8c15f2ede4aa4d83c1554d3b744e75bf2a1a4cb6276b6694d2190ce1fdb3da6454d2999ac16c273bb1d3da2799963218b6548c45eeac5a9742067308f7d913299733cd3118f48e6a68eda9d1498ac11ca3a5564444f52485c1b8bf3a218c12ffca83c1ec2a33c9d7835f3087d1989c9277f6f5d00ba67496ddf537ec82b943a56471bf37fa73c110e43a8bae2569aab30593aa5c6acbb916b43e2d9856de4ac50b1fba542e0b66493bdaa2164f8fecb060d2dd117ee296a13dbb82f9fd7d4dc65356305c081682f68668b55505832c95b774b0d4eb6a51c1289fdb838e9e29987405f54e5a42f8a9a5601c0b97d2f327319e8d8221ec98eec50be2270a0583fc1693b150d7957b8261262895b3a2bb677382b15286d787c97d3921c0164de0524a31dc24048fc96d593917534772ca703131c1f01f7cd7ff4b8c8ca00e2d1f3d72fc781360ed12cce129b5f78770d7955282794f3c8c1aab2c1ecaf15e77852d9260128b9ec3cb2a2de5500b4e0e317ec78f1d39c21043013f586070e0781e3b74a0068330610b24184290b1133674eeac901c373420811b473055face33d17b4fe81a070e1c595b18c1702da23f8914fe923415c11892d36be4082148fe8960d2185ffb4a6dfaa11d8239899e2e2513e286da72f04839c010a3579023478f1f770ad84208a6097ae6c42e547c82d01641505494afc69b50dac514fc02d073a0ecc748c17b0ef42ae01d5b00c11cd73b6c5f6559b7fd03d3ad09f16aaa4cbb5b1f9843bc8a3d3b49ea57d9a207f59d505555a905c2163cf8e2c85d512a4359c4d86207a6ca695fce3fae0363e71429ac7e69470ae5c01c4452eba52a7b75757060fa306a49dedb83c8e506e6318fa192d67bb6326d60104b699dc74499a9af06c65bd950cbda163430879295d729b7591873d3237fb04a0b59982df6c8cff1c3788612fb418b589842a827d1f5d3cf237b8316b030ec8e97522d95da93a3052ee82f8a85408b5718ae4a7890366695aae40a73fc6c4a72d23ffa766e85f1830822bb649315a6dc921b2e21e4ce3731ab16bc09418b5598527ef1d423fa4244548571c6cbcc63775cdb3faba0452acc9f44326dfd11265a04155699502dff259fc230a34cc88f2915d6f6358521cfe75628fdabb3b018b42885e1b2f7fe45f49a14a60e134cdfa828b1bb1c85395c5acf339eeac2afa2308eeabc48b9d3f45c96408b5098cbf7b44539edd65c3ba005284c6ef26974c3b49b7f72f470b51f68f109834ae9f4c7ab3bdd73da400b4f1854884aaa840a75e408038c318e560db4e88471945fb2244509a1d3ad43eb6c075a70c2a0539e74d5c5408b4d18f34c6507a1224fae980e2df5e2878f57c18f1d1eb8e1c58d09dcf02203ff0cf027560a2d3461485a232f5aeb3cef291346ff4ff2d292e80afd63c2d8e13c4493f1b86df94b98228994093bb2342b6f09e35616190d0f6779f74a9823e57a4fc152840739250c22e798a0746cd2567c12068b2fe5a2b327fd159284c93e0459f3ac643ae648987278ccbfdc25344b0b0963abb88b2ad13ec224223c789824bd76af23cc9293e571d1e3e6d1d2084352298e899810234cb225b65ef8ff7cf5220cc1de3d6878fc34a529c2d41d41df52b8c48f13618edf9cf3e01e57928830756d45cecb8d649b8fd721463b40145a1c82331591218c75d9794cbd2d43d742984f4e7b24091129e8130e1ca702e3342d0861c86be1ed3e9aa987e8200c9eda4f5f5032e2cd220883d67de9b3e471443c03610ae6e9df2cdc9ef65a00c2dce15762ebe133299482167f300735c1652e47889cd65a410b3f18ab2a64cbf1d64e418b3e18c55475b6b353d2adbe2868c10743f6d5135aaf5647655aecc1a464d632540c1145490a62c17e410b3d98735bcd2f2c678d11c181038c1cffc9c73f036e7871430c15ece80ef87813348f9ec03f0370e030841679305dd073aa53be893e3fb5c083219a051341854887568e1e583734d0801c28e8d1c3012df8b103f5f8e23980821e3d74941677305630154b295d314f5b1a9000056e7801868f1e490b3b1844ba9eb7904f7950578726d2a20e66f393d153a254c4d3820e26152bcd89acad2d212924d0620ec651423fc511f25da43f5e0c315a0b391894dc91266cdb93fe6c1c8c93bdd2e73c3e726d32410b3898c22ff57a7c9af0277f83f9f3072574bc3531be26410b3798472895eab3662bbc7c072dda60c8273227a94fdc15291b8c19f7a373b0aebd11d7609611f94295a67e75eed460f408c254fa9a4e923b70d0220d46afed30f6975375d7dc062dd0603e35ad382af94688941b5e9ca9418b3398f3fce5152dc2a4e792a1410b33183e524fde3199ddb6dff0a2c6a0451978ebedb2f2fca2f722d6879324a61406c3a7df8df5090c061dad4252b944ac17a52f18bf928e233a0891523679c11c93c4988bf6d1c9a62e1882c867852f53b2b4c40563e918931afbf3c1b40593d0f13e4a4584da5c0be6f68fa3a498c58aa32c9853d788d2794f941661c158ede37eef5e2a055dc1146e3c7ddd862419b28279fe4bcee8fbab6090e62616744c8da07e2a18c6624c2edca510d69f8249e95dfbe0f7e1ad7c29182e8cd26b23cf46463e0a269dc3c272fe50302825ce2b45f34bf67982414fc57b0f2a77dccf0906ad274fe85375555a134c3a8708593a449e5039130ca67f365b72eebefc124cd992ec7f8d9460f2fb5825314549307ad67a4a592349bc2024984ebd8268af9d8c0b3a827945a276cf858c609025eada84451e9b8b607899d4bee7fe612b870826bda2fd272c4dfed2108cade9ae2573fd742e0453ce2794d899dc9377108cb91fbcf64a290f910682d13d4552232d5e9fff03b345d5105bb99377da078664559354277c52ea8121db0753ab5f7d7df2c020bf4c84a8d7eff9db41e168020d1d9893105162890fd1b9710e4c49d32575c8180786a0bef44bd27e55a51b98c5f75b6e926c609291c44a50313fde5f03f3e59cc2e2072d0d1a9892febdd42a1b4feecec258b23c4cc55b656150fa21251593a4fea8b1304dcaa3c4228ba70b515898267accadcf6c0f7b7985b9f5f23ce8595d798a2b8c1d5b56afdee1e45b5a81865e532774921586144ab4d8d8f6e55856618e89be267a91fbb4a20af3e851a1c3ab877b2815860b8b93f20915d209156635792ab77592489f5318b28bb689acd3144679718fb91475bfcd52148e19a4308a57aa5b4a99fda65198440c69a1f2c6960e89c23041b25ef6271426cbb1333ec2a030669c5afbd04974c99f30fa6ac88868f93e56d0139d309abc4bbb3e274343e484410961a3da356ff2869b306875abfcbd5013e690f357055342849cbf4c60c268d13326a99cf025b74b9874a6c911fd99b6bb59c29c3c884d0d9d44866e953068cf9de81991db4246893c5e658859cbe973d608332661d69850df7d41a77b86244c29528f4d8e15260445c210f4225f277123f503095309b9b1a011b7acb247983d674f743d0f92e208f3a6c4d4fa485e426f84419bd7d68ff43f1f19610a2a75e53849c482482dc21c16f4eaaa9a84982bc2b45d1e5f553ba2a60dc68c44983be864e796e5647f1061ccf1ae60123dc40c4398a4e50e2b9ef6ed82448756a92f9a478f1d5785306fa530aa3bbb88b7b00eb531f80433086188a35ea4c596a7e8fa8c4118d5238f05bd386f396c8230c6cf5a85b6331046ddb018eaf3497f889e236df978318e0033006130d78bdc414e8964f78c3f184b4f765dd6f51f7df4d0f1638caebaa10109e01066f8a1cf4962e75e32ebeeae0f26ff537aa7b9a227e5834969f3ab1cc242f86e0f065552825f0c911ecca2cb528544d339964e1e8ca33e495dfef849780e1ef89a64415a4a4ab98b7807f36ee4135ac4b2f34d3b9cb25809e9fed5a190b6e4fd4513f25266c20c3a18c3b2c7cef924fe7c7330c90979b6b4b7f8596c861c8cfd97221d5ad21c8003c70d0d4860461ccce51546ce2819a2b28203b1cccd2a588879a787a524ad9c9b5e1f9b958f1f61a861c61bcc2985c91c15f2575ca243cbbfe8f1801ecc70c38c196d306a4989db7d2ada154d9bc106d35cccfb1625f2dfd83ab44831054090430033d6604e9e7447f850b711e42c84196a304ffe1cb23f3c92e7c98c3418ac74b22e11ef4907090e1c3870e0c0d13ec6e81968b825857dc81354b2a843abc78f349f7106f28e9bd05f1dcab34d3136c5c8007f608619cc55e925f731544a1a6ee086052430a30c85635730830c61cc188329d9c87e49d2728820d7a16580196230e4122a7867ee684e4a0312b8810307f9c18c3018dc3e3eafce25ffffc1601a197eeab79d2f18d5cab3d28cbf5759bc604caf53714dec9c2565170c22db079d2a642569c805b39fc6dd64b336ff666cc1f849747a74cf2942cecdd08249665859fe7e551b9314db8c2c98a45e557f927ce1546233b0608a10473cd89c55a490fb30e30ae6d0bb6a9f99f45d991d6658c114a6b3e73af9ecab38d36146154c492fe5d8ab6fa9a602e00c33a8608c518bfea5840ef2f41b5ef461c6149890b44e4c8f4f1b6648c19cfb2147496f414d4e6b46144c41656fa9b0154a07bd176640c1f81d7412499ea494ffc486194f30c49617d5dafbffa934cc7082714d09cf15d76b82a9b2e699fab0a4df2530cc6082f14e8c89bea518c2f59fb1044338d13a22ac2d82194a30e49cc6248884a01742c418a3c3c821c60e6e818f1fe30238706ca9624612cc1d63fb268b30155aa4438bc78b5139988104534ff28bb0358f1ea52aa0a5987104d35bffa7ab4776f1920e2d6bc40c23983f6ba86d9365b6629addd040038c0f338a505cbad89e2b29443045ba2472382192a537cd1882d92c9fdbe4dc0d2f1e8f304308269b3def5c5d62b456403023082691a2f6243523449c3e030826554b428a89084984cdc630e307e69e113ad9dd7c6c4fe1c0e163475b400c31ca15337c60f252b1fc3ea558e961a1193d307bae9945ce7b29f97c43030df8b1438c1d1cf02f7e070372e4c08103078e1cc452318307c61256722a5f2f57590670e0d0e2cac38c1d98b30891f4f3bb622dae03935cbe4b672aa8bab6941939305b75bf07ada2c2b636030706a132421223bfb43a35e306667349d739445fec6e0d98610393ce7b9769840ffd7a0d8cdafa63914eab9bf6cca081b176db364e2779aee82ccc4946ceed8dff08a693210be3c559172177413bc5636194f38e2b278260613cd7ce31d2e39da6fc0a94aef8fb9083ee09325c61ec7cf23a3ce4e6faf60919ad30658f9c96142537bcc0218315a6999073e4ea910e2dac406291b10a83acb7549adadb2e4a2f9cc7175ff4c0814355610e42c7f149e6e9d022960ae3d9e892a3639f305161167db7186b61daa7c5ba828c53a83e76aa4445047bf4d091e30bdf61860719a630480ecbb94eaf2c4e2cc8288539629c6e8f16d7a1556490c22017a9b3e4725bb3d50b1d3f7674408c2fbe48c00d2f6edcf0e286173732a0821d482a4761b4b2a0c4e7876c42045114a694dd43540f391446c99e647fdc49ffa2224106284c21e662c4c5f809835ac8ba30df19d5db138631b7ce412e44cd8e77c214ea1be1229987105e4e98f3660469bb262f4b043761883ef239a424e652fe6568c2142ff2b9cda92492ac3361082a4ac7896b1f3552ca06199830697eafa59438bb9294a606199730a91472393fec32ce34c8b044f1e1b533934483828c4a18238454562a4d2a7d6f0b3228619654df9fb3f6c4c93f13644cc2f8a3a299ac1b99f2519c820c491874685132e6237f098b181d0973ce9582ba7e114ae78a9546410624cc397b4fd01e27be82e8112699a49fadf493490871842927c7aa30c16b84c14d9db814132e7d106530c294535055f14347c78f1d86b508638cfe6aa8091b1f725610321461ce0f95cc278be534aa1f850f321261fe380badf7269a21727490810843184f1b2925ed8dd172d4231e3d78941b641cc268b5a293fe9b0b13711c38725496187a906108d38eb85162e96154f40b6152295b596f48ca1631214cb9ae944ad4532bba0ec2587b29b2abc3e5944710864b89912bab2d4c326504c2f4a22599c908353f512b62c8008429b279e9cf130bad5518eda3037f307fbcc4f8bbec74effd603aad350b31e42751ea53c1a9c0fa60ca29fbba878f947416e6833182f7972a9d2e9550edc16821bbca7e9c6fab8f1e4c25f78456ee4aa3cc9307e3bac82d51425b3e7bf160f4bcdd799797aa454887965a828c3b140e956107930862593b5adbd529ad83412e3dbca9cedc7a4a87961264d0c174f165a636deee1fcfc1b4e1bed6d1dcc2a5580e46577d8bd955da3ed70641461ccce715a247e4a685d02e1c4c55791b3176bf2ac7379852f2527d13dbb4c99d4508196e309d5e9494c3855cefa71b1a90c08dbd3bc8688329447789134a847ab4d860086e369643aabf20630d06d3ab164fc505196a3099c8113b2ed7686d4f122bc84883410909e95208299ee98c06438af4a9e4d9a4d39f9d808c339894ead8ef3956e7b0350f1e0fd811460f0dfc1829d8c163033a7eece8000e1c26e0b1e3c7183d7ce0c0e1e3cd60d2a3eb391e2b88e43c92510693a84fead6b1db316e9c1ce3c60d2f6edcb00006bcb8e10fc081c30212b8e1c50d1c3878f4d8f1c3021240934106938f29edb916513c3e7c7c8e301a0c6519633045fefc7471afa4fd9f500632c460b4cb712409157eb5727561307b2ed91a592343c74d9001066307ffb4506da3723e468f93429bb04cc1e05993432f220593fe892ef1588a8249f642d5758a9ccf74a5810d2818a2de256dc154a594631b4f3005a57b2c7b6ddca897c70e1b4e3098fc29ab0fbb8d26186dfc4ae297a7581d31c138c1db5358a99760fcce5e5bb335a244af049338bddfd5585127e6eff0a101fc612309a638a722e85cd9ab944782c1f34f5a374f5727130a8020070f1b4730a7cab6b09775c17392114cef1527b6adbc2b45304a94c97fcadf3ab8fb3688602abdb1e2794255bd3504f3e587cf0895297a9484404e9e93b9a52de8e1e38718ef6a413067567ff6a9708fbdb16047e206d80082c14c9427b359316dfd07e6d841c7eb493aa77d30bdb0e103b2840bbabeee8139b57c643fa52b6b877860ccddfdb1dc9dd424ed173d72bc0f2d0bd8d84127529410baad723d2e810d1d184bdf4bcafde1937e6e47056ce4408b91f76534ecb64f3858dbf7c4247bfa8de8d0b27103636c97c43711d361c30668966f7687168997ad816b7fb927a6c2290bd9a081297c7cb88aafcec254c12dd8cee994e654b230aca82c51ecd4669f3c16a60d153a9fcaa8e4e8ead022b030eb6eea878c32a52ee815a6d0f31c952fe474dc1506e595e553fcd22263b6c254617977d262d7299d1536a0062b4cebc95a553e45e598afc2a44ace6304f56872d6508539f7be5498e3cef87d4950117c820a7399d5968aa4e4f97fa7305c482747e5689bc254a1bc8436292985294d8638b7ecadaa954981d611a7fb4dfd3e7ae4781e3bbee0b1036b8cc260f27241e59cebe324573fc600831950431426a554cbdbda6d49d2c9e1a3478e15b48fac5098449b2559417e50183b55de587f3491c4fd845172f04962d259a41250c31326d931a25cbb69c4c0b8ea8441cc8b2c112972501d7528278c3f27a95626a55d52f5f8811da8b1098330f93a3bbaefc953ec050a50ffc8808f0514d384495f859c773f972ad5eff0f183003532611cd7ea1ca93e4eaec7043530611222561a3d218a0ee9f4eef145a97109e3bce87a90fb31a1a52d4b1872cab57e1debd2fe54e2501734e4828a52c2202254949b8ee63ba3496422ea537c477bbcf0a22c06c00a3524916d5aebe54b8e732a913067e6ff08d9134aa4051f011d62b403bc68818f1e60fc04b4f8a106240cca6544b8bc9fee0f070e1c38321f61489dd536d4e8c7d1d611a58fa79c92295d75a0f6a21185a30623cc1e3d7cc5c9c832a754eab250631124255f215b96861a8a30d525b3b150154a525e65093512619c9c3b251135ddc583a48a500311467def2c2acd64fbaa09077e8cd1a3052d0122d43884e15d2df4ed1a400d4318a28ef2583a7cc84a6f214c979753d985d850ca25844195d0124b2db509f90dc27026d2fda739a5c65382307e8e1df2fde7304ae8cf63053d72f4f841017cefc103b74620cce31b26522701c22cf6f15a2caf1c618c9103053d7a7ca176811b5e707d515450e30f66dd3adb4a7e5af22460ecd021068effe2f40f151004410d3fe81d3f89f5b20f47b90f1f175d7664e4033162ce45313ffdbfa0c61e4c2f1f2f4d5d7655317f50430f4813f51a9d9da72b561298c016dba0461e8c966a67cd2f88ded58d900703ac6ae0c1ec17c9b2ea5ef6e756f650e30ee6de8a92fa849957bedb50c30ea68ed6692d8cb6e4bdcdb8461d4cf2fd3cf578e5a778c3c8f1a3ad6bd0c158135b4e4c3a8d144e1d5a24a83107c3aae5a0b4940e22a6c5e460f612e27954362f754b183b5020461c0c3bb6292a4cb669da3ab448a971a0061c8ce731474f68fe88f9cb1b0c226f259554861ca99230d47083210825c67334f39c7bc951040b35da60f6dc972da4654922a86f6840023770e0b8420d3698fe947fb8e5939aa51346bf036e7871e35db0c3046d811a6b307b2959331d961a6a2852bc53322aee23a635d260ecfc9a10b4b607a88186f5b4ee44cf609693263e27a3066a98c13462165e91b70bd42883b1b3c8f458a16f3984a506194c39e364e9143d8c4e2a5553630c069d464e87b42a492aa544a08618cc22b2562276c4132daa11863c755885cfb2256ea006180c723e399af652ef59d7a1c563051d8618955f30bae7647617494504690d2f184ee879b40ff69d1ee9f183c70e52d70573c9897cd6cb95c4a23a34470d2e98f284247b47f6c47d7e0c3072fc185fb40ad05250630b85cf95bce59a5c122b6b68c1f8396f94d0398f1dedc38b478d2c982e549ed9f54bea3f8885add37cae31bffdd2bfd8a1c30b1c382c20811bc400a0831a5738a5895421924e1303a386154cf9e2b7da8b5fd4a88227bf4274f79c1692a840907fe1fe6905a1c6140cb2f44d92a87f22844f0a06553a9f08a92395582b0ac6ff9ccfc9be46574728a8018582bbc5d6a83c411b396216d2ac091daae104a3f5a589a182e59ca46f8231d26d8b2993ef9dfa13d4608241fefe856ab88b1a4feab2a3c6128c926e945e9db80e2d1fff83c72bc19052249720440c912c9b6a24c110fef6e4a988842bf3336bc289cd521d0c6a1cc12445251d2392fa358cd099b04bba222d656419d42842fa5c5e539474bd356b10c160523664a81c77d6f7342daf310443163b7d3a89137e419aa330a186104c1e26e88533fd7fff8aa046100c3a98ca76aeb14f5a0404237af6783a45ba3ca2c60f5049693e7aa41c39c478efc1c37e8c510107e0c0518a1a3e30e58aed263f6ef5942c871a3d3088912d572a44b20d3578e027f17493dfabb103735ced77a8990a57971a3a3087943f5458c9a73ad960e4c033d4c881d922fda928fd19e1e339c46852622440af060e0c179e3f9b87d00b356e600c0b4a4793f74f178cd72ed4b0812949df1d6d6abd638735307d8f4af9ae4e09c9162ed4a081b947c50e2a09a15e4e3486037488d10ed02146ebf8a27d3c18af230c830347de2c8c212c52775bd29fe6781e3dca6461f6bfdc2a7b3ba1b5d6520004390a402316e634e194a7240b18edc3470212e03c3e011cf8e28b0623035f7cd160dc6981062c9c9c73ae5675a0463edec7991368bc829c3346992e9bff920e2d41d07085e94fa7cb2e73db772740a31526954ed5a893af0e2d14fc8b81ac30975c18d7ac6c17dbe34a0110e470008d551872cbf9dd4fc8e621c80c345461521f4f24d1e5a9fb16038d54e4a73921c9ca5ec1da916305ed637d40031566af12a7d3acfb76ad5314219f09917c6cae44caf0950a020d53986754b9e874fb90d26c5da0510a1bd02085d1c3a5df13ad9fafd71f6f82bda18106fc0e1f1a50c002c0a0310a73c8dcd2a33c2d99ceac1c804301bfc3870670e05851983aa66d7fa5a450183365c2c5abda9af280c2f471d7bf72752539190e1c9f30296d4a694790ed1cce22030d4f18d47cc5778e36a94d3c061a9dc03d4dcb2ab897881c6d25f6f5d161bc8f30d48b0caca031b0464c063438613c51266e4d45a408343661f059f5a04bbec4451b8940431366fdca7dcf7163d2c45a9076fc48cb8471cf52d4fa6c75685565810626cc394bde3e1124df098b0b342e61ca2effeda87ba2758e1e89c7abe07d9c95818625cc2112c4e8add7d27d791f581a584163a06c05ed4302342a61ac5ac92a172f57e93f0337bcd021463b602d8c06e3980220c801011a943097cb9588a6ddb144476312e6cf27c9ecbd52aa3449c258d9fcadae258ae552240c97826765dfaf58c9878479cfe244ae6f8d6efd11260bb3fc68e97e270539c2a0c2c9bbde11a722db8d30a8a042f3fd466ec66684499a276d62b45e84e152d85af913492d164518ab5256a7f510c2f3980853a5892343a99e5069448439dae8ee0f3d514be50e619e496272a2ae6e9e9a28a061086388fbcb56915408d515c2e8b3d55797231f4a8430e90a7d4a6245cf6ab11c5961f4040c01340661fadcfe97453541d457f24f4ad8888f1e583f7818b3301a8c3b340291eee4654ad51bd00084d1f8c37d4a45be7ce7eb29411a7eb0e3c8a4cbec35097a0660e086d51a68f4c1543ae5d3c9084a528ef1d841830f26c9a52dafa6dede227c008d3d98e2c76c46ea91e2f1530ca0a107739e144c04a1b742d7d963051a7814f4170a1003078e1e87041a793049c8f1b3766944091d1d8a85a583061ecc3979b476ed088d3b9852d4515d794b72cbf3020d3b98279e14714d6fc9b9a4071a7530b9a5ad7f38f921beb581061d8c2a2a74a9eed892b3c6061a73305c5e5910e97477d2b71c4c171a274aac2ecfaf71308c0afe156a42e868270d389874aadd129d722a914f1a6f30dadedaa7ce6621f97783f944333d7679d2f15687968e80461b0c13b52eeeb7a738f5d16083492d788ed12372f2d3d660caa1dad4472d1a6a308e8ef20e1d9d278eca6909288d34182b9dcbe98d243ddd8206e3298b35e22e2bb682ce60ee971ce6eb7ed5e5651c689801d1b32e4abd153c8f2a1c380a216894c12cd1265c96d16c097fdff0e28617371270430312d8000e1c8767a0410693b8ec89f82989bff60109783076fc00038d3118f4497df3eca81da72406932475e26830f6769a9c7412f14e5ece60b42f29625732e24ece0ca690b29f71225e7dca3298538de796bc351a9223836947663cce047dcbec6e0c66f118ad58b1c6d08518cc7e215b5b1669921a06431a4b1f8408263018d62c249e2a1171dd3318baf8824956f4641db7a0dcf35e305f8a914709e5915474bbd005170c237a3b692451af276aa18b2d987e4f462959d182297479da6e87859c9845e8220b8b09392aa6a89073ce16bac082f1d4af0879c1fc2cd2573055f80ab25348ce1a522274618552b8d8ae4a51726433b79817a5e73d5cf9f758e8a20a260bd62772b96fb1bc2074410553f9dcde958974895ee2144ceb63d944d8fea04d772705a3c987dbeff93dd7d754605130889435bb6bcbda4edf0514cce27aeaf489a4271845899c62891482e53b39c1a89e7a4177a834e23522852e9a6048df1a263c8fd08f6d0a5d30c160113d89b68c4d7fd112cc23ef849a70093a59100a5d28c1f8923ccce8709554971908ba48823164084b62941a9b5ce1c08103075617ba4082d14aa43eaf12d94545f108c60c791d215e756104d3cb7b5aaee9b5ac1626745104a3e7526aff3c448fe5218239d889a4df7a1e82c9652497484229953a672ac8612ac8c15d08c1a85a21e1430a6f0bba0882c13da478c976ba501110cc622162440941a5f6f507864faddef20e2654ba74e103a38eceb994be3b11d6d403739bead57c51fe154d3c30e9f718bb8dd4c9837660d26ef7163e483b9f5f0786f9911797eae7c0d8ea4959367d5b36e3c094ad73775ec46f609e28b271fab781d1940a69e369c452e7d4c09825dc93c7bfceb4ba0b1a98f4c94792772be22fb3309d1615d5aaf2e91861599824e989d99525457f636196cbf20c4b49bd220b0b739c5dd3d77b798571267bcc15062194c9b8aea6c50bb6c21cefd2c87cfc5861fa709395f3534eddd92a4c32940911e53a92bda50a934ae192d8a79718499b032e5261f4d06149e9adfab81415c619cd0eff5449a5e49dc2ac7a1ed29ac8712757cc8529cc21f69d3aad3ad1e7be808b5298d2bec73f5f8c49f196643515df744b0110e4500117a330b748f068c96747b72c463117a2308499b9cbe9e4a1302479399e5bec10e3d4a030f929799f438ff64ee94f984fe6a9b394145722f48439554ab1546676c2103c54f0d4f1d3a4cb09a35790b359d242ca966dc210a48548d9b3ab54b65613c6fd8ac9219b7afd138d910963c88b31c9fd8409f38920b27b07a13ae87e09431ed7b81153f13a792c61ae9336412cecc70f57c2a85bee4946357e46a684792dcca8d4900f6fdf240c2e1639b6ed250953ca3946a56ce1f3ad2412e6544a7c72d45232250b09c38e8f78d1ad5dfbeb234caa2bfd848d312174ea087388ada43b5a8de494d208a3eea8e7aa161961bc58790f134afc9db508639f9d56c826449e9ae8e14211c6f288b5716e39592425c264712ed6b79c4e297b106174f708fba52fcb5cf210668d1c52ce25cf10a653b1df26e4b417c285306788701b7ad2955eb92084397afed5cf16e71f498330eea5d39bd7e9f17358102639efc17d74ee4f6803619212948a15f92cc4a880305b74499252292df7297f30484af36b9bd30fc60ee27127680fbd2df6c1182284042149e3734fb8e083714e9c1cbfb0ead0dc432f2adcdb2c25055ce8c170156fd4a98c502ae7c1d4c94b5faf96af248bc78efef13e481d800b3c9827f887ca8cd0154ca8035cdcc1d8d559bb2a516bcdb68351a469aa44d3c147c975684d7bd221f62d74307d4afa82789c6429b23918d7dfbb3c3f85f66cc9c1fc6a2e323c290e868b6b11c994ae3093c0c174226d85dcd8d688578716c7808b376c23513bf54d0660e0866d10b8704351f5730ca582d68b29b6e1d8503812176bc84f3b07997b22cf05065ca8c19c15c53b981ef352b948c371818633983decb53cffe80dadf2e0c20c65e0820ce68d98135cac432ef975687106b818031762281c18e0220c25792247b780551bb800037a629f4ab22faefb8239bf54e948d0293eacce85170c07175d30baa756a8ffd38ef51817cc17e14ff6bfc52e9d70b105a38794bb9e4ba405439e90440eb29e2c982de65dbcfe0b93ca830573fd88f80aa6129d645f1172288dad60ee54c2f3daaaa8986e158cafe29e477dbc934ea9600c8b1a663a2a533057fc701fa6c4cc868a140ce2d2d2ccfc1905a3e59517b97c49de07a16052e1f465316d7982719405afceb52ab19b130c9e53d5bfee4fcaba0986a433d573ae3b7ab4b86082b94ca99f90de5f8249bfb25ea7da9e7b4809c6fa3ced2784f810469d0473a4f86f3a799f4a949160fa2064e7a0bd83be6f4730ede6967ace13d721c58511cc77a72bc764cc45118c0b2218bc6b573dfcfd2c7b968b2198c35eec8a33cfe9b2bc75e04208e67893f3ce635ce93c291ab80882d92b299d929a4b3f1ed601174030598821a394ce5ebef20f0c93948af390fcedef5e0eb8f08129eb26754851990978ecb8e1c50df4a32770c38b1b3888e1808b1e1824c44f4fba2d073dd31909b8e08129e9285ff2ed4e52f8eac0c50e0c672a764e49fd3a30bddbfc05a5e7c090534e77eaf6ff727e70602ae927c54267d50a3937304ad239b6599738eddb06061541c83e59f9ffb9b9a88149ae7bbed694a0b4ca5cd0c0f4e3ff9f575eab559d8541e6df95672a0b8327557af26b5c8dcac5c2705176ee2e8385e9fe4f6419ed10f373b8020ab0f10a539dec59e7fac58edb1526b5ec7a5e210551e96d85d1934cd35d174195b4b0c29ce3e933da3c26a5e02a4c71db9252e496111d55984e2bb9ba488bf7a5c220397409e95a4187104385f1f25772d01f611faf531852a98d105b21bba6c914c69ba472309dbac472bc14c68a687be31f77ec6a5218fff4c66c844effd78dc21425988cdf505a73335118accd2c34dd0a8549750e1a4a85adcff10185d1447d1c4fd9d1fec4363e61b4937da7bb69199e270c3a7bfad4e2419212f94e9863a8ce06274ca7674936376468d7c6264c2145b2d86225b7c3c786264cfa797cf7bfbb53884fb09109574434ab1cb53ddbc08459edb292240faa335b1f3d6e780106f2800570e0402c281b9730493a9597b98f258c22ff734a1b63f2835a09b36f29214feee83115a2432b058f9e035afe60831226f1523282aa1c361e96828d4998d3e8f8a444885f22846c48c23cb9245d58b687534148b01109c3d59c641b0f3944cc06248cf6a5effa3b7ca6866c3cc2d49e2fd684485dc086234c4a9f14217374e5d0fe4d7029b0d10893d0aa8bfb693b87d57a78f5b0c10883b0c8de651fb63e692810a327c017051b8b30fa25df09494b844abb0d4518bb4cbf77457c885c6d24c2181e3cfc9dd4309f30220c9e93f0121a56af351ec214a49a16a5661f0c3084e14347a4aacfc536dd033210c618fe0bf00f630c7fe7828d4298a44dca4e77499d5cab43ab7afce0f1000da00f2303387078516c10c22ca3e457bed69860631086f8492b7f88916b5b2208c3df25f716217ecd4f360261d4ca6142994f1821260ad8008451764d48bea0cfc6471b7f3065fadb6e79aa84c1861f8e204f46e54c57d1c7f3e0b1237f60a30fe6af93277252a95492bc8e60830fe69195543a112dee771bf8d15f3000070e1effe3477f918a60630f0679baf37934c5d2c46ce8c158ea259aadfd44af78d9c8832917dc2359aa4a7a4ad99d69db2fdb47d9c08349e5d592488bef6034b5b5bc30aa938ed20ec64f39443a15456997570773ac65ff7277f3942345041b74305ccb5958fa768e31cdc1282a96eed6658ba54a7230ffe6e7cb895e39f58f83b9826ffae532eb4b3f1c4c724aa9da9b90635efe0de67c237de28f2e7929ef06e3bfc90fafdbb5c1f4a5b26548f2cf2284d8608a286a4ec70b21cee5acc19cbd7f42e85822df78d4609e09e7c9f39bf4de4e1a0c263b5cccea4457d3a2c1a0ecc7b44cd4c7dc3d83293c8fe4b524c7fa5c3398fdc476bcd1b50c267579bc275e8e98aa643068a5d293214b8aa59431984fc9180b693907991531983c5bc7cf8c4db41cc3600cadaee31b1ff252080643c82942bce4ef0ba6ce9e161f23e8657c5e30f99ec452ffaba6dd75c1704ac5da7d23a67cb86092cb3aa3feb52d98cdbf3c5592acee6b5a3057c8dea698d887ccb260ce93375b693145898d5e0585061b5830e828e163e59b5dd05ec114b4355594a79d39cb0a26212b4fd9b5dfa57c150cbe3d2a053d51ca83a8601a917d7af653018f54a894d025335924100743c15010c4403cbc6d13130000100c1a92c662c17848268ccb0f148003543e2a523e2a1e262018890503e140180a8501e180180c06034261602806c349a28751d20310f8b499e19b9d6bac49a2423657152f5569d5e3c380512ce05266d1bddd8312c362bec94777db72661ea541b2ac36b3d306f1af80a70ef03dba974a6be65752e034a571120bae82e15cbf6eb5e1d18b8b7d6154f19942a700305298cf2e3415a7267fb2961ce7e5e56f2d1d53f4edabb0d3e32e066c7a12232aab4beb137361bbf130dcf1390fe5319d83bdd9537b6025b91e92be782e07ec432bf114e140259fd046b6fab2a22f3a7e06f1d24f435a69f112f44f9e7f906520d8e9a5a2dc07813d937072cc0fd3199897d87328dfce1f6e1f6a861254fc2ed413e7c7a5e1c5229016c2f828b1a9a733b70de650a90d572f6c73a196b17fb05c8db0e6402a0bd6f05b872ca89b00dbddffd5b68524d40cb97695066edb939aa4f00a09e6f1c10a9c856e64312afc5e1adb219956da22a50ac3a67a477f99a43a9ef7178bd5d75a361e08a77379d190bd810b622ff7c671dc052494eeca73c9adc552ebbec6368d3918eca3e642911cc289dfd627fece88620e53aa2f87fad6bf6fc919121d98234ff8b1515ef24a33fae3c07196c2ff76baac70d578fe92040146a16e358123ef8df6c684c0b16e420346674d8dcb9ac45af489a6189a4832f5a8bc3bb76ce929b07e326f372bf3f21f455e5b6b0290e1eec65e6cc5fe8d645439538fa8c1465ad14a6bf2c1620a41ae8c6a00553950493d2386564935d5e9a8f2cd06d5b1965aa59a6729631f7807769268858738a4c4a6dda019a84a0ef5e459815517ed0d05ac5346a7cd294869dee33e01d11e8d836296db82495eddec12b10e683e3deb459a05b37ba42c79b44f1da27cbaeabbc54e4706099767991557a93dee0a07a70d058ff86ec48bb1acb54a39af575cfe598f2d6d61e9cdc773081d13b51c68340462a8c5bcfc611f15eb58c8c7ad925e962e938f58e5a376f9231bdf712310c02cb86e384438b5385bc9284f3d7db9d73445c3b40898941cf78d6ce1ffa37fb214c5de568c3dfca68a7db66adf664c7e84dbfeb905cd1ce96a5264ca05191612a54651311f061b891055b591c0fb2200155011b0ce43134029fb3f005028019445046f5705712b21be2accf6e52206e0ef8e38d9fc78937c467586bbe0497c57751f58f907c76d3a61d3349ba66dce9ccdd8df06e383cf6dc2c566c6aeab5ea9c7aca650ead4c37cb60b1a56dbd09bae6198241209692381d0b761954cd2fc2b6a478d8483db0edddddf3f0b8d5279a71f7b3a4c1a3f3a0d52d0029b5bff1a4a6fbbcbc2a5b3d15cf7261daa45536d6427c256a4b7b4f2eb147e3e71b59535edfae4bf4b5256c21d7a79e966e933983921f1be0ec1e111ea3cae8132e18f5b8181c0b5687972090bd9c5d9722346b2be794fc7c3879008da71cd91f6564c9258db5f40b58bc9d26bab985f46598d0cd6854fdb0ee72bb560595aedb7cf488a53d5bbef2a852d0673b671f076a6c4af980a645b4e8c863a0e96640a64c78e2232e174434370784c61e35f96174dcb54f7a823385adcc81a612f4636a9143b54cf050cbb38a062ba61c1648392636b97c5e2972ee0fe25c01c0f4e2c871d25bc73538d344e653042bf476ed8d971368b34e5739ad260e9988434bd9beec966fa7b9b0c62ec605e991c665ebc3901172e8e862f602ac89cad4f46647d5be043e51074a76d8b08fbb52c80d6af0c78cfa4e3b1a4db49896b0afb556af762711aceeb0a45e94a8bbce25085ca965cf59432af3a11bd7823d4965a751b7b915db0c63c52c9dd869b642d9f5051b4468b1ba404c19c80e2d28b0df68d3b440ed19d21d3239d64c90fa8832d2566e7d55f9715a9346e58b23f6727f5a6b5e0ff2e6789c3bb9a60037b1526127773261668a3aae671275fcb71fa47779967121f1a97b2cae77456676ebc1b4639a50153a22b0c8cd0b27072ea0a64a430036c5b451c4766100855e6269830bc00c351cc090e892760078d2905276bb91950c0065c91ce663266ddd5931585a07a827432558fb4464a4f3a4181466333a5b0b810d0993edf6e0a9a2a40020e3184b2cf5184f54e37f76ad388e3b72bbcd47461b339d8a51a92d097d5ae450fa4e32c8d36a703c96e61a3298c4c61047d23298b4e5b44b48fe64ad9b34c2023813d8e76f522c30c3752d77ddefc93a7ddc199c30fddc4a2a7bd1038e4e05ac166817c02aae06052aa4294396ab6965b5736b5590281e384a690516894f2028399cd7d0bbea4106db01981bd891e648a7a2aea9ad87e40d7bea36fdccf3217bc0e994ff290e0c0cb60c941029a8e06e238a0e9665d101bdadd546d0baa4a293528038af25c73199f45655bf1d8734a5e060cb55aac4f0d1b93ca924d2cd78230eefcf001b51970671fdc72535e08cfc99c72a66bd357a1745a4c7552e883c3c0abc67b343e457b3a22c29a61ee78c3a7efbf8fd3fdb37618088746246e03d1ec6607188179121cd9d28156b122a69c0952d52f7eda3c751c75b331d6f3b82953b7dfc41f57278c09e3bb74994b5cf36dd8189a35ca092b8893e22a3919e666ef430afe86b59c35b85e085fc03f85650062130cfd45e46cb653278e54f94e2ed4aa4f05897762437f48032d1f1266e014e5c341a77368f51855ad204e99c684388f7db0b9594800e090c6d145d8ef70a71c1bf8db19601e19819dd4b11f28fc2222da55eb04cd7489e900d649c0ed7e1178e75d505f6fe2f69afd762ae9577914d638f593f6cfac05dc0c71d36d4e0c156175f171a57c5272f426bdf93b6d5ae6b630bc42e51307f6ba7d72a704a90367b59fe738f3630d699e56d84d02da54b304f8498bb5334aae74845f6110cd0481c198e712298b37298657995ca0d4353af8f4926d114a3865065b4f7dc57afc1b4842bbab1c21dee2807495908c6fa3f6323fa7047fc0fcb0063399ea6d72277a0790dea6bd710c09e468ee9f3606679f76314f13bcd7ed0f2f714ece14dd65e85475e7a3d63b9d9af7194cf8f543d8300f202c8d68dd9aa20837c14b2bfb3c2904752d403a5387efe5252d7af1ccd30142e6008bf107ef3f65e697ee696e7c75f860cebff4b865a2ede1fd93a222f42c6720729d7ad59bdce495ee5d9ac7b0279b77aae28d70c11162ef21cbfba007a3679d772f8f8ba770fe3626ef665d2079aaf5f0f47cf3c2f25242bd09114008e5dfd3cbf3c52fb0091f1b315214a07b5479cec37b7a47802c5c01e668960647181d85101c078c47b833a25347e5e55895076977d9a081fb483a54a6f0c6051e965182688cdf9f89a98aa2bcf117908fef69de2acfa7bb66025efc100c2d46c03055452898b990ec20698ec4250e06126e23ddc76a7720822ac22ce6d1a6b915c2ebf4555fc69d4743dc39f9040664a7247476c848dc11dbec2386721713445d2b1f611a1dd78216409cc6e2d3da6df6a8aff2d1e4c3e59a1a2f7d143c2b35ebdfecaa10d690eebdf4c7c6a03218d74d0e53f6e227d72c070dd329421c9070f100083d2fed8df7a627a46a741a1b06d106be25b990de10ad8713de0c783a66b3fc6a20e90234318ae816cf0f02cccf8b5e6fde327459d8cabc8117f5c640ce521091494731dff2ae38892a89c34b9c21dbab4d999fb699b077f2562435f17d42a209a93cc046ede4c80cf046f6166945cab259d56a0a477a78a3e0b009fd23e219bb513fd462b9db0bcb2aae456ddc06dbd38ca260a65ac13b50fc71e6a08f16e6b81bcf426a3334d41c7f5524526bdb3c8430bd9c50ac2eebc7fc0b61aab2cc5e9c16ac485417bde40617814c905bc7bad1b9f93b30f705e8cdbcef093c25c19fad8d1885694a4df2615a5bd0544c481c611ca3d6dc08f0b1980fb1178412706435437b95314ff7ae0225e4f7f9a893d98c95460acfa0dedb129b3edcbdeb64e298d294fe9b9723734538d1a49a26543759017503da47c2f2e402c78d9ac5c721691f292ba2433df915acc2a5a652d435ff226f89ad82d4a878246f4ccc279c4be5ad07949204799bf328845ea2ad218cd4da172d75168e4ab2be4adf32f50a5fd6b15027a5db36b343c4db31edfcc996c89ea87c29689c58b48cf46b72312f416d11954b3626d98005af89cc2bc2ca34d7ec882407214ba12a8773f95dbc1c0ca646295a83f76c6c3019e03854150e5ba115607a56b05ea97711bdc3d805482284d5e74ff2abe116eab0e4a943b9351d39e41c3f29f6456b105507681b5d6a5f780919b977483365040d368855885f2ab46e5ac8f29e11e7f324bc82a1f64ed01ec4f7f3888c665b34eb275bf068f9d3fa06b1e77dfe69222412dd15bdeb2b08739aefb9d10e5017405806fe1a3a6484b20b6776ac3db078a2fb7fae0be339fa5c6cd936e2ec390fa42de5cc339d5450dec9f586acc1206e6ff97819e2a30ca29a71be3afc18e036b83975a802f0ef03a90db6fca0b9864a36251679b5eed17f5f37fe0c8726cf9df2313c6a54460a39cd6467e4fcfc0296a78c692a0c47b825165097ffbb2a53de36693a049e6adc7523eca6737748601e4c35cc8033909ee502852ed838886048b2664f4744bd9d5809a486ba4def9cb6be5021e71775e544a7e060e8dcab300ff427501a1e39f0cb0b823e73dcc07eb714820a686ec76795ad0dea05105c66fba636caccede7c3a0863dd0688dbcf92de66f7b2c33d6f23dea12042173887d0b43dd1676850d0100dedd02b3139eca4105b64c168f2005514910058144e34780d62701c2c925bc30cd85fda2e93fe37b797630b91fbafab1181630e70e45ce4b7efdfec087fe31e5134387e865fc4e47773629c01e90f477414134466d0a98ad5a0ed8fc41e8c81eb162e068c4165027681f08d9a9e0e78e3f25000b2b0cc546cecca2da9eb81bea0d4dcacc814f073d9e8efad6e26dbbd7343421caea293fb2aabb615866d670049283ded9b6397c5df0c339c84a72e2e29ffbf6148522f74febf91f269490a1ce31902810967cb0c6508b81b068ec4b280bdd009301807fbddd0f84ce622a80f094dbd5868dcd214d55414f685bb6976eca808cd6c42e16d80ec3916596da24c31a731cbdc80aa51caf6d1114aab44604657cfb633ee1ba910b4d314304acf879c3498924d1dc7c48bd986307866a9c9e3991014dd752883c08c932831ac49eef518c87998a08240c44a27c596959d9b090c6056d8744f4c605e49f31c38fda7a3c62411750d95fe157748ce7c868c66cb1025121e0645dba5cd1a8b6a08c5b8cb5e3939d0adc4f15022c4c318065463dabafb62b9a8f0f20a7cbd44c86c0180b187ab22f98a7d35b4521367ac03732761288bdc36912a2bfb8d0c44b9d99d93406276b558cdee0d69990c94f0cbd866d6dae13d2e6ece317134f00e66fd08d0dfca7cb3d1ddeba20b62627799ce4547e1c99a62c6f1ea680f34adcf213c55cb5d5aab3497d90ac89d7fe461ff59fd3aa7994240d9860e9b4dcf59a8fed33825f8e4556d95135b69f7c046030c5b4c342ac20a11a5da9d601a45f1db42e503327c86ff24d5b0f97ce45d6545892926ceec0f441b11711f2d098024833dab0f26c43fd535aa41310754df2b0137f55ace97e0a2b113b52ceb98a6faf6a6835c2e0091ee48b0aac5f046a188e9447e8bcbc476914c73a9712d4e41b155cf2e1d82315efd519328c0a5528671198d360fb249c04db4c0f328a583a0528394b019ab63a8c017ff0864cdd3cf53b3ab76d5443dd1750cf806d81f3706580d1f107b29e0f37347909aa5e26ae36c1707c8cc1167feaa1834c25ffae7ab1411a07607604c2b5d978cdf51d03056e41e0ac6e3700ebff0fa2fd008f6497133f3edd116539f66d296f6cd25aa5a284d1e3b643541bb18812092ce7cadbdaca3189ab97a601317a12b3a3f29e47f338c950accd3e1079629d5dfcc40b06c87d014747413c83f02a41c59bae590fa251982e974e0a11451b1219c7a3c605dc7287253e29ec039d5127be398228ebb4c2297907c0587f366a7d3406589ed8b3ecce89df6803155098f5bd3e40692a99f0a0c4ea0ebb12a66175b8b488f1436761ba9336a2b80974cc89af07fe9cb0b53c976c588c12b69ce5bfbabd172994d4086b48f80ad80523d82f70a73028ef8dd542164bcd118a2685d650585cc11a8e57cd63926981851f3b0d49559d7234d3e61ff8f3859979309be98a9cddb78d2b571c5c622b12b2b151c6bea4adf23a42ff101393101ac4052628486987c0ec47df25532e40e9f931965e3b135a1c3f543752febad0d5bf38e6eaf8bd584a9d5a348bac32b5d5939ea50a29a97ddb46a2b52bcec65423495533e15c0c75f35593aab8ab1259031bb07671c8ace71996be3668de2c1a15305bbce4db594fd410d27c9cb89ec304139d9d168e2ade362a8107e8f9f3cbb63decdf4a23f875d88e9625839de55465783bc6d9f7c721e4dc844b60e3a09723da6e3c0a76a2476027ccc3b3ef13b8fe6448e15141e3320369b440fa52e344844a03218067a5a0f435026870307a8458508fb9e15dda2bd52d9dd6dcd2312606bb544680847e57d571d8e71ffcec94ae7dd6097a2cb3716a4fd90bf935960d9a44b175a60d794ad78b757836dc7e865baf5e4f531de949e70a1917932503588819d246b17e5b798d75078569fff3a30cc8c9c52f265bca384b3eb8aa02fbd3e85e110c92010321d72040ba517f729be6cc8fb743e4d429855278b501c285a453497fa9fdd91425f4ae34f18dcc0903842b425cbf862511985892c0464bf1bb2d5cba68f5972992f0992d9e50b04cf10dd7904942214bc8ac2a727dcb8740d15e0f3f14f24b5c88684c2413e847c330d2cb6948a060bc7894da9ec75dfc80ef3105a05d7e8020f30096af6297179d876c20aed32ddb4706c4181929e9118fd7411d03afed58e614760625bca38e3f60eb0cdc129e6c134c541d6934b4b52487d7d570b023d257901b82a8328338ecfeb2097148fdb0863e38820dd0a3f5b60b0af72e2b301a1e3c030be54dc341ec81c232920ae897ab12e9d037967090a55c0f033d0a2a7ff9945260d61f7bc5f0f08148706a80643ab38abc4510424c024c48804680f7263a0fd3906446323e5a6500f3dc922a6a32dbc0712c2e478ebe597a430cc7a4f29051a2040b4c88210155970d549a4e0e891ad23cda8a26a5a1f0a4d4028a32f9c0cdec6ab8505f2aed1d01742f20098753a60c990e6118092870418961c58ab965b706f4d7d3e854407eed61b33149e9457b2aa8e2c451f5eb4781488dbddd5a7193934fd0c159740075b7ac5492f841473d86d18d4c1b4e3d6692e6d0841934e7836c80792d548edd89a8c72e5a61c81412195b586063ed5247926e772f5e29300fced024a689d8623752c4899345af69b5e819af4b6add870d90e8632d4c1f87bd25163e99b7bbe57acd0e352e6a58e652af0660fe91e25fa0104632df225ee104899b219d7df565c78e5bf8e7274153dab03d1436760185990dd1f249dbafcab550b959b4351c3c318c638e2f3b48a415a546d222249ebd1496c4ae90efab227bacf0050cb8f1b59f2b4cf8c21ac9c6884756dc49db1c7d63347a110b2dd7c99e41cc686373898a7e4bf39b319222f43b5d2d9a7bcd364949940269f98602980cd64bc63a259d0935407463267a9f392da6a7a6aa5702eeaaac624efe27d360d4158b086e24ee243c3bb91499fb3dc7bdbf2d43c22d42377305136f0eaf1f2120615999034c798744bc8073a4ea92ac45118dbdc0109b51570c7aa8c68f7248ed8248d92644e4cd19f80d5424b78144392b80bd0a6a2fa26647cf958da8230d6a49751677144f6b8e0291f8b0e90446794ac942d30d04a1c7c138b5975183c9501e9b072e9917f4c88fd9e8d2bedbbd26f1cf42a1c1cf6dddd65478a22ed63431fb1a4d9746ed8096a03da7af7519ba4da9ffdca676ed187d92dc9dbfcc10004c8daf7aa631ebd253df0d88b622126d13db3dbb57c50eb6265d5cf16fd9062cce13f275b87e5abad6c7363112484409f79b518e957bbd1b205b0f7c16165db760f03df9c44057a28de50c200052136e6a6ebf7b5a1e2e527959345e9f1cce5d92c4e6755df175fad643a462aaef562b3d013bda9896a47fa054a80ee8bb84237d6200ae9482d1ead57c369e5aee13c2eac391aa268d22fc0938336a1aef967827095c2db4dca77fba0205af57055f27c7c32b7886d72bd513961c7a274909a428d7ae788e72661d24a9a9ad47d42177112fd570ba2cc62907940a6d989ee44efd47c69ff38da1e400f76f3b991171de02459eef153ad5c8d574cdc59e46c0fbbe4a3473b1ba4e326fd71b538f07ee2ed20cf112551efcaea61bcc99f856f8888bab78afda2bea0ea28436dfd2705efd2d503c32d95527640d46b809c69ec016019e6f0f79e0335861592e6b90bc1be1600123150ccbb6267e5d71af0ca5eff72a282acb556280d979746324e182b71b1782e422b2b72c0fcb2c7690b8bc47b08843c1af11074c7238d3caa689e69743b9435e87904126710f3795b4ac6f25ce0a66486e86d7c81618832acb52cdcf162c34c336aab183cf12846f1b5e4fe956f250d0ec437e55874e21058955ec5f22e89b03b1530ab68f1c4245883277d53224f2dba9372d4456a3be07cf98cc32c414f9d70f18d2de61bfbc6270528776d9542c12e1edc37c9df0d565c818bca6ccaa8f5467e49498398ac4b6b1663aae2f182b2b61e4b51a235f880ec5193976956cacf33ff4862d521f6903838aa32e590419315736936f442f74091e9312d1df638195a0a191f2752327efa417d1ae2f7276569be2b4e28deb8b4dd45dc121f9ba3d142850a635dd5654ee704d0e3a295bf69a7ed65fe79077a7cf3b76b318acb024097520d505fe5aec52d5c08d490cf4d613749a5383110aec14c901761cb056fc74f3abee5d20208798181aa3e28d39e4f18870e9c6d1d6cfb4782d672cbb3f8ce2b36cceb7201b7175266ce1bab62761aaac623042a67c49a5a2c1289f1faeed37ec88d57cb158018ceb9eb4d15f64fbfb4d4d64791de7432c0a1220851797cb4a632813b94f088bcbcac4abe06e610a4b1ec39659868bff25fb8de228e71f5ea540c7f8a1fb69a0550a18dcdadb8a0ba611635627746b45baf1620bea216b2613641ca3ceea465e215953679768696de92ecd38aebb2c1805bfd615c8657440b4ea5975566bf26d3cb19eb9eead9b91cacce90ec5c0771755250d1cd2fde39803648d81352e3dafb90a652d4b5af7c3190fc82873835b1a31d4a36a5d3093531ed615850b66aec3b5449423be54f17328e7a16127bcd1ad4cb304dce5955dd1d011d8d15d46ba4892a263dcd9be86562fc78151d32e491bb4e3b3a3596efa1344211afb9d5d0f205e69840f47f37e12e7fb7bf5735b8599d722ec8193241838f828799d5f5378e43d06aa7aa315f2b0185219ba08e563f82f4a50a32c8c6d7d48d45064b794a0c6d1264ccdb9712d39e33aedfc1d82c94a39701ef9f183c1239a03b012bc8cc3e8a40405cf5ac115898ff029d682197c7c349bf1cf9642497dce19a9cfe8a032e35b782efc8795741e9cb934ff07ed0358df888992134bf6d0b2d9feab05c67fd10dbd24d55148f0e825f673f3958282f7235e839dd1ee52e6d43623a557ce0152886f01bd65f309494ef4187deb2c6134db3649c5605c9fc5997bfe7c2f39f37f40b9a2ca0706008576e1c240dcab0280da226e0de697ee9a2de840bca9ac507894f1302b18c2de24bc77d8c0c3108289e403324fbc56888a750a0bfdc2a3c21c2f48a21bacd74e4fcda98033c5fa2daea57bf3af4f6c1e0aafd056c248f1beb48f17962e6a8113c22eeedb15997d2bc69467088dd4d7edcc519fb36d93fce07f13f53d32ea5ec781d7679f349ffe6490b0c1b3cd7cf822933b0383e639b145f578cc619fb98be6d55e373b4488441ce087bd78599ee1644ec7e0dc7c169c529e86cb4011698530e562910cedd840a9eb61849406b6a27b594cad486c57f50a67dbe456a9b7c1744e3e60d72891464770b7a50436b8115031fa227ab70170e960748fb05e9fc3fab51c78aee9d805654f19ac225807b5d3bf2fddc30e49361b576f11ab05ddaa854a816c04dc14376442d21657983b097d68c2324a21f2f0af304d378edd082e22c2609984ca96617e8c43cb5f1802cc2b350e0b4b0b768cac2f061b85437bddcef9c48f6fb9c6fc6b005d895fcb798bae8bb604d2a9fba5081c55cc11b96697ced4142a706945bafa111ad687215b4aa407aff3cd0fdf98cd56f5e8c7eb7768d63b16f20a5dd8f6d8a3951658b5ab48772a3876b633248872b2eb8341ba69489121f4354a4a9961a941da2a8246b2304d340ab1af105502eeaec973f5db55729d773430c14a00200766d175d1cc51d9768384495e319709320c6efe50e9acb1703ea79237cb6d7cb85ddfb6ba933498d8fa36f936ccaf8c85a82a28f214b04c0b419e444a856fb2fcf8c20641408e26a67f18c0a68132574c260b580b429d8740d074a3240bd0182641c8cea043e55bee357a798aae228a55e86924a1da2509a5ad0f7a243b81caeeb662cdb0357fc2a7feda430df1ac4c7d3716d680510d9faecbe152e64a8ddf65baafbf10c0ed442d02423ae63b17c88d630029d458782ddf224a28cfe7e9739e7fc5026640609806721612a47dc91e5a5d8f8375f4ff0eb186fb50000d4e5ba3af8a487ba8d46cfe65deb392ee628f30ab7c320b45a3724ad487c03fee0887a36a9d8f3950e84bdd6b802e01ae8e3f5459c73403e88624a0b3931eda800fb690dc0fbdc741d60d7de7b8f01031f8734b34041eaad2338dcd6cc544a2682ffe6e7c02613a8ffeec91fbf83cbfe75382f0dc6e7e9590eb0c961c6aaa6e78ec2760a0625548e59ba705b1c31f91595841541cefc2612ae76cb9a01f4cb38edff797470a414bdab7b28cd4441c5a960ecec11df7a017863bb7666d401e70918772c58269b6e7dd5ca4ba36e08d534df440d75f11400714bdc27af48093469a78e7c21f233bdc260b7ad3507622571f5b8c41429276582640c9348208ea080efae161ba1210237284fa6b4f19cf64d92aab1d6bba2bc4a0701a548d53fd997ee4505967a16ada03368236222bda00d6f961d33809153854b6a22f2ed447867a2151ab4842ee125be7705b4c5cfdc70a36d7d6244e20f9602f834194fe8c626881b6aa3335d7e000d767d3294ce945b7b38485db47aab142485dcbf70702649ee8cb84c3ac54bfacd2f309c5c6533dbece1d774ead1a857194239b07dcb14d97d3f416b7c942f1eca0b1d943889995dbb66878f50971be3a387e62325fe081ebc2f372a59043173e5669dfc34a61c7903c564eca80a04dda0d90276be186f89cf6f895e33638690d16d2e1df7b53ca0cbf1f707dbc078f38d171078c1cd8c759cb5914ba7711b6b3e5cbe1a404377a681b6e14bb2421751a8d908f3234766e34d2e834da4c88c3a9086818fc28d6e213a73025b0b4576be2602193e86e38c88c443e1b8fba8100e62aae84bd8e7af742d27f9ee1e680ef10d7f597532144be142e53d90f78c7f979c0fc1172d5ef066eeb71b60b7452b2fc6a87f81ed9defdc3418abc4bf9610162b2380c113f75143a4d85422a93b44e2d50ede6cf890b5b2d39f3844194d2e3f96c70907449568efe72549e923289dbf9f3068ede75ea5a07e051d54e91472efa8a006ef6894aa5f9704d1f536dabb8d6d7156e249b232b2d7e865b2f5a8c11d1e82805afc030a780c3a9696f4d88e8d81a7d77df019ba37ae6152715edd15144abe8c8e484c7ecfab0d624aeba8a939ce0fac1afb7465588012f91196172d62d10382635cdacb58af9a9b0b8a5a14955fdf0510b5a65324434d421abba887bc00f47ab805a85084e282f788b83dd9a64620358e0d7316590585d28e5f563bda56922a8749118364b8910204a1ab9c1e5e55647247abe63ef13d13413a667c63639d7c3394e65fa13de103b4f36255277c755d3fcc9ded573252b54eb13976bf39bab33d446edabd303d25d49e448120af5c23ae3b87720a337af52500d32341af8b8300c7c1815647a3974455f3b2836ee48ee558d1a6e596428c6df716f7f801efa11daf5aa33a4fe45705e395eae28cfeb29e4573bf14d5a6be3e8711f50c3eca66f37432b66e2c98423b4de95e9a61f18214ac8de1273149816185a5c3c797b877e41b0c96311f9187f0d1f4201b06e2520ca2187407381985e1b403bef07d9db6a45804fbf824c24bfe0454c733a71c8113e0961e1dd4cee765db8dd585f5cbc3d3e55b1a50a964a3a3e8a0164a4e74293746d2a7950728f8b38c7259c9dc62f78ff78ac9e632f6e6f0a8fa1db2508ab0c7bd85d7fc6260508106f8a2dbedd7edf159327d990b253a32c44051d2a41ecca4043048a974b0027acd2d736e67f4e5f0b9909b5a0f37cf440280e57ff2d5ed824300df6234d67832768308877416f5180f61c96a1b9a427f604402f3d031cf4315d01c62277514a8698b9476139a3e459ce0740ecc4e13f7351e46da49dfede9b6bf83b08f1e76d917e3a51c82dcb66dbb31958f3f086e70ab726f1db8115b77469a163fe3dc98c88814115c78551c011ec66da45ceefc07a8dc1fa724606c96d8020aa47e09d6bde8e3e29f99cf09d61fbe3fe2c0621a2f7c73e660c35f492775b6a13c4caf399d1e3070d11275c92497602d4eb05734730b76453e0033a1f31cde2c7b87989e81084cbb84b3a66d1d059f4892e2c6be2976db7b981bff440800246ac1062f9ae5ab8edaece3bba3e31683b14986dfee2d23e26b0390c5408c6c6b593a5b5b4bf9ced44ee174286f5a6ca21d9109b18984aea0c11f4bf091846d1db507c523aa42cb103fb2dd60c3ce030f0ebffd64cf129dddef80818efbd6506f053c4445c794677891ad3c9e864e7347fc1f456e3951a9d14d9a9be058b9f73947b65f1f6cd001c2b970d090bef270c52da18128f1c73c791a6fa3f31d2e527e6f3a3ffcb145c88899b3d04edb0214ca5ee5eb181a888d31141620493468e1b97430a0927150220925a16ca59ffe804af08c1ea12f706f7c463f379c3cb0af1311e356e25bd47eafd3a35fc8f0e6455847908c7ce7ea337c69c2b7cf1cd00648968acc669e0680438b9f92fba685ee09ffda9c4c21309dee92143d9772422fe51a1708a1ada5698503465f36cc13aa1d835f7379f7ce223f5c631538916940c440e375d22f1ec7897fe07cb80f40f5d7d8d3813694cc7729ba4299d01027f81ad0d6f3d628e412aea186e5508e8fa3071f2724cadb1d808baa27d20880d375c61090357f91da029953f424dc06f3efe744ecb7eb72f4fa150686d5407d326c4842ef146a0eff4852752c068e495487dca50a8e5b2bcc81612061d58de41baa3066642f10cecd173d0cf619d1b511915fe15800d0914ae75b7ad69591694ebd273d5cbe6680206c8884f9e41f948624aa762b88412dd3d99e1b11e76783effa23e5696002f44fa0494e94a5cf9b60ecebd5d9d64f0681b91ad0c9be5c58e4157020db5de81f06c2d76d3da574547aa0687a3913b69e088a97a1619651f5f2da1880d9aff817b8a33e16a709e255235692f8cd61dcbb2ca36b7e07d9b288506090a286a28924107c10306e14d2521402b54eb5142b2bf0cad02378cd271d4f3564dfca9c84ea1f5b6695892ca52498a72acaf16d1f24ea7247498e8bc09b1fe19477f45284a3d76648ac9d6b5fb28d265fda2cdce0977055da5f99dd39e0dc4821e21ee4b8aba6e03c9aa09f6c71f6cab0301bb565b7e20ee711dc7a849cca1b6266364375d6f045a57f55e74d21971778fcf4debb85671d42b6b0a146daa886d80ecbae7a51a3abd9b15b37832c988884f1820975d824c7d7cf71ad4af38f46befd91649141d65e30384181599e1b2543250d2cf37c8e0808f9a3813824f43a548bdb33b416c03eb8c98a12a8a21d54ee7632ed553a7bd84da1a6d41e3d42352ae15092c91bc743309fa4dc8fbc8ccc962949f7ecc45902e399633ae71a98b78ce48f9cc3599ff9731ef83f15529e040159491902bf1d5e1805c13aea94186e5bb0363dd61c9055574acfeb2a9a1d6ba80350f7f403b0748ef3df466d05c585d1ab4da3888cd48c66454306c9eb99a07295635523c9513759c9861ab25ac6e2d3ac4822f941e990764364a2bb81df79c055657f0a36fd9a8ec3020c0615c3747aa9fcbdf6e52c90c5d6074f6369e14c5d92d0c81f75acdbfbd5da7d8b49949152619d5ffd543ed5477aa76a8367807b7153eabbcdc2a01fb09da5cc46860cce790f2344625f524fc504e16595943c0f890ed161a89a037371011ba0a1f0f0e1184e3dc404c08353fae7c73f64f9aa0263da1138a6377b657e8f11eeef625f021a52f3fdd502cf02048d3c70ef42ba2c4ad48baa15f915f88e324dec2481080f6e2128ed1ff940ac5f412d89d693fc6cd38ff944b7f5a297b453dfa24aa326ed4e9aba97e72c8f48d346da6c1fe46c40f03ea21668870dadc160714499c8aa9e2f1d192d80a1d6165d471395b8b70af40d8f16cb3f27dd8165cafe11e089ffb06a5e11f3532fc512b2b36583f5f70cbda50894f97333a1473135c4ef4adb28e95dd8828a01b9d4812c91efb92026f71017c0ec004b8eb460391df501c329f4f9608d0b3ca342da857199e02d71d1d8f3ce3af7961f982c56ea62efcadaa629936ae0d94ab3726a0da893e9554233e8fd96f86918f98d9d416624a180092463d5597f391a4c234616d74828fc919f86fe6517321200988891736d76da16e7dba75e9be47971e5dbb75df4d976e5dd5e96eb6b4e7546f57b73fbaf42dbbbd74c4b034701721de6bb68bbebdaabbdebaf5f8386a81cea7e5f34f4bd1d732ce5662e4ba44defc8c2c1619dbb127f9d1156d912a8b382a13962fce0a1ca663dc3a5ad7d455b725d9c999638c0201353038ef1fe174ae0a317563ad7d6b9db98d9f2c424a8eee521b1c471730b30d1c9ecc04ea4095d5528989a6a133a4eedc2525f8d03d01c918954cfa4277092fbfbc68cb63838143b244d7a0fd1eb560b1e58c139d19ba49f2f254e286cfd42c6085b24c81cdcb65d78a1a6d47fd3049cab59c886aedcc9904447ce68aa98bf11bbae24db2134ad52327c04be2bfbdcc940653e8cb6577a26e53d282916c2a9af1faaf974ba9beda2e1f01c1d3261e702f53f6395046211f579201c3b6a893091d469b86b57c95488837a69cf84b38017e5fdfff7f2d5996ead893951120ed28cb083ba9b1162fc6628e805b4791f2e56dfeb21b5ba2bc2df9c30f7f0e67a417d37e9e19d252ec3b9f31c89723d5e75e4b2d49e85736253e3090558a4a505ab52292b8a1364d6e234645436825ff92ae0815101052406d5fc16bdf9a58d06ac7880bb791b18612a4aa64fedb6df61f05a751d944b93379ac178cfb1dd67bc42581d513eea04b25e1303d49a3cde3f4ad7dcea809780e473821667e2cc18238ce13020629eb8c2d8c8ac36deef0bf92599cdfa6797811d0e6bdb4d7eb68fd61d17604b8006781bccb5e5ad7c87d1e977677297828bd17608a2c37c122aa9cc7d4b8f6358aff73b490c06adda264aa02babb5b7e73a9808c4b8b84806c9b24b804da864ca9d93fb369406eae6845b622e7d5eb44070dd8e1974bbbd82934410812cd353bb909638689d11e15395487272806055a55717743284ccc017a5b27853070b4940e000c30c000030c30c000a811c068cbbdd901003441db006c4a4a4a8a1b17c34ad606c04204d64684c6c5740111088a08eb07ba67a01127375bffb5e871f44ea8a0a1459fdb9f1b52fa351ab6c52c1acd182219c3183da7b5852cfa890cf3c78c2c3ac61b046c118bc62c74fe47e4b895f1b0e8a17a8efe57cdd931f28a1ee5fc2ecd96e18a3ec4f1c63c34ed49af156d87cc91bdf8cd2503b66045ff185c94d2148fc99d55f4e92077a7f19f7cccaba2d90e19b224f3e81049451fc3587769d0985222a868520665e6a153eb75f0147dfecc1ffb28d43fe54cd1f67accaf7a6652cb4ad1c4841822a85f4e1749d1ac44c5909daa1a83a8b718451729e4603e7aa1008ba2c91ece73d60c14c1960c78052958c002104080048c60051430818fa051f02cf8c316a1e853c393c4c99bc75125822dedc016a0300b6cf189c653ee144692a58c99819ee884d241cf63b80a27ec6cb189365c8a7c1015ff93e8c7852d34d1447a7cc6282b3b8e99e853c51909513a4c7429c6fc29b8bb97e89159ebc330329567b444ab96730c21836a54a2efac12b9ec5aad1975c21694e892e38dd9ad934de4338926c4e48dfcf954743692d82212bde77448c6a1d3c5f2256c01897163b18a38913ea28b9396e3fb6a92613aa289ede338b998ef65f6a891f39f0dc262446b2da97b324216d1e588102b12b9a13bb78522baf8225912a721852d12d14aeede8f5d0d611f8e88ae716758cf980cd31c87682c446a06cf0925a9628836c7148b50113d32dd2c44e7fbc0273ea892499310cdbf4b947dfc960e7206d145cec6287c8cf078ad236c218836a5fcf499903a659203d1698e8862bea6c1720b883ea34e5afe383a3f86ffa1f5fe7cfc9fa1cea5e8875e4ece232e57ca167f1f9aefb290e52ce3f9cc7ce82c2d4af4f929cf921cc2167b6854e39f06f79f74c919610b3d34e12cb87b9660953a9e875624af7ccceb7ea07cb0051e9a9f8d7fbd9c71ce5c66618b3bb455d92f698a595a2272852decd066ef24eabe2957c8963a34293639b08a7e10424587be7189745f65af3ece0a5bcca18bfceb7d071233c62b87ce71b82c9a2118842de2d03a1899f76d89865ac2a12b093f6b6d314ce390a9b0c51bfa9c7e292219e63cc711610b377421a358d9b26b869a32256cd1865ed73ae7c773f605890dfd86096a95dd71742cada1879da32bd3152d46aa861ef4a71c1bf2581c6fd2d03cc6f173fc72cff80b0d7dd8e0bffa7032a34e9da14991c2f563cec5e766861efc7f5e85bf0cede543df2c0e3ee64232b4197f0e951b1bce9e3c862606cf3b93b269c8be8aa17560213ecc181286d63d76792a09c9424660e852b4c3aebcd12f7429495b876a6fb6895e68534a8ee54da1916ad785f6a232ca7b588998a25c68cc349557b098541cb6852e834832edcf0b39635ae837c4efdcb3f9915b9a854e7b7c2df36ae3980f0bed46648390e921c9e22bf4b94f5fc343d10a7dc631c404ade89e64af4257de8d33675648ee132af4e220f959d0065b294ea15db56425c95229f46b793d438fd98e411a853683bf9ce2388342f39343d47ad5b169ee094d7a2e0fea9ed173684ee8c2a9e6a874bd09adc588a771c2c387d399d0c99bc857ce7d315e5d421fa366fc95a1435d5395d0777c24daaaa924b4129fa2f59e67cfca21a1772c71f2a923e68bed08bd84135735ad8cd0869858b4905166fcb3083d6a14f3a470392a894468265b9e0fb9cc81867d08cd49528b214b27ff09854045c72bd2132c41e85385f9941971924309103a7f14a29784fc8362caf78933a3b0850f9a7c904365ca8f2d6edb83b6f4332f1ea778d086489ddce4603ffedc411fad2169c610e64fa33a683cc85f548bd9283e2a07cda47a92164fb7c0419f632aacca477cef788b1b749a23c59ebc3851f2dbc2067d4a9919966557d41fb5e8a16f2cb1b48e75cf68d15774982b8a9986d8e82c9a3c3925cb8b5316cd67c5f1903ffec6ecc6a2b9d8680c332addd0102cdaf1d070d16c7945a37b59216a85ae68834ad6d4973c71e3de8adee25e94b2866592b3a24bed712efb649595761548ec8f57495a51451fbdf17cc61ea5a1c452d1b669650921e555ff0b156df89c71589dccff949ca2774fcf3e0e3d486bca149d07536f18e2a77c9a2d459f2304bfbc1d1df385a468634a19b9a8e5565e3f8a5e7d3b2ac3208aa2539387a19ee43ca75828fa8c7bbaf1977be1018a4e826438a3a7394c353ed1ef34b43cf259ea31e589d63322bb3bf63583db891ee30ce9bed93cdb839c6882a49768fa83f1dc6ea2ab2e8b1957eb5aee144db415c28694f2cc99483c7a829868a25a6494bc217527f912bd3cd269733f13b1de127d6ec8e6293956892e6e8e6ca2aed5d01c946892374cfa59a911369d447b9acb53a84e128d4ca3b0ab55d6dea1481ca65651a53248746255a119eec13d858fe8e225c78ef3c3ca2bd3117dce58f344c78550f236a299d0911be69aaaae31a20dba0fa7a15f8b6824e6ac67354d116d9caffc72a2aa9ddd44b4e32ebec144cbb38c8826365c2ca6ae58640ed1b7ba054db9c6ff2965883d3c4aade22916a2e9503185107de4642134677785300fa2cde0d3f02a4355a4a820dab896c2a5b428104dce30ae86dcac267b01a2f5071fb4f16cfed0e5488a1317e2391af343730ee4e286870ea7217de81faac80789d39973980f7d66958d6f7c1abe617b389c7e4e21038beba1d55cf9f02776575e9587aea13c508d241efac7eeefd6f9d79fda3b341222412643349c4e7668f5d27a354e4ee88f7568d41bfff707df8f5fd1a13dcbe8193d27c544630e7d6a903a462e4e475972e8f5636c8b1e322a8b2f0e6d869cb099a3b741dcc0a18f9807b11cc7af56f00d4daa14afb9b22d69446e6845b52a5ea33bd449bd21006de8b256489f94d2f3f5168500b0a1effc986415412c4eb3862e83e01d354e7838ce8c206f87fe81789f47920cc6e73a74a12beac48971533a8a0e6d568dfe9f2d5812499943e3e11fab727ce4d843e4d0ab68ebffc7d37c968c432779a91eba7c67f385436f41b3e9594a6f685f3f439f881d1fc7e486ae4cdb3bc3dc86def5655a1f57ee87980d3bcaea9e35f45ad9a38de14b840c574363d528945e76d3d0436fcd791f5987103568687522ae4fde0c8f816768d6db1d26f70811276e862e455586af61bb1aab65e8413cb2ee910ab30c2243f35932632652a4451f43173dc44619e53f5e85c5d03fcef961f3b4bb832f0c3dcede3fbe2176082a83a1c92093dd93e867c8e30b3dc8aa93175ac79ae3c6771251005de8321f85594a898f2524822d8c8200b8d077a45839c7901b52dccc04b085f6a344de90cfd14227d9a33b33e452cd4b161a710939c429077a591b26002cb4f9ba71b8cec61f257385662c2442773febc88b060158c1aa18e42b3447f89855a11431a654546663eda8503cf62e491935f69ea7f0895c9491b8259659299017d753c8339a2838953dff57544bc6120a651c2372c33d66ff8462cac03a23cf09bda523cd8c8c4a2608a009bd54a360e9ae2122250026541d9fdf1a5eaa1d670967ac1123431cc9f4a98461f846742a034375803417082009ede5d8b1fdffa1537924343199ae04990f319a2482008ed07a945897e84c35040118a1d74851f14b31698e3110045084662ba74c1ec7a417727c1000117ad8539e53267f087d7a6811b5e887ad4121b49e83244be1435e574579104010ba8c7d751a7b4ecad4c6e00d0a3e50566f1000109ae85c296feb07a920801ff471398478297e5010800f7aef8c9a1bb1ec82007ad05b9ec8f1e15846cf1516000f4c25399ae738612e5b10c00e1a0719edc87238778975d087c951f54da61cb4256116727c71d0e30e7296d338e3b884057083b63bae925ee3c6552d55110460835622ee766ab1166d0c89526a118df14669d17810ddd6fcffd92a3e66d14399378dd10fc99349010810e07f052d10c30b59f4f8a43b7f6587a09f7906582cdaec2cf7e94f0dc388b0e831dc4d629553c6ef06e1c52b8cce065b0ec6b2cb22bd62f3ffaa75a5acec8ac67b35b664aaec9f4a2b9a90254ac8ab6156f43055ceb8dc234f625d05dbd132b134265c78a18a3e46caa951f87c3c9a118217a9684b5336980fe6d98df281519d78818af6247bca39c4c4245eb9c28b533439464bd9c53536335378618ac662aa6c042a700400195e94a2cbd0fa3346f85513408004c8e08c6005af8108d8b94035b0823741795382920213907100c8c20b52b4a3b2da99611f47596ef06214fdabfce4bc709a49aa17a26872bee885128f7937065d8002b040072f42d1eac36fd1e4a963ccf402146dea98f155d66e7ea14ff4215c349c2a4b95c7f5441f53e4093b318996981fa84e741ac9a7d362e304d79682ac964679041fa8da4df4919b935ec453045b32201f304341060a50009385179ae81b6a78c70dfd235c26822d05d08b4cb4914534062fed3832bfc0441763eefe0c834a5be97589e627e843978896c53f23bcb0441791b36c437ff06fa14a9492bcc295f57f3e112f28d175380e29755749caae400a2f26d18fa54c8d0ed7d7a03759bc9044975c2eba7965fa831791e8cd813d780189c3c6286696e747b4272251b7d4252cae8ee8c483cca8a4e30dc9042690c10a5ad0882e545c8fd93da77497e5032d781664125e3082bfa0163fdafce3b7884cbb377a5fe5caf6f042117d37b418d3332bfafa44b40f723d7262c590a888f002117dc6cda09e521cc18b43f4d93b3e4644b8decfbbe05d5081340abc304497bd77fd31300d934108072f0ad1676cd08b199385dcab092f08d14f0e3942a724837f80135e0ca27f8c41a444d1d8186604d146ebfc1349dd22791e883ec4d9dc38338c19a718107d8ad9a1e768f8c133fc437331a92d777898c5ca0f7d652509172b5a5cb082165c60032e78d18756b733fa820fb987d3830b5ee4010fb581177770fb736f5f8002f80a3050b78a1776d833b2e614d9a93cbf827f41f316295ed4e19827c348615bc3e274d0cbd5d21fb4aec77c0e85241242438eeca7be90437a713a4279ca97ce0426f0c22d70410450b0800abc88430f7562b53898ea901c0e6d3b0c0d7973788994bea173a0f966a973d8488d1bda98a12f8cc7ebe7e55eb4a1c98b9c98bb5286e8d00b365895691ef125d19e650de234e40fa99d2a075eaca147d13535344a08b1c2176ae8b22297a5f893c36f9f863ea7896cc8c1a3ba945b7881867ee432cfdc9349709033b4aded69514d6386d65ac6a28547e51b7e199ad78a551136a3e4284e863e7bce96d02ea2eb2e62e1c518fa2817b55f73a7f5a75e88a19dd4be1aa2a1b355270ccd47868e34af9ac859c0d045b9e8554b1633ce78f1852ee25f94d98cfdf1910c2fbcd087b0cc295e332cc478179a8e9faca77371a14dc957e9595115757bb185a69259c40d31656121165c59185e68a10f2172c4c73f29436fb2d054af874879debcb863a19df81e3bac34889fc9021480062faed07af889112722ff94d6821756e8f244c8679e8fafa00b50005fc1b3043af00116bca8429f2148f29e7330155a8b1f29fd09c1ad2322d84a410b50e0801753e8738a7e72eea1d12662c0bc904253ed2177c8a2d221df1751782b8b8956c6b2922dc0023496f0020a7e9271949b65377c022b9952e47811fa9e13f4cf9de2af8ef77d5a1ebc6802be608215005f2ce1f830e3f43cae793a25507933e38821fa7a4d2a2dbc48429b3d8e82684ad6d2233ae00512bad0d981a5a08d2258ce119a6e146172b538eeef18a1711cf2f195e5bcca7c115a1f0dadd25973c7f989d0e5bc12b9722c3a67cc10deea4c0dddc9170d085e08a1d1fc9124664f49245e82d0868ca715733c53f70910ba4c21c78c611a98c47fd04c726fe0193de2317cd069d4369f5207929af7a08f7290de604336fd79d05f8776f09508e7baeea0f73469ac66ee3184a983d6e7311c49e19fb9e6a0e9d8a92da2486fc6210efa5021c38b9af2556adc0d5a738b16b2725ed8a003c72315e98b412da95c14c10819a52442af1ba2ccc2f2211c1742e83aefa328baf2f0f4c7175810b808021740e8818410ea16278ba9fc833e68789037637be3f73e68b2667b7fbc637c0eed4163595d3e233c681de67e716439e939b4831e7fe6a00e72c22a223ae8fc1f4f893448a95225077db0ca9c1ce928e13a3868524a68387e6a2125fd067d230ba29171c7850d9a8cbba1697a9c4e886ad144b8c5f58b9c3b3492166da485a0ab39c54cd12cba6e20939232beca0c94459b9127c4a7e610b632b168633ee34fcfb08a8c8245df5a5182e536ee4ef3154d683f4c111df67e5c5cd1f9b8f86eca1eabddb5a2cb31b89ca6ee99d02e2b3a0f25b3d1718239ec57d189548b449bae8a1e9679bce66b53d1c5ddd988ba1df11aa86853caf27992cf3288e6299a38552ab37153347da1f35ef1bf3b4ea5681c8be5e4a9d318849e14ed9f79eaec3b19453b2a151df349249f90287a601172c3bf4a61cc118af6ad3b9c88ea4fe81c287af0913c65e74ff4d6efb9215ad60b21c4138de5ad488f90184be39de85c3d780821c4c5fc83134d499f74cea94d34a79f3dc710323b21d5441f763c895af846c828136d8cf951e6e82f267af3209372eb76896682e78b18af32498a30113725a9124d0e1d52ca30a41c9d90124d4c07176f129a442b9e4386cb8c24dabf6ce12375ccc92d25123db858e19366e7490c89369ae6e33857b54e9a47f4a92791376e46083f3ba20f91fb1abb1d3fb478235a0f79232b55cef6d230a28b1b91fb4c5652fc2ca22f09df71a2c4aca93145f41ede1523e88598e825a279cb7810c91be5184388e8b1c55ebd1caa734bf810edfe684e5e0c165763883e63eff75cd179bc55219a8d216b8964fd0cdd428856c463acca9c83bf3c883644f335478e1dd59420da4a19458d31ab6b2507a2d19d9e8cf462c7cf2840741e9a514453428e2effa10b33b1cedd28dd38e5873efa74849042b333c7fad0a6141aaf328aefc8333e7439775ae71e6fc912df43a37f792dbe2447dad4439bb14c74c61062ddc379e82c344caad141d47f141e9a88d943f4412aed68dda1edd37c49228fa79c658736a8c79aa68798fa31ead0475506e153e65392930e9d690a3d0ef23887f65f3f6c989ceb930e39349b1ae68168461cda8fe3197a3e929cf387439b62342ffeae4a0abfa1cb8e672eb6916ecad80d6d87aa6530f19ce42c6de8e364fe95e4316ff09e0dfd7f0817f2641c1ebcb78676ae420ee929357732d4d068b5c79332c7395c340d5dfc0e9e19f31c87dcd1d088bb9ab4464aeef93f433f9b3d787ef64dc7a319fa30bd9347d72f388696a1f398eb31c1a4bf572243ebe162d2f7bf4acbf818da8c8d34c54b2962682be49c817bbecec647189a4995b353bc08863e63c43055cdf3cfc92f3461fd73d44c122fb4213b2efa4746179ad020f89c7c0e49c2c9857eaefa5d2cc30a916c0b6dc8127363a94c0b4d68cb94a311fe17bc2c746e162242555bea14c1425f1edb8357675ca1ff90a81e72f058a1d51f4d92be39438b8f2af4214163caeaa7a77143852e83dc22392bb7418a4fa1cbaf99e69d53293431c3ee90518617ad5914daf5fc29a3cb50e8b378aba49b674e8a3ca19fc87a1ad95d62734c045b51188013fac89afe3197e590f135a1c771529e09ed37cc0ea7716c90d45f42bb9633eae988081e2e25f430e67778174b999493843ec376e933d94cf23148e8a57bd7a16c6cee851ca1ed8c3316654dff1d67842e3fca9974ce1f895c8ad0eb83dca01c7e86f87922341be61f0407b137b61b4217de11fa326728a6a5109af9141556821c843ea6486cf02c19f9fb40e8d2d7d73b6bf4079d37feb238417a32321f346995fba83d85ce690fda14396c748ca69c7196078da869dc080f3c553877d055598e5fa17374d0756f740c2679eea7680ebacce42fd59fb91ce6e0a0f58e8faf5937860cf21b9c217f4c5f42ca006cd057bc6c8ea1cab568559286a5ec2d2d5ab3c69132aafcf27c66d1c69f1491837ab604872cdad78f8d93cf63a3a4c6a2374fb9d9dd8fe2270e8bd6424a578821c4f490f28ade4198f611cb0ed263aee8223a8c39e60f392d525ad1768a0ccc333caed0162b9ae8d37e155d6390f4fa53aae8a7753d64734e45df30ce231deb64c1345474593de6df48de50937b8ac632be1442f08a299a721c191e4b8666922f45d7317b8a1c723b7f3b48d17c66cafbee1933166f149d03cf9a51589c69df44d1a514bf1b36cd628c53283a8dea2037bf7a68c780a2adfc1f2fb6e4299df8273acbfa8f634e33debe3cd1e5c9a83e97fb3f1aa7134dfe8c49bf62f88fcbe144ef48e6f3a7946ea29d8c33248bf9728c9b69a28fce2886661c3f99954cf49aa3668aeef9b935c5441b216c7a78b72ed14567e7c8d2184b745126c7a41d231c6f5a897e5b525c6d901be3a69468ce73d4af709e1bab3389262e58079d183733d624d1669e6821bc5fb8ea8c48f4e292819af90312cdab6793890dfd63668f68e22a672696c6114dfecc949672a6118deb65c661ed9ff960443b9617394a5ef5fd16d15be4fee5dca422badc334dd21f5295e24474dd2186bc55952d328c88d6c1e68c2f557b88a6b3f1bec6f74a8d7743b47a3a1fa288669c520bd1564829e479fca8c32d42b451331cff55f79b9c8368f2a8e3ceef7b29d11244b3f94942786604a28d77bc61da16cf2b02883e88be23891c4e8305ffd0bcc6caace3fcd05fc6e76b6d621ffad0ec31c8c71cafccc387364bffc78410c2e61cf7d0a9c72491f35e0fbdfc9a68e8966439b379687db24f4b7ac620d1c143334935f5c3f4f0bac13b349f0e274b95cb6a19a9b583e8dcc828a2c9269961d0f7d2f8a312ba4844bf7a1a5d519e1e2588e8bf7be63c8e26f09202088c6f401787e8253354c8493454340e115d18a293d89d1a49a253632a449be9c0e3bd2382e882104db008dd92f906d1ae07f91e6f1452f376852e04d1bf8e6af7484eb75a02d16f8a9443638a03a2919d5839aa577752c22efed085698f255a9a1fba904c26488e29c42efad09f465407f91c3eb41e3eb9996ede98dadb432b53e153f69e8a6b165aa10b3df463e6fee9b06284ed4330828382149497441779e8b26bac637fd4090a07a826d0051efa2099a3abe2458e29b9ebe20e7df0d78e513374286fefbab0439bf95bc9933c9c0ae295a18b3a349f752fe722c70d5de9431774e8439e6f92289752cc1b1fba98431772c83b25a2b1498684b46c39e8220e5d9c6b309e277a8aed403c7401875e1b9f38ec8bd16bf10e5dbca18fa199f253fe324b961dba7043933be42f4971e81f5355872edad088ee6c65cee7fe1d3634153de4e5d421ccc235f419bbc2f3b7e5154b0d6d8629e48c3957529c4e1a9a559f0a792966cce5a3a15191768df8bdd69b33f4287ca3ecb9b01eb3c40c3d0c9eb3562ccd19f932b4b964757e839856cc20438f3ba520d92e92a2c3636836acab72c21543fba922bac38e86a1f3f81b3a316e2e130543531ab63a9ee70b9d749aa954c86fd1f742f31353d685364455ca3155d7e4422e349125632765b685a6417955f6bff05995167a6c21f10ebac8429fc26590bc8dc475dd3974818563c89e41e6f7d5eae20a8da68877a02947c81d267461854eaea2e2837c56a199fc3719dddeb6dea8d085f4d68c634ac9c263a6d0e7376818246643e78e14daac11953334f158311285b62b9e9883568bf71c0a6d0e4155e292e3096df2eff9b8972ee16427b459b55276c7acae689bd086945f3ea5993ece6b174ce8416358799aa34b686582c34f35ab0b2518b12693a29a23098de99f9585f3ee6c31116c0dba4082e2a79bded3a39b2f47b02abbe58a1961cd95db3a3a772749045b2e60c107ce0b5d14a1ddec31f31c4cf0065d10a1f3938c2956c434e86208bd69f438aa3cb99d1c42e83c3b861216d5372a82411741e83f6eea74f2a80c930584fe533408ae979e3246bbf841ab250d27aaacfcc4e583d624a70665d98c41173d38fafd522f28822d4642173ca05386262167e59cfc10bad841133363a554897c31870005e8a03d5fb7941c37fce3a4052c48010544d082116c80055de4a0ddf82e9d8267cd9b5d1c343bb3adf11fc75c3e3728f47abebb1fa60b1bf4fb41437825c91ed36bd145ea18d23be7a4a132e582167d3689251e634e2eebcca2ad081f77ab8286fd03de0213bcc9222e64d15e1691e4e9172ebab1a802c0a213f1d9941e62d24979045cbca275d1ce352bab50b941b87045a3123f06cd8fa1638b6945ebadc182668ffe215758d1794a67c879b3abe85b538c41443f935e45156ddede7c31261c64bca7a217cd9d62563f50d1e7f8eab03de34adef129fa141737f4c3d41da4550d70618a26eb666456a570518ade43498c8f3a04ebfce18214ed75f45845cf113aca32f015acc0f3045c8c02b9108572110ae4021455005d2e3ea1838eb953e26211acf9e22ab8800cfe018c79092e3cd1673031cfcb8430a26e27fa94fead21eae8b85938d1540e96926e996748392e3691052e3461f0bc992506cfb14cb4bf7ee5a9719a37f660a287f1c28154e8ec70fc257a141ac7dc2198264f164b342986389eba65ab3cbb12e8c00748c04525dadc11729c980daf5d4a34152ac557999cc93c9f44fb0e35558a19368e6848a24bcb1d427b78fc9647a2cf0a8f53d0df98543124daeb97acfe1f7dc4db171a73e629b970441f5cd671e675e32c9a3f70d1884e5e827a8a8db3354e6144bf9ede61b52c67941f8b687bfd3464dd8ad3f3ac3e507b7ce04211bd5ed239c97f94f3337c082e12d156fbb57778e8558f83820b4474197d4adfcddb53ae021400eb0345c6c521bad23c21b5c7ac21e7e4097c054e0114a460043318c10946d002154ca00005f88026b830448f45726810553f2ea60ad17b72dca95274f7c00521ba96bc10dd3b6507d1658f7a1252bb68c31441b42fc1439e73e412b90d4497993d21c6c999717280684df79206c759c662ca005cfca1b3f889ac21ca51dc1c3ff4d79d25f127c44989f4a155911c99cea69127ce87ae1cba5476871d55dc3d742165ece39ef3d812d543b3218a87df18eef2e6a18b7ed987fd7933b2e2020f5df46f13a914d7b2f959e0e20e9de338f963e6d64ad5d8a11f4d294ef020a23daa15b8a843fb302a72636673e0820e5d85649798d9433c0f99c0049e8005e8818b39f432bf39538a5bca8eda19b89043e31a3e7314c9cd0a713f70118726c5acbadf6072c3b94ce0020eadfe064d7ee91733242c8881b18b03176f683a857666947fcb3482810214e00a5cb8a16f07f9e7416b65d49dae818b36b4924dfcba5747aa418c0d5cb0a199a0297214efdc4a4faea1c9151e579cc851432f997b19a63a5a5609388071c069818b3434526ee618580e9fa25482920208a0a1b914eba8baf16124242c707186b63566338adf0ced6492b5d0b82a031765e871e8460e894ab9d08e810b32f438568eba951653cef931b49631f665cf8c2524590c6d909093571975a36086a18d934a3fc3ac21b5886068542c87b0a41a527fbed05e4a9ec9b18c0b2f14a707b8e842ff2f1f3f3357e8f20e17aa00cac516aa00a7855e92846472b971c8052eb2d09b24cf39a4a2c45ccc0803175868c37fa86cacec3ef15ca18bf1dc2a936c6ed9d80aad8ca7783c1bad42dff02acf635589b21f15fa0f2fff383b0c3ac19a421f297986ac5f7444090326052ea4d06f87caef74ced5fd89429743f9887b83a1d085acf0a70e33e74b9527b43166235c8e2924f775425f19840d1e37725d73885c34a1c9792f348639c6085c30a1d3d41a2ac4867e86e2127a1c6364678811442e94d079c528a1fde32209edf9a348d11ba3be8a20a1d129cd31db272332e3089dac7cbc181afec57d10810b23f4088891a8f4ee4402b240241088c3a160300c068db31fb31408001824268b0663a12007b4c10f1480033e1a1a362628162a20141414161014160c088442c1701808068302826020100a0983c4f02858f8006e46c45e7c8595611e80f12b06c584b131749360fa200db4e50ef80967c1e9f17799a7984f3f49c546f61ccd8d0b435a58cb82c69a6e226252af826c9fc08905a2303efa85c4ee29fbc680b72b10b5077e5192d40c8452aab118ce40b481a02ed5d331c293563d67aeca8d920c09068da55142f5cedac8a5eab088cb48507ea827c7e6544a9bea023c7f892a04cce976608d588fd98724796131c5e171dee344e53e0dde0ca4660489457b6b9d67bada961f2b910d0211bd03159f02df3fd9dcaf5a614b0f0def0ecd01a810407766e2b218b6bc65c8071001e1d640c3c93c5d609d5fbe97bb7851eeacfb74ca570512c03f65f07ec9177e1bdde66c087f62eba911226e20240dad162ef765304ebae279aea8eb1597d0d72475814aec822f57012ca443f8704d01b9eabcf5da6580e69287c18bf139725b84310e6dcb6b35b697129850d2827d548722f904e880d9854f45c12887c61007c6d40417383e043efa36fad4363823183d4a8787961e5bb5d5acd0db3eb3b46989e366c6a8b87ecc8aa2d6839a353554394d4aaed28c0a05ca88e3c562205a5a412c33771b5da0680559b74a07f3d7eb8d5ae1246cf3f9f98fb828981b5a111ce9873bca6c941143851d9e384f0795a7aeea2e825b457539f9210b8ae1dbfd134a0b3f41bbd017fcaa886e8a3e2e8cf2a7cf05e478630db7cb184219a4ea23df306a11c4c94d0fc0397f32223a63abbdeb2e81d0a966a78319653ba2d4e7349b03e51c441c4cadd49302124624d014cccd0825835b013b70b0026e378820a577c6f8c51242447ad1a19573feb953852d44845d6905982519d8205d6b7fbdcdb96bcca4422826010cf5fea2551169e760086feaeb07f24f30fed84afe4058041f14c0518c6b2b1b34c8cb8ad2502184f63f03c6976f79dbf811144e47eeac102866348356b5a0c431583b7a531c814943d56aa700f2ad1dde01b67cd6491284954fdccb8b8eca7f92360c14b82a1c3ca0d94a273fc6868587d1ca3723183c3313e1cf0d30f58fabf38e4c034de97aaf652231669fa760506d4800e9a5376371153ed9e10f6ced99a9d2c7a4762df8ff25d8ae6c31aedd06bdb6293f41d802ab0128efcea22354e58a8802e11a54fc6fea5fb95ea646b6b629b0879a6bf7d340249e480c404edb0d238607c69b217de9f87ca3ceb259bfbdbf7cfe3fb8e4c1ab85f7245387eb1eccf1e7e0953ab7ebd71638b69b6a3e0dbf9d90b1351d60c809f4b522d7835befb5139e04c7a3e435501c7762f4c52c523a3151e0b8d3fb3974b4cd35413d60be69fcd1b64c351e6ff3007750c8208e88db45541272825f561cd0747d462f5338b3eff447ae0a2d3c0c0907c734fe7d2c6cbffb7b128efdd72ea729897d3c832c5901922671d77a90c2de4e11e01dc1395e4aac381bde312b5d6dcb99c460765da04b4aa6889f7665c16277e7e78690ee08e3e0e40bc837ba96403863e1dee0cf7ca899ae9cb77e871e078d700ed8e4ae2ad047ee22c01941448cf370e640568164ac81e81af1fd8f07d6014831ad9d5e6637a1b0765fcb1406e25244e5b8d48824a7b86aa57e49c5d75469cf6092e320398e2b4738e6a03cb6dc3203559b864defcc146e43d00785459435200d3d8000170405cb8fb9d43ed59a48370c15436880b61f83c576929ec4f338b84ddc524c040070c52b13b9d24192714800932d1ac08de6087af871ab6f0486b25b6cf2e3b5c9041ef096f7cdf15fd5c5b185088cf792b5ce9eefbcedc1244008d1c534b030c88762998d4c4cc1b5b886820a67cefc0baed7b25afc37154cdcc90dd0cb0b3025ac737543fc1048cde4b9a20fad0a29402ed31fd40d026a04f318813e72324a7d9324d325c8a8455adb2f90fbe67b43dff0029cab32162b487e257e555ca4bc683ed455ef1aa08aabe82dd67193cbc4ae4894e96676120b68139ce6718a5b167036ed23c90d89733124754ac8b95c2d90af0c881a83bcc8c2e0ce70e4f8d338f769a19b7dbb93850daf236c02bf1189b9f0ae066a91ddd136006121a50f2313284d51a5607ebdec64d00a8cae210cb3c21eaf40f45ad26caf20844e0990a96db5128df48744a74e74cdaaa862c516e303b6b343048046adaa1f88e5ae039ade3660302eaabb0296d394fbebf0fa37ef3d3a88b26794b8c75980281887d2e07c75ba6118032b454e3959a4cf9678fdf9b406474296fcccd32e291638a08a5cfa33b2e8a211568ee5fe5c18c1f18c4102110d332ec40eb5df374ab6fa60405e9ef928b00a99898eb42c59db25636ef6568ef209a515ac89392f759b604a5ce7c90a59cdc36f66e5cb3327631b2e52788567f292506a096bea8a58663c312954b19a2b787ec21d5ca533aea07a5b739ca1fb6bf1dda747fa094cc4087a6d32d48244229b966ddacbb0b461d9332e7e4fd6b6aaf770809b6fcf8cff335a1b6e26ef784e5b6b4130b136a10dd5a6c78dcd7066f78a06e78a34f34f6224f2b942030edf760e86ca553cc2d62b54c270bf4cc891487dfc9425d24baa3f28ef130454ea0b9fcbc2646b3ef59f6723ac0cb69f47a5ed7b3d1d3a8f3be2787f9364517616e372daec1464612dfc2c468c06526edb747018999461916d09acb9d2b5d20b6bbe612f5203887f49e9b1c978fc04beb8192df45e2076a91298f8d438a55ed5b52a31862d13ecf9419fd68c048b3577285c800747b1fb70f1a4b3ccbb6225560e17e9d9aa2b9b25a6a0bf46a9a4c64d502bd2ef24319d53b7283b8055eae090db8d93f84bfd8d1f1d5f3a81391f709d9610576e0b2ad167ab3ed0c49e942ab4a2ab61d95365961096d681c37a8255114a09e29c0f24568f56ec23863dc4f03a40a130289142c5416844f47149b9501fd438d485420f149092d252209e7250184521290e2a134a8382a058280c14024aec28609c85d31c304a07ec05aade4c144d6a0a513a62300c0f0315a13a52d43df1dcc5090eea80da50bb58940e711e91ba42e54101fd2a8a8337afc37182ba16c5fcce040a88d97f361a0fd5370cc8f572a8f1a8beebd3f3329009cc2cd4f0a280867c13b6c5102584fb3064a98994f5a8578a06c540c1a1b8a3a2524828118a0ee53c0a958a42598d724ae150001411ca874a430150361407148582a1aa505e2806058782298af9dd17af3828a86685b2d8d6eb8b26a1c489c2a42828209407558262a11c280c280c454005a1fc50b887ea63b80f1c2ba8060dd507323deb0354ee45017f8626cc71a0741823967e87d2a08a07a5c32171ce504115504016054c044a26e7a0ee3d5f82d538a13250009494a1fafaf57e407250ee88027efd90a79714b004467dd514580a4a040007bf256bed36a816ea0d3541c55008280c0a81224355508408d577c0c5580328461e457bf37a472f28c35196140f4aa4a2ee715cd2a974a80dd4196a110af83090e4543427e0c90144b9c92854e9d561fd02c5a094a04828010a0005433150290f05b49c35585dcfca227e14d036bca7b166a038545a096f01d52cc5fc2e194a04243b8c770215400da17828311400854321a050281a2ad541dd035ed262f05009d4188a975040b6cb0f1417541cf1fde08a1b0a856aa170a814285305d527e260c80dcadca080cc8e6b9d80d28111e9d24f8e2a985262a474408de38048a830d43ca21485f3a6a11d8a84d2a070282c14e450ccef06b6726201e5818a360a1832573e7636503a90959a7a8152a0121b258293255a2605d40935801aa122282e14028541b1a13a28d245f59d6407fc1a8a2184ea4301fe1a1f5402a2fa54bd97fa59150742dd782811d0261b7813ea54d42e8586d24279283a140d450d45a0b28d022e404c4501ac39b48202f24528c41150f158373127187ade58cb2c440c254b8fccefd18be1bb791e301dab06c8364a270bb406c8235cfecab3a43286acff4b0fb08eeb87506e40f210fe30f45d79002e291c91c4000e03a373c4999671ee146b34f7d6cc536bdaa40c21244d5616589295440a150691720d2bd99c1482f74d8d8649569e29f210a70440df052c810ca134d69c1a6c510ee3d0f2cf55bf5af9ced6170351c0c9f49f16224bcd25e548e2827cb247f3a14f95e4f748dae0ac7eb9378422edf8ece4a50152433393d744ea92ec4c2de3dc2904f2ce552843693d011824d32b2b2432c9829564a530057401d7cbb2d6cf692768f599ee84b3de535c6332c09dc85a879a4a514dedee97aef6e95044c4f307bf175d5fb0629b91a92a737f221b44a64ca5fa37529bc9e70edd3c722f6f316c08f81ee95a299ce2b661a1c5e1feefd489765d9aad67dace086d92c5fb5a822a8076118a60a84a486ce89e453c079da505ee5fa047f133243e700c182eb5effa9a308775529ff6b24061e3121eacbfd3522017a610cebf043f0a2f18e7ad60b216490b22a95c480fe55e7d483a68575433a91fc006468d5f0e2cc8fb39706300d961cb4a4a33e479bcc1ecd40f5366b424bd0b70577eba4481d5d4e0157ec9aadda368e82140ffd1b95431a003903f6fe329f7d7c18cadcd17e21ba715e2bf79653005771b68c506c7c393ec438b8cf03244b020db82d22196df1b2d4311e6556b6512d1c0965555dfc8767ac0e97b1fe3ec40fecb31e0a43639160191b98f7da0dae06326df0a16ad4377209aebee2c8f4eb26c3e89731d59af75a5f85a9c82310c10429992f62998b7fd5de8b6ef68f3a6f847cf9945bd97ec285d5c573e48ad88ca573322e1eb3360f3ccf188e5a290b4f4ad9fe088d49b974ca2358cbec20c2707a9cf55b80a77d61538c90c9c4f8f5668310bbd7bb511974600120a8d2b0f3c975c1f417b8fccf8280f2e39ed117b8752f3691958c1e3210a0c206ed990a6c671bbf665f9229b6727ea76539641ff36789cf131b2fe364119e5c80701761cc2319a7fc461a77bac8d780781236534a374662726e94012cdd3272990ce6bdcdd04c0bcb8313c130d8194d60d445734b6ee1b835fa4c56396604912a4bd771c89f52d854c0fc49f5f33c0d5fc22cd8dc8b7471a75e419e93f025a2e4572e1916ab2e8a45bfa15ab7373536051bb5ae84aa185023186ae8bec41e80bbd565a77742ef4566d1bf2b4d0ee725539d6fa61ae731d4833a23f1ab3d684585ea4631098d3582b50789d1bbaec7cccbfaf06ab5078e8a7d20f293cf3a3622d633fac533c6dde632ba82e6e1fa0c1978a0cc5e8eb3277f566005eb90102968c64acc19ad2015f2dc00050b3c01429510a8b8f1d80172b1208f9f2a4d6d41b61ed293b0db5968dfb182b2e13f98de895cf90d080ca3a7a9a3803d9e1205f048811885cef0ecdf94ef678e22cc55b9e695dbe3a2261601e706ca9753dba1eccdecdf24d34256732c9928a48cd550c0e17fa38446bd6462f31631c3a2e712fa386dcd55a5607fd2252e0a29e0be055d3bb3a45dbcd961a5e04c0dddb9bc466437fa119095db04f40ab51391b691e29ebb13a437e8e9d7fb57b31ac6a8dde832384f56dbfa187f53a2aafd679652acbad0d9017b90cf4cf381f46068b17c80ad87b1bdeb4b254acda337f4fa5bbea2e27df8abc2f1479fdbf255773b3d6c78a6426b39b38f87c020f9f7da074ca6646023891a7327755400b37db0e463c432253536572072964babc600205bee47f5d906114ab642b3c2411719381a8a5f9f97fae5ca4a13e8958457400d495292b0906a33f390a7ecc3d574a339842c1d7d84485fb8bf2d22302300b4b8d8c755143928266878f94de5a3708370adb69a4d97e660c20d2bba533a01ce963e1178761802905e0f502592f380999fc22e096b5def281653d22c4c9b44f04984fc385793dde3f36915a9b526e2ea1d506371d61c29c1e01ed146b1b15fbb248a6c44be240d0cce1115327ece8cee03b4524f1cf62aa9d95341c817c1568ba2f7a82100619d633a82909543899045b18600d9285811be68f33fcf7e4552a04c198a85eb145e361d99470a56936e2bb96539f7c0be614b6a81c7d861af0a6802283a1fecf8e57db5d304ec151b595d95c3da38d48cdb29ae1f944ddab4a6d9880bbd72b1842d56ac26bfbb29e6ff881e1ed0c9e6e61304dcf84306b76fbd291aac7ed99b64d6d9c430e4b67d8f5e52b3f253d8e9eac57db23ccbd83471785c494f5980d2c094aff481d4d909735204ab8090836c184fc2178ccf5704812d38c18521219924c1691efb3765ade214759d11cc680c2b84f2b9603b657e4d3d84e715ffb982b518156210ed79f78b6e4d166110d66ac0a32c1eaa6aaf365c93ae103b0f465d6499084f86a0d0b86ea5080ff9b2249203259af40d4a0cf84559252ee0251123393961cab5269904d88adbe18da72bb050e9d8ae0815933ced2ae52fe79b6b0659ebc478a945ee20d6ddf0a13a4343939b36b54366e281501354c40778b203b5cebd340d451cd1cb7032de304ce290254e0b7595092cb95a0bab292825e14a59c48352cb5679aa4b07651951dda606efb851afd483e5352d0129e40f5c101d111d58e9a3a647afd10d54651d0f79b0dc89af5133fedb1e2ee499b1407c709102cf8c7fcdfe3eac94ea44c15b991ef90ff1985857d4482b6b890ae64f92b7002b8e55bbb3708e880c4cc968ae54bd1629e0fe3b181143af0276637f1b2f51f11e07a856872b26b4994e233728fd0c896ba884c6da979b04517161185df790baf7f857ded591887eb64cf1065342689ac84d6d40573b8eec7e9b824a53bbde1492216cbadc1db42ff88d4c62a58e758534321bb12e7402237c6b54d3669b305e320471788c864f3df66c8155fe44942b40701140edb4209d3a2e024d4b7f364e024c5edf5ad7d49e809889fd1ead6da7f06c3180dfd6c5d40379d00c4e7653d47a8f1b3998788ea1b9e0b1124cc2db4d0350b070d616c1143ca9e9f965c5c60824c277b5afa243a068b226a1b6131af8c39160abd76c0dda2ab18699609ba487d357c13750631c73027acd5030d7139496e2dd18834da59a5aafac6e09b1af23679132152d373af232f8628e88fda6df5e6348895d851ca5a4bcebd417d184a54e78f38f04749bae54b475943891f696f5da7b59eb250a895bb4ad9992a28597e782215abac9bbcddd0bc99eb2f208669915af4ecb2422ab4719dee7a7bd594c7cd99e7f79d13371ae538529e796f7a74b97528dfdccbe3fe724a7ac52868f3e8f4e922182c938dab8a161aa3e16e3c7eec8a4b413a738ec8a0e6f02d65fef0c3c408e846a3480afd359e2337cd6f938d42d5ef55efa6f9c9a2f575327484b4fa2b8ada9753cf94010143135af37fa3ad398d962ca3a5e7f815c2ae2e5b660945102d53018ec62f03b75e944e13d7feb6466aab40e4ecb4e0d82d35297117bad252ed244fc9499eccab16e05ddaf13df64fb69d87db78e18a85879a116f5346e1d45990d16556d1c356bce4fe2c6fecf090d7fd5e14f7827ff09b75949c596492ca829257253ed312f07add55a7e27b8687a2040a91a86f6e78fc16b9e1da8be2208c0e9bb1c332fc6b21d9d029081ca3081461c6ad1f2998d27152e52540cf2162aa045d4fd4e30fda3005822a5e230597c7b621557dced1438d12643b7a59e7c45574b9cf3e8db7fa93945bbd477369f8020cf67e060055503bc8aae6dc719b89f7b868a4dfd3877793b7c530d814f41d89a8c8ee5d809dac870088564c5f581bb9a50a0aba88931135364bdc67ac0900600e35a4e99e69091d6041f40aadd669b5b07fc95d6d2428b4cbd7c50adf2a398d1f344e79f1cb39f79a3df5bae0a15a7167278f832123894b7a34289ddce8abe8c1ef23107af300601b2cd707fcff644c357d1eccb000af628ed655bbd43b1549fd01128a796c7f4a8aef258d252dc6134fa53df5e278d8c8098d5d859ee5ce9c4a677990790f61753c51805e35687a6f6e2ed6a4d34541f31aa5c4e67e14289a909e236fb4099efaea8a7ef128d883ca0b5a054ded235f429be3af69d3b7a3209ced2da1673f286bbeb0e69b6b1e151daa0dd583f2504828060abe83cab61995351516b0ec81baa1c445545fb024ab19ca4951a84653f80f0b5409f5873a50582828140505a1d28da87bb0fdacac40bd500a8abf45f5ddf01cc3018ac9a3744c43deea1d4a41a92fa87bf0a7060cf50b6541812a4a8711312d4841f150a308459bcf23d720a04e18b5499d5009a1fa78cc07fd375407a0802f98b26947d497e626c183357fa173053b83ba97b408110a350215426d040514f0a2eca427e96979a243c9507880d28106ffddda501ca8b9400155dc04f2a540dd4bd97d1a00144414ad14a4a8f54686074d38a81f5407b58102a11c282e145643a12a52b25e08d4f8a222a90705840a41a9a02814ca4201a9d569c182e2a0568a7b1f41cec6004a93229bf31965598f81ef1cea83929f02968f45e98a94d49850b4a530aa4817a8035584aaa1ac1e14f0edfde77e146d0b0f440e050a8028d1a7bc14e2282081761a0618281db233a47871eddb8262a02618d5d72f30e5a85054358a762e150dd4a14e501dea008529aa2fa7eb6b684145739408a036b0ee02f581b2a14828351406554309a03c28020a3614b05c2f7ae45204a09d68771ee3be3e7ba2a92423373db0070724083178720252ac0cc502fee144760c3750b2a4d001c33f3f3f3f3f3f3f3f2ff0bc6dc1362b0a5bd85226f2ea3665f3de94524a29a554068005462384b4c602c20680c3280007029f0c1e0c5e0c3974e0a8c11683183f2846f70c426a76184483069723860f8a7153b5c3439cae57174870e0c8f1014b70e0c80106aa22460f0ce36d5a638550cd6d0c1e7c3e27bb89f661a2bb48fc606237e6e4a1f2c0872e968f9b73874f596235b8389ff0918ba2499b11b22796e8b33f705192f74caaf012a14f076f715529ef51da47a794f8b04561999aa155a5596b99bd95f6673a96d0ee79c4472d4a92a6eb2f61539592c4e5f0418be2e75bf334a531c8963e66518eda316f50f2c6b8199445e98495e65d9b143226f98845416b50ab175edfb7a28e0f5894367827f956a4f9092d31f0f18af28fbf89cf3933b5665d513eb165af94f2d8a283ad286f28b983e7efd71bf5072bca277d492d257eae38ed6315e58df3796327ee4c92f95085d99af655f2ba23966177d2a4f4eb6bb055200408e02bc1472a4a6772c939b4866c13c30f54946fb53da9cbbcdec6a7284731399f464567fbfb618ae229a173ba7e75ca94e58d8f5214a48930515b9b55922c091fa428273159a9def07e8ca224f4cd97940d8dd1ce87280a6f3d424eecbdaf1285a23c2a359ef897e94deaa02809a7f526e7fe5c9bdbc727ccd9365713d93bb132bdcf356a1e4f4c2ba164c7f0e189626cd32499dc98573646c34727ca3173c97af1fb41a64982011b94a3c207278a1d3dff3b9bd69b5bb5f0b189620cdff145dcc5c91e6aa2fc9e6c935013a4783ef9c844617468cf13748e3c810f4c143396f6d0d6fbb693f9b84441ebc44b8dede77b630c3e2c51d09afe5a4b64eae6eba312a5df24df93b2519e8f8cec482fb09111005ce18312c51651da57a1793eb993285f8e94bf3549a298c93fc713dc4795d47f44a224b498feca093f2051feb8a369b4c66ece691d7c3ca214324b8969339c245e868665e2c311c5f8d8e8f1eb3332628a8f4694426a9824e660c2433482f0c188e2bae9df9dd12a4ffbe06311a5f1ff924ba78512ccc187224a3b6a2c6bfce492e3a9c1e685df0f3e12516afd64b2acfb40c49961937aa7f410a5cd135b74d64726e5a8147c18a2982493dd39c9a28f4214f4fe9fa8ca9f244611214a6a94ccb5657d0ca260f1a9df6265b5b3461025c9c3a70e9bc31f8128e9763a3549d43cf80044f1ba939af2e0c1ab3a1f7f2809c26487dedc96e7fa871f1236e98b6509267d28e66a499126e7689d4af8503031b5f43e7b7787d31ecaf9e22628fb204d68480f25f1bbe745bc6ecde7e4a134fa6173c8efa84d62f0507edd6b8fd9aec4ca983b944c4dce76e9de6dafb143398ecebae32a2a37df752867f79b4c22371d0a32a47649a194483df11c8a2f4aebc92d5ad55b9543e935549e096fc6a1a044b76d7ad072ae261cca5df5267f43399f1862abf7c483921b8ae19ae4bed126fabc6d28a8124767c7ae8c3bb221133f61a619aea1bce5a6dae39f546e721f6a28c63229a32655d3508c7255a3624dce248428f84043491233a566d392e6d3c9198a73fae7a727d5c58acc5092df74cd68f34fea97a124aa8611136afe204339b9fa8a38e184c9723fc6e08cd8b9cbce76698ba72bf92697bceb7c88a1a04555345f357b898f3014c3263d4268b715a5c2447c80a1604289a34207f5e30ba55332c449d9f1041d99051f5e287ccd7ba6ea99a04914c747174ae3e22736a76912c6b4c907174a72eecc310962ab57fbc716ca65720c7ba208b550fed8fc68aaa6ac64ed230b1f1f58a081808f2b2ce0c30af551051cc917366850610a0af89042023ea240c306181110f980828c8f27847c3881c68c8f26d098f1c184057c2c41093a443e92f0858de4e30309313e8ea0c3e3c308313e8a20800f2214e0630803f81042003e8290800f2010e0e307340af0e183f259c853f25c8743c71738de6cefa30725131f111ea3721a7cf0a0989a35e38ecc5ea3361ebb2809257f92e4777451dc121e946ca1af81472e8a69374b8811a573aa8c8b929f204caaf0a8c72d4a623dbf624627e143ec618be229b137d3ce8ec9985a94a4d02254fbcc4394092dca37a746f39ba459945634a86831a92c4adfee73aa34e6cc61138b9292d3eb7d1ecf9a1f58144e50aa4fac737890fd15e5ce38a2b4957e86125724bbae7bf3d76e45c1daa44ee277cd8ae269d3a0f155dffde72a4abb73324b9f3acb58a9a22464ee4937a95349532a0aef71e2e6aabf5d4e5051fa0c27d4670db92de95394547ecf5849f32022539453cfcf58975b8a92f67c3a75f690a2f09ed386e739259faaa328a689d19c46aea7b94e14e5fcd4a737b6e9f0084579a38ab0f3a0620f50144bac95b1f43f71c79dd331e7853c519031797a69f79d2875dd99e58cdc8dc9e144726e26f1b4ef9b285b6dba65fe3f6192ae8972988d87926da545da9928668ca80df7f149f38b89b2e8a876b63e2f51b2912597922126d57e9628da9bc6ecbb17911bab443906599ec7a42729cecde141897257084dd25a096ad94fa2ecd935b7e794cdbf614994736ecb3d99838a6a3712454fcab6752ddfd30b89e2c9f1329c2c329bab3ea218cf43499ba7744479c64f2c5525265964641a3b3c8080116c0001322861ecb071c3cc40066964240c93238781814723caed66a3cc54073f25c38882f092e4e826bd069b17e946ba51bc483752075240c3782ca26c6a62d66479be1e8a608f44a80722cee310c5563749be13356b67507a18a2709e41b489a1bef5b5367814a25c77e3f2334abccbb5081e29692f2d1c44c1a4cb94498e5d102531ef093d9723eb9607a2bc27e3565b2bdf641210e568f7fb72728cf79ffb434193d4493ed93a3f14cfee479baa4d95bf0f2579bdc3247dbaaee4e6c187a299ea49659d79b44f121a683a72a4c48b3f015b261e7b288712cda4915dbddcb741e0a1875b81471e4a729de8ed9ff66e27091e501d4553c8f0d89dc71d4a1f93a053c8a81d4b7e6bb07d916aeca081be30ed61871c498f3a1c1d4a72c796aa5fa6065bd9123ce6502a71cd3e6164b4c94935d8e450d40f6ba3ba9471380f3894d4e724ea26cb4cdfeaf18672bcdde68d1d7c33a46e285d5fbd670fa623cec481471b0a6ef28853d2d4c88662bcbdd4d56af924de494772c0630d45afedf5202dacc186f8031e6a289eee7c5692896ab09d471aca7e9fe6c2c4d618b6e12cf040838d9c0b3cce503ecf72ea9b83699c2032818719ca23f4273967b030c99494c1830c4553db64e2c70ba5a311468e1d57028f31946327dd9fb226958a654003c70e31943fdac6f7936fd27e57830da51a8147188a169faead73d2ce7c028652dd67d3b1a3ff8592e7d5f7c9b16dc2425e78946c25d639c977a1e8a22b93fcb9f927e9b950bc12f4a8c8eeb750fc2e8d53b276fa6c1a2d144389257fdcace5b6ce4269be447a149bb150f252999aafdc2b94cc2fdc24bd415e6c6b85727af7369da327cb5fab501af94ca1268918d3930a0599639acd9a7b5227e1144ab922f49b8ebe6b31a18187148a7163126e37a75f6f1885627293cee38d8e85060a2599613efe868790b5793ca1a8e97c336e07f1239a8713caebdfb9b3c4a38cb63c9a5038a183a7cc1831692c087830a1a8d146db7b12ed37c9f1584279b4cc49d3e1342859bbc14309c52f53d731271dc489198f24e08807128a3e5f92b69e93a772aec1a6630637c0c831031d6927f0384249b63e693d36f3069367bd2f3c8ce03cf0284231c6a877256544c38308c5dff97513738b8ec975c26308259d392dddb7a6613b427808a1246dffee9fb8eb22e1118482d29cff79f5727e42878407108a39c969683eaf175d5a8f1f94b45d4e54bed66692a271100d1d666dcdc30725fd7b676da5d37bf735d89c032323347460606484860e2f12101e3d286e1e0d21c2e3e998ba0b544003089800022ee06ff0e041399b8aca3d498ea977ac5de72e0ab71b9e5bbe4388f97451b02a1d3a4a3cc63ab928c8b41a1af4091d64c645b9e4fe31694eb86f32b945316747ffbb098db649b628d90725a679f82449fd602dca26095a671b935c722ad1a298c1c4c6344dcfa238f284b398ed6cb208c9a2b07173728ca11a1727b1289e70a3e3bc29f17444b02898bde6e4e163623253bea2389e4f49f275d576d4150513f98c7766af26795a514a3d1ba5a164585112231a36868ee9f36e56513a17f99bbb2564335745398bd8f0fd18b3a990c15494f3b56f4c327936284145595b664fcacca728864eff6f628a2c394d5190ff50a3d4272da324b314a5ff2034c6cc31294afaca32e357ab4a7e475112ec4d7ba4898a82d1941e4fd2170a4541e9fa189966c24aa3a028e70e272e4cae2a57fd44b9fc9490a74a14cd58f244c994d04ebea14a8a5076a234a2ee4cce1742960972a21cfbf6fe66bfaa3fb7899288cf41ddc7d1d4b3a326742bd97e2af5cb44f1547a7d4ea2f39912c344b14d4ff674ee319420bb44b16c8479e8f91fcf181ba41d33f0a2c60e1b3568a361004b946e44bed73bc8e0a355a2584ace91d125fdae579428471332fbbe841da5348982d796161dfbc4644aa2681bc424a557278ca891288e2a858ae6becd251e40be50fe1c3be72462d76003c38b1c356820b32f6c981a13e0168078a13097a5c524f7185476e400d285f287ce583dbb9a199db901840bc5ab6b4d53e7569fe4806ca1f578f5f0baf46e1db19ecbb87c173fb525102d944451a6397bb24f0d205928c84f8212ce7e4c49b2090b4513253489a9a32b14dc2d74d2192d959963856275a9d3529dee66e32a1464ed548c8ca6e4b01f0815ca9f44e79960029942d994fb7729afda4d5f0ae53d614c129b7d724e2a90282cef3557b76e9a25f3fd3996c56e205028e90eb725d7aae4bc19c8130a267e3815fac49d5056175dfabcd489e1bc269436eff4c9ac291026143bc97ed895d304a600b28482faa78ea5d5a4149303a28492ec59de6a3b8324a19cb5f24fbfbf54f30b8284926dd665d261ffbc24811ca19c4d4bb6d062f2349a18a19ca5afd2aa4cca248d0a528472de9aecf721e420442867d1f462e7268901902194aa9465cc70726aae580805fd0fd2d44bab5b29418250ea3f294f63ac5ebd1b04082559a1565726f64e3cbb00f283e27d105e1f5ec3241182f8a024dbac4ab215dd81f4a098aea7a19f6994977807c283f2e899b61a0d426ee66e1705d1cfe6a6e3ca4aaa580532745172517ebacb45394b4e9dcfc6940519b828af686c9f9e26d5e1b94579c3e726354aeee4bedba294e95edf47666b51d2db1ca3e9abb64c8d1625371193ab7485286316059329c6dbc32e4316e54da5745f3f4531c88845a9454c3ccae6686f2219b0289df4f93a9a12eb9b9b8c5714cd45dfe68d0c5794946d3a490cd1e91964b4a2e49ed4f6c5c6cee9560519ac285cb6c764a29eb03eaea2247f5ae9249c36d3b165a8a2d4d946885ea96c7e2f15c53cbabda24db59598230315a50af78c491a5d1be4bb21c83845e9ca635dab29bb3c4fae0c539404a1fa4be377b94908324a51d43a613b87f90db75f06290a23ffe54ed88a8c393b10648ca268e31f26bafd3aa8900c511473fa1835898cca0845f18350a227eb1cbdfecb0045d9a434f1dc38f113c5b44ee5a7d751836f3c51b8f594677aeededdea4441668893417adf7eb832385112e2acb4c773fd0e4fc6268a55a3dd257a902236265313a573d31ac63d6a09edc944492db5737478fe9c4a30516a4d6af28afe5ca2d4a9046919c44be5c9c8b0445957e39566ff16175d6625ca2f636b62862ed19ecea444594e8635f946ce9c44a973668eea26f1f2e34c4994e73794d8ea2945e73e33122555a74f8e7ac209150d248ac9e2648ee2f6238a952332c8242fc311a572b54eaffa1b517055156d3a9597bc31a2dcefd5277aadd996691125d1d119ff2cb4cf9e88321451dc1639b965ba44144dc88779ef9c46e5c911511819fa4bc98c5b907188f2986ab4bf924b33b788320c5110f396a14d5092a41e23ca284449b2919ef1b22444d932f6ffbf6809d3a24114a49bc99bdc4409a2398228e93c6679aa71198128bd959fff6a79723b6500a2eca5c48ec93e7f7628c9f843e98469bb91f1c8f043496934994d468ba7967d28a9a9b23b53afaecdb80c3e14e4dad589a2a1ccc5a6828c3d9494162d5561aa6963848153197a2895d99be8498c51909187e2ccd789419c6b1e25b604197828071dba57773bc997ff4ec61d4af2893186933b47861dcae6a743c9134d96cca78c3a9474ccd164cf4e22830e2511199d467dd494d1aec126630ec5ff24bdfe733e4ed0caa124671ccf10fa8a437175bcecdc4a666f090ec5ab389d1ff4fb8672dec59d1c5e7b7b536e28e9933732c6bc1a3eb4369494ec34a194c72463ccb1a174379a94a434bf8652754753cd93da4f5743492cb973b8c8d422364a4339c7674ffa3a5a9ed4a2a15892f44dd1d99ca15c72f89e13cacf5483cc503c513c89d7f15f829dca5092ba4c6b12e9b9276cc8500cdff60fbe4193a4248da1d4b59d2fda8aa1b8315d76aecbc350d2f03945ec2c030ca54dd649da2064b68c898c2f14844e25270d6fa9a11e195e2829915de2b376968a25a30b25e5a1a38b8a51ba1e86820c2e946d841077824942999a750ba5ae915a279af426c8d042e142b45fbe9bef4972441959286cde1ee13185300932b0501cf9314d4e733f4c9016645ca1f89ec3956d5677ee8c150a22b398f250929f4972aa5012fa73fcff91e1cad4543074c4690a29ea299457b3acf6f87552af91424949a13e4267eaaf3f0aa5d50d9a2413b47a3e29148af1996a4bfe89257bfe09a537f939bf3e57764f4e28b79f9053e33937ebd78484f73631a1e47effeb7a270923cd25944e7f9c66123ed5c7aa8462c896b4a19e4d4269b764d50d5d8284829df420fc4b76d8701ea1a4eb2f37ea4e2394b59372fb24660ed37d11ca172626939fe029534c8462a8ba87b8bc10b1dd10cada269e5ffa2784c27627993e2e5f69c241289c0ea1744f4e13358e8050f8cee62d9b8492a1df1f14740c9bfb4fda07c5e0a5946062667a5076134dc97691cbe041499bb42fbe1da7aa25bb28f7bece8ae6eeb0a7d74569ff772c4e664f77712e4ac2ee4e858c372e0aefe9e9d1e47e8baf47a76b87756c511a6592895d7ad7a2e059b2694f4a981605a5243b25468ff5ebd12c0eea374c106551527af494e46a23c6a2b05fa5cecd3a8ce71c44589c639f2c32534e315e512cb1c7f3efa6677d4f1262b8a2e07135e9abebf220f318ade818ac28f5fe78fc90711d637c15e59c4e8e41c55655146ddf64cb7b7b12833c1525196192e0b7a98350a2a2985b723e6b87114a7d4e51d01ea6449f0dd79e39539433899fe37bee1cd4a64b5132fdfd277a92cc3c4b8a6252d2aa5a6bcc49cb388a626649ea4df81266ca248ab28c6512c27d3d683c43519264d5566b5e415112133a09a546346f27f944495ef772cfd7149f644f14f443f79820bc189d28d665e8a9501d278a21435f9fa04edff66413c5faf56c26af26dd9b26cabd793366e9ef9ce32813e5cc234a18253c4bd02326caa149a68ed194d2f8da258a9bfbdc8485c6ef972d51ca36ddf8499b54a2243a29ed4a8f23d43c9428f7c85c929ce5bd32bb49943b7318e5a92949944ffa6463f267ba3e51248ade7bb31a350b89f2686811a6a7d442ad8f282619f6fdb64a8e28e993694bace3334ba811a53e25b6d586d737b16684a7a2335d44f1c4a8399850ff1983ac8854ad669d3aad1351ea581e4ce4dc9ccc258828bddde792d496621ca29c77931c4df29f7b098a6188a28e9039369e14a2546922cb5326497eff310851d4e0ffe2b2699289d0200a624ba8ef518fa39ec51084f96fc64498307e06a251e53b621902a25419a388ec127438298df1877de54caa4d3a64c9c7f043152246084bedf39c1a6c17a30fc5d55d6e9859dcb8a5b96bae1f1b133bc80fc7e043c9b39c1893e469be598ab1879250f91e74c9223d1477d3cce68c9f3bedcf4369f6e48d9bb388cb128387c27a9834c983e59cd21dca1fe38970dbd81dbab643d9e663ccd3b3259ab37528c69b0e9364eefbdb930e59c9d4bdff1c0a379ea3f664e5a0598c55ccc8e8b556cd28d1e492d46a3a0ea5b714b9fff81e9a4ee050d2240916269b9244e992371443ef98463329277b891bfc244369b8d69836944a949f1cb3a82741cc865207bf101d54e9f053d750bc92734e77da637b490de5301127eebfa4ff36d350568f9d5f366631d0f08693995bd3a8629ca11cd566abe42b71529c662809cf3339537c31ca50d2294e267dd5c520433974aa884c952777b43bc618ca3b62929c73fe7cda670c31d8488c30149424f24cadcb8ad6135a186280a170a3ed253f4e31bec096cd557ec7eb16c30b4593255bc2eded85185d28e789f7b9fb6a262a1b1961440c2e94cacb3baabe9d85a9636ca118b2a4bee7248ea1855249a624412e565468c79b4631b250de16257bac667c743c31b050d26e9258f7b75ed680185728e6b0994f940e32f54c6258a1984c990cde7756a1acb98492af9f4baa53e3c92006150a42a8dd935b62a6ffa631a6e05f783ee1779248a16067926442d865d694a350aedbd82ac94a4ed50485c229bd7b9d3b339c376ed88801f620c6134ae29c184dc912fd20a7bb410c271483f856d66f4847138ae7ff9a3b870d9e8493098513b4798c2bca1b333049424b28aae6d4efebf969e65a183194500ed2844c326c94a7ca8883184928e9c9c613b4bd55735368831848288a88ae9cde6c92b3c311e308c590f2259fcac4644a3742a994542ae276735e534f478c22945c6f3cfe83aeae5012a1782384cc23d44e36874937be6fc41842d14d12afdb1b474c73f72286104a9b93cef14a0a0ff2e4bc2008e5f89c7973f22e693aa363611003082579f3e72cbde9481ba5de467c694e17ca49a7103949479446b950ba0e2d1963f292771d36da74dcb0b163060e10d942f9939ee6249a4a12056820a28562aac949dc468ecce9ce4249aa90f9a5197b37c7582867eae9b0b626c7daf20ae50e6b9dd4a42695ff5be14acf954bd3ba7bd50c72c32a946eabc47ffed3399550a1aced26327c7fa650ee346b72f8d448a1a04d7c3fbdd255d1a14814ca721b4acc3a08d18c43a13036235f236493c62bf284920cef9239c89c2fe389130a2ef6be75ff19f763449a502e6f4f2df798cc348709c5cb5ce7a72bcd52c72594e450629a7b0d4fa289514249aff30935fdf897b948120a4ae7f9acfba5de4716414241b50825e652f2224728e6f18cf13137c67b2b62844db74bce4e6cb75cf5f4fc735a7fc8ba458a50bad5ee241ecb800811cabb49d4d9fdd1ef71141942f9f336bc33496b828e611aa62242e0ae3bcd3575ccb5ebc35e283d42224128ef08a5277ff3a3230242f1e54f68d9d56bb0991c33d86174e4edf8c2068e0fac99210d911f94735266f27e624510f141f1ba4e378c7811651f911e1464ffa7b6938d31453d32827610e141417c5e6e8fc63cd14cc82e4a17fa2faea4f35835d24539c642eef45a673521b928989f68e6a7eea574878b82afa96cf19d9bf5925b244753f229ab726d51d8d3aff9049dd49d94d5a274bf7127a6ba79d3295a94eb4e8e5776f42e9b675150bfcd4cc296b2b063cf754dd776e5ad2dc3946f3a9da3e4d46351ecf02a5f1f7a766f9a542302382c5053088145c1545a96183637c97ee515425e51d07bc29c4cdf1e26d3ae28a6cc2686d2dd0b21d3ad2858085582865535e14fac287fbda9fb953969c55b45399fdc6db2d99f762f5520441505fdb14a6ec80dde31c3869054942d3e5f975882810ea480068e1054146f378406258c8bde1ee19840e721e414a5d3204784a84ebd6914628a62d0d0719390c6990d300c0d2f74981a3407424a5190316689e68e93a2fce3f9437309fa356821a328655b7d96d62e3d9328d8b2757b17b1b78c1f694abf9de4261a8a82d83cf1a25386f4d98480a2e8f6b9514eba219f2807f51bcbf8d9cf7f433c513825db58e8d589a228ddf1c2a385d637cc1739d20e3e1d3a72cc60023ad28e3024e09c289d980ea31fc2f2541bb209e77b444982aa93261d219ad845c4dcdddcab624f3b339e52675d6562428799b8104cb49fa771f566f2a199c5d4c90d9b904b1484963e379974d4dd6e89c26997151d3d5c4b682a51facc0f639bd3ddf4a2045ea376a59db6e2597726089defe4924bc49c4461b38e1e25ba9960298648a2983b6927ef0f25bea190481444ce448426314e928443205178d33cb2ac67d466d203421e51927e55367237e7f02347946466d9664d0d25c81ed28882550665a22991ff358730a2f46153f3da5d791e6de6224a82f8dfa4398bac0e934c4594e4d4e8662685d02e32c44414534d29314676f21d250f4144d9e3be8a9efe1384ed87286e6c91351eb5f32879288418a2e8e16cc65c733734ee3d08294451efd7a4dfa87949720851ea2d492b7be389ed1e32886297b97ffac6d4b65510c5ffe0b91a36dc64dc8404a224dc2895fb61bbb64a2180289a2475c6f0491bf28782d6cff7c850881f4ae2e59bdd270fe23f8dd887d2951c4df521848c5b08e14349a84ce359478ae9b4410cd94341c6c9ae351e0fd143e974ba3399531f9287921cef0f5fafadac31082178285b85759b7d521b97ef501c5bbf19134fd212447628c8ad8ce727ed4e50e23a94b4aa4eae1b910ee5340bd99a4365e376e6500cbb7fa55743cfcc54851039947aee73ebe383ccd81421240ec552a35ac2dd835e4fa9100287828fc6b0fd9341dca76f28c7125fed4b7e4f9276433983aa3a21bdfb34d73614436df63e8ecc6ca76243d9ebac640de50cb7de4f520809216a2846d3bbfa1bd31e084943494c323267ff0d32ce090d651f194deabc4975533e4349c7e8e9a7d733611d33944346e8a09d3a34fe2a0326baeed615ef5a756d69397b4227133c870c8c503a68faeb3b8662923ec6a4c397ca18436228ba5ca8a75f3f0cc5f864f287e9f89a660386d2cc082d3ae612f24ed5215f28f868a8fb8ad80be5eb70e25ec649178af1c43d974cd326b5e642c98496933b7c49a6e4e5215b287b92844fdbd2d142b1543c8cac7337f9b31d21240b059d7a9ba945988ca2c5427964ee1979671a33ef902b94846d9518647718930bb142d1d48fc611b2e4fbdd2155287aa8f8dc768d9b5e542a94c4fca3b3bccee6ff35640ae5f64cba721fe5cdd31029943cc92685bedb8444a1f4254f9fb8957d1fc41028145bdbb36d4f5a71cd439e50ba93df395274520e1b14b04288134a2174468dbb18597aa24d086942a9c47aebad924129796d258430a1a083f0ccae494f67bd6a30762005344642965094b70cfa4d946ab0d5d031831b896620440905d3281a54e98b9067d6607b018e1b189284620e6d25c9d1d4ebc48cf922318e0d20a1abb84bd52f57ed7477ed2f4943ebc4d231492f78018e0d8081a30323232fc07143438e50eeffd5d839978c50104a2893791aa54048118aa976763f9c44285d8fce12ad5dad2f838290211473e3ee663511f2632e849228b14b84ce0b13f50e098297d662b1dae6d696997dd2768a7febec307608100a9a3a8e0739579710f283c29d3ce19d94185d4cea5808f14149caf8892729f9fe39ec84901e94048df13eee94986ccf85101e944bfe19fb5d0bfb18d500d94531ae96c6ec989500a28bb2c91a6312dd7cb6f4e7b928e620aea683c7677bb868f33ec5f36b64f4d23d5433eb6f79265121f3050e1b5ed8b8e120b728f5ba8c959c45ce44df16a593ac2499eb37b52849aac3549c8b12ed3b4600a145498999361ad24e3a4c8d0f300d0b03641685b5d2ef41b72a8ba299bc994e9ef10451e6485f00894531c86a198fd1438735018145b92f45ba8f277ded5f511a4de12b52abbd37c334cc15c5934ff55e061311bc0e1b5e24482b9283501bc62cfc1c660616b8619c15259d4d7c338d56e357ea551494524d22a61642d3abc1a68ad297d00ee2754c7335a928cdff06add9e52276b20e4050518c614ed0a15a66f7740a198098c234ab16d5dcd1b6b85c93c4b04b51da68723cb9c40a1dae2245e14569937f4209c828ca697e3e5dc7b5d4b841445112a37c8d4932588bce86a2bbbd31cb5cbd7299570f26e79b4b0ba52350147e4bf66cd289cdcee5274aa2bbaedc98209e287612b78474fb309e459d28a77bc6f5bb5b838dc66e200212f0c0062cb03b6c90767c81e369ac71a2248fdcb425c9de0e1d386200b28962abfa08796f16ba7e4701441305fdbc0a694af85cf24787b9310197024826caaaa941296974734991f9c0c8080d03c144714cb579f6a46aa1f49728766e36fdf3b6f145098825caf21faae4173d8254a2f056f623fcd449aa94400184124513e166268a6ab609029944297694304ace410b2347f7054024515077214a9b9cba9478a11bc946054646746c183bce0b209128e7b71e53e9b1a4062151cc2155554598f586e811e570be994aeca9d37103e288f2094235ddba8c4e7dd2888289cd7ca8944bdb9f11c538cfb6d1d3f6dcf9220a4a6d9d3eabd5543a2ba218375ed207b9b81373798928f9865c784751d2c37a8a8882cbefe92679276812563d4471ecd3a49b983a8f980d51d87e8d9a4449a96a21ca2574ceb61d97ee7a6a1a4008512c51212343dc54add783288c69d90d76662686ba0451928412f79817d3a9633410c5922b931fa46f8edf8028aa9bdce3eb39893f1dd53f946563efe951e29d0e0dc78e2f30303212468e1d3a6cbc20e5f001881f4a26eb4e12a4cb75ec3fad046d3901903e144dcf4ede58e7e7beedf8c22c1f8af1393bb2346934181949a301eea1186bbc44d9ca9247da1cb0014604d468a046036403881e4aaf3d5f1ecaa2745e7cf0d4776e0282874c7fd32c766d903b94ab4c099ff1be1e80d8a118f79d444d3491ef79903a94abc64e7692322074e0e4ea8469de8f39d3207328a8e692b9a4aa92ab1518618ce0032323378ed9910307188d0510391484899afbe1dbffe4d1c8881840e2509017699a31ac9c5af10361ec0863c75d01040e9ccc6c6db8a86dd6568d8bfe390b4d2f1d1840de50d48da3f9c4dea83e017143614ebe927d93aba1d394e30309d286d2ee954ceb7e15669240d850f224497afc3f644cf2bd86c27ada5bdf2e13b6936a28b9ade9f4df2bf9b408928672492174dce42428b184d150ececb6724f7286527a68f5384de2a8d640cc50fa5c9da579739224e945014819b22e7db396cb9ccdcab60ab91b435c329483c9c5472f41a6f0fc1709640cc53e416ace4932e95d8f183c133911f3b894176ffd2cda774be82409da3094e48866feb79e3eb1beb091e80002868214036628f69a9cfeecb1392f4349cbde079bf1246a92a12c3ac58927b66994c6504cf5d9caf327f9491443f16343ff6a34f9d10b43418c8d7f55cac61018ca416b9650d1f00bc5603ac953ee412f143c6e4c6b0d73d5a42e14d46dafe38bd5671217ca9fef9318634cd361da42e1c2c7e563490b2541ef62ef04cd12cf42d13c2613a733c81863a1709acf4b26a5f55d7485525f27dd21f3562886fb15b59a5385d2fc86aef6b0a2939c0aa577d7ef6aaf3f319f423183b4d8183a278572e5ca565aefd6b517859286bed3f4f3db3f0f0ae535d1d3bfc94dd7dc3da1a04c18ed9dec5ef3764e28af2641fdf5f3a6d8ae09c9f5b16342514fcc6e6727f163b484a2897a8dd149899e7f2594e47ad73f3d6523f3492869f66bcfa5ba44ed91500ca54fddca8b34217384f249a2e77f527c148d110a4a66f9bd986c21572b42396fdde9188412b755234271e4372971b65f42694328886c759bc999108ad19424e61c93582a0a42b1e37f480f72d63c09100abe3a3af4930e17273f28284126997d261f144f932e31714c9f7f801e9484cf415975d4fdf7003c28675f3de9a4162bf377c1ca09be2e8a1bbdb54a2e4974d4e4a2e8d77165548e8b92e49bb673ee5b9462f49489a2db99c4b628a9eed30fd98d49676b5114dba0e46aac6d132d8afd7f2f22c3f8c8cca264c266917d22d4c9aa2c4a4a12d3dcd13fbe83b128888955af76b028fe7749ba47af28ae49b37152349c1c734539c878b092561453ad4e6a69062bca412693ac24c964c62acaf3a394789a255baaafc1965e9003477268e7aa2856f95baf9bb8561eeb9a918a927a9cbf37cb74062a0a329c1275cf2c3d8612d55394b3e77ce2b57ae9983e39c314a5971367e489210c600e1b34c0d091368719a5289a28414613a91e834e328314a59149c75c7d255c09f3288aa73c6cfcd56d12bc1545418ef9ffa6e7a431d65014c7d3d9fedec8e634ce0045d9cc63da8d267fa29c6378537b0f2f08333c5134b9d94f9cce933c9c086146274a5ba7a93fd5a91739274ae2c27ffb45661345cd6a66829c68a2549e644e63b9759d431d99289fa432c79c3b87cc66c244c13c9a70728e9de424d34b94cfe478d0a9d34dc7184b143fee97246e095fb2a54a1474fc28254af2e329a52d6f6338d3248a26a8d52cfa45a937257692a8744c4aaedff845c22caf8c8bafcbcc6640a2a0252725ba4b660d763fa2ec26423f9d876e80e10506c290800e1de6033f32628e28ca261d7f54f56473dc88f2bce64d723849ce378d11054b39f94435661125f5257ccc3e1ddbeeafc1b68a289d34e55b5b42a912ce1a6c896846224aeae733c70fa74f4bfbc00c4494a418956d2b0b59265a830d2fd9c8b1230f51ee12f22491bab6316d88a249db498d495b0d362fb610659fdb9884931673926c924e8882892935c9f9bad2cfe54114c634adf4478931ef331a36c051831d2d88e27a8ac664258fb867f502512cafdd76ed58191b5483adec055fd8a8a1c3d4600ecc00444136a3ad7fd49c4cbcf50fe530a3da4d78925525a9cc0f255b2f0ddd1dcdf2ad1a6cfe3a6c78917d28a6d298a64c30d9b0976d0433f85012a72709a34e7c2891ead843a9af3bc913e4c44c92e9a19877264f4306714f661e0a3fa7a49244d66c4c8235180f25bd262664b0f90e2571691ee5e490f13b5866d8a1a03bed9efe2872b3dd3a149387cf5869a1379ea543d94af3ccc3c8e8756a0fcc9843314bcdc46fbad5242b95435937e7e6f2f7d62c9b068d2f72dc0566c4a16876b2ac7e9998736f376ed8d0c0c8080e1b34c0d0a170405fd8a8c11733a0f1858d17a42380196f28ac26a1ddcdca33fcc80da512a49669d28c2d199cd106adb3765c4ee3ebf6b624e126edb406931a2c81196c28789589b44c4d621499196b280793ba57b23769c56723497206374c093050810a8c8cdc0c359474d07792c54913a1d58c3494eeb46c940b1d0dbc75a8c766b85ab57bacdc76c7757bdb93d367f0247d628dd68f3135c30c5adc985e6dc7b7cf9aa0d3e9b01b3c2e98518672d75e5dedc9e1248d93a19449db9fd01babc3555a983186624c279949426aa8927a1d4498218682b5bb991c113a97492e0cc5d1e26d2eff926006184aff497cc6541d4d27fe4241cd49f7dab02e1af6195e2828c9afb232fc6a841d82195d2807d5a964f3dfe7b26770a15c6a25535dfd334fce164ab277ed33b4a0cb8abba95d768cf7e954219394a1429784195928799824a704b5f179923768243070bc0eb45481195828d6edc94db6a13e06255728867cd3d1f74c073193154ebad7375528099d33a375cee0c1cc195428c694efcdb7dda227c98c29b81a5aaeefaab55b3bf7c17474c96ef26d7c86148a27fb397614e5aaa504258e4229e7347f6dc93de97fa6614607faa43b980185c2a726a957a3abf5d68c2794fb241bfdb20e223c35c3090539654a12219e0d1c25581ad684921457ba69b1a9c10c2614e3ab986cb9bed924c33037d2054646900d528d194b28cb891f19b92e66b5cd5082b7367abbba79e99d1b9b1f4f3213bb8327abd28b1949288acc7ddf70531eaa9c8184820eb293f5f999f84e728472ecbc61da24f1f58c5092bb214e47e69358a72214d53d93189359c2dc3e8308e5b23053da3c4fd02447c28c2194e4e851afee5763fe720f6608a15c32cec5fe974128690f2243892598b7d84028c924aeae2773db2fd98c1f9447274bab0f194684b855878ec6b1038c3072fc3260860f4aa263b29b4fc2f95a23233b8c173970243474e8483968b4ea408ec18c1ea04e77f860ab5bff60060f4a2f2ae36dcef50329c72e0a57c29bce6c2ab77545745136493e88589b45725166aa5c6b68bddcaa665372cc5662a5082e8a26863ebd9fe46f518e6de99a525f933cdd16a5d09251ebf67115e25a94574bd2923eda9c7f9a0722b42826af3c99519e26671f0922b32829b9d03a32f20e1c37443032c2344cc70e1c3774b489c8a27842a9c7c660bde3ab482c4a4a8c7bf8acb2af994404169e7610defb723a2bf20a342e652f3fb52edc56664f4e3b3ff94950fe8bb8a2241b94924508a519b3482b4a6a4cecb4666445e916ab57f7f2221f931c4a64ab2849d2fce6cfd19f04d1c006420a789f79aaa2e8663da6c4841505915494f6ab75115494ee4deba67dfe5a6e4e5150326da9dde7a62896bb8949d4a41a4e264b5132bd762f6f9d14056d576272e3f7f9dca3286bda98247b6ca2286d12be2773cc9cb39484a2a0325567aab7cd730b14bda5cee88e8bc5dd9bcdc9cb9c7429ad41fd094d90499df613634646aa20e289621a159367279cccdd1736d01733c0c197db9d28996bad7f4cd169e18d8c340d4e944da59670ebe3043651925475bdf66755359188268a79afc494f5fd8a934cd808264c2f6123229628772aa953995279caf42295289a4a134294d49121935062d35ae6159b9426c1ea6668ea55c678d5e59ba45398364d566e9244a9475b7cadf2b2486ca1646e0689e298c91c79ab0aad311d11794449b0f592e4ef501131d760a3618354832d8788234ac2df8852ba57b33b74b84823ca364ae77ebc0e8d18a40406da17228c288bee1863c7cdacd9fd46c0082c605208d2dbb82182911a228b28868a5f8f6b9fbb9583e1858d91111c364646bec0a1030634b405228a280977dff91f5293683d1105214bd89f0e6f15999374208b20a2fc23deff74fa523a260f510ef11ad373d0a618182fca54c410ec5dadd88a58ebee7ed7bc0793ae329a8a931a6c5ee0d8a16306bf35102944c1840d1354644efef2498108218a394a64e4751c573f890ca2bc1bec4e7e9f2cca4f228228a63c65db1b736ab0e1b07127120816880022edb091c3047f285b9727a9276ea3a13581881f8a9a42555ee329d287a275566cd49817e1c36154ec9c52377e61e36c0fe5fa2469773ea9ea7ee481f485074620a287e28aa692bb9392c29987b28a2c413de7524ad398be2081081e4aba2fe2624d4c3207e51d4a97fdf9a5ea59262f0c88d8a12c3a5c7f971426326d7528dec8ee133d4b9f7adae9508cdf4e72963d615f631b7328a717d95a1dda47ea470e0535cd7f259e395ddc3814f4ff860eafed24281d0e997ab4366320f286f2266d720c278e0803113794a35f87f7283f32657403078ea37a43a40de5f513446689d85012a2836937c14e67c3226b285a6813b3848786306d0944d480756dd5c8d957b5876946d951557991034705bcc88123e11c3bc04843e1c62d94dca413414341094aee5eeb3db34e32226728fbfd8e8e8977a208cf0c6ea41c60708b98a13832c9b2a14ffa128498460dd2176b1210294339469ab9072ff17a823576980f648d1d068c0c63c7a50819cad5ceddd4f48e11d797b992c3acb16387c8180aaa969fd4c888a178aa36dfc9d341aafe226128e68b29b935f747100143e9a450e246448928b143912f94637e9334447cd7c639125b8ed41d10f142f9248cf8a8342ee622e25024120704c280301406087817631308001840200bc582c1603c8fa4e5071400045d2c1e382a28202222141610200e0a05e26018100687c2603020180a07c3a07060889848a13cc84a465924120ed7ceb69ea9b4098b722d7dd09432be77f5e1807c0cf37d9cabd472e45825b01d523c9f625f3015d69ec37630284183e3934398af55e45ba2c7890e55515dc24dcd41c9f84d1712d2c96bd9bff8e486f9f87580baa23820464244a92aa08ff6605deb60c245f6e102f245f86bb530e918290e8e6173112a6c90ec31612c66379e4f79b63327ad8dfeffe8a6d3caf1eb006545814348321f473e05797a7aaf45c4eee9748072549d72bf545b9de82a852bad73119274e0bdcd53daf917e5dad18b328b5014ab95d61e3a8254a13a29aaeb9e35d053ee0e780dce9cc2202c11ae2d7839e9cff3ea112d7fed0fceafd7c90fa108b3db0353144633c05308fa5226915d5774e21ccce31292bee1756da22ac3e93fc8b373d89970c601c2aab0266420fce15b02aaa558ffe3f174a17f8ebbcd1f56ef873933a82c69963386035ef8c846391bb4dbcc6abe3441d2ce5771c476370a6d6f79bb365bd4b0927fdb1876b1577ae1f46a73b69b320fcabbc0cb36e4187cda4229b0259ff42178e4651e0b0f7145aea331f45a2a46c09e9092a6666c3d523cadb41dcde8ee927cd38d04aa6ae07310a2878a4c6d074d2b08e5d82a0a3f3a5d4f594cfe2c8f439ed132eec9052460439f23106b1b386904f6de602d2aae1b7f959ba5c9adbd8acf26738e272adcdbc23ca535de78a5aa8780edab7a37057da4bfd9e40a3fb22259d3fb0e7ba4139275ad4f3fa5272ec2572dfe1a3c2fa1872f6c88bf00f377fcb2dabc820bae4a30c6b880dd6fed190455f501db1421240fab0ba7fbce9d59800ff6f4819111f35be8aae8d0794f410b7e88dba392d6820eba407a34b797c5c7ace324961cc0e4acc67bd0f66c3e924107ff53ffef850822ebf57c21383ca3bd40bd5f8c6eebb46c4795512fd0e2a2da14154d0c57cd84e761a410f7cc68f5445d998e8973b6e4ae0330d0e72f0877a15bcc046ceb0b35428302e7138eafa5687a8bf9f1b5034ba1c0906d77ea591c75a1132c7c9155b1096b2a6e726914721e60b31ede0c2eb1a7774ecb684256dacb05c791aa73819be13f0cd744c422b8146168522b818ba6ab5c8c52ad333d888e2d54158987e84544d36c760abd0d5c16a1a27e949e422b02eb91ce192456d448426eaf1c652bf8ec0bc3d39442a53b62435662298cd421099c3930450970c4d62a37d460c25c512568d94213521e0c9bd41165ad133c04cf4102419482d6eaa4523b25caec01e398d97027c0890aa113f29716c046db743c285addacc176277816c63a73ee486f346ac2b7d6428e53084937d8d6a68365b82105ec37914bdb042becf5154d92646d13885f9ff1fece955ded89c7265b950ad2774d2310476e678984e0562f731f56913a9919ac2ad81c57d8725694c04818e64c54303e1abadc864f0d694d912894969298e8f1bc078876677518e38327911d46b9c559e6602801ca4181515d810ac477eb7ee99ef8ff24c2380c3f76c3b1e19e1f3e3434018683fb0054e9c6ae238f69728174e1188d2a6bb46ae187780b987abd86c3271b9502660f34b9043a6b6092b0b0762910ae706332b35abb588a0260cd62ff5ca4f1cf210758749902e447e0e1b6de7d0d8d2fb4df7bd98cbb8fab0dbb7aedb3178abeafebf6ebbd3080a146a46111b19e099e4c2d83de232888aa25f6501121a04a0c94fd9b9adcf151bfe7fe20dc2c908696fafd38f7e2ab03e2635cb81786eaa225be282a45741dc820ef176a50b6dd1f8cf99cae3a41901f3c1f3580b6212039d7180bdfab291cf6eeb430b183e69924ec706c148f92aa6e038b3cb76c18a18a5797fd959a493eafcddc0971837f22089ce69bf88038170acecbdf5a8105706b1ddc15028571cb4c9b09bd01d90f384c067e78151fd87db988dd50a3493a1a898ef43a621f1053d427e8b20198717adf2f7700407008a0b8be44be5cc54afb8fa43f1ed028b253f4c52745d5e3050be6f8be07afd12a011e07c2383614bfece97e44e624b5e60bcab5756867fd2311f1db8804c87d5bdd1f846edf8396b2a324ce82774f1bdbb8772fa3da6376eca4c7a5c5b91b0e2d1038321eda4b598d45c2afbc285f48124a2d2687647822aaf11fc23a55b54501c4fe13b8beb0f390676a09687ae6f9f890ce357359d51fc5b1bb1ef3bf8314ec4115336adca10e3fb7689ee97a9203915eb59a3b0c70c41341b34fda31831d8470875101fb1f3c819c9e88f8af550aa36d5e20b54a78d21999a066502a6791f0ea67564afd6205a578b00927fcf6e07e9a45583d9f981a6d640e20ba6a99b9a45723051a9bf7b85becfffb0f652525a046181dd930370e0debc158de1387e6b738510219a3b331ca5905eee3948fed9bbb8d6662926a0e492dfc8c9a92b30a33d9c05d76ff32e38a48bc0041bc960330881d04f7229a401c3a6da64863748dc1f40ede9c25997558b4cb268033eb420264806441d030c47235f008135c20db999d028aa38c8df7f1f9b5c9304c38518c52be922d56cebbdd281ad2803a81eecb38bb2c025d0be123366106a2c82cd8ceda11e7e7375fcfa93634240c41506f9b1a2947de44a04d2ebaca6a11f930412771d67a241aa388a9db2732da1974606872240260881329cf23ea6233b923acab2565438fa42704a9a873f759fe845a6e6c96cb53692fc526fa20b22a33667c5777c71a91ea814e1fbe83e324902d54713dd6a9cb15ed8443b77525f75ce922be2b61902cc3ea5682932f925ec05882610e4434a0d2a8a3cbf7874bd0f2d95dde3c9b68322560576ba8c40166fa6baae98e0eb6d211705d25f0a3285a53bd212a7e5d5299396fd8200066a88ca9d6cc66ec4a43f49e8ec9c2465040ab5ba90f346a08230b0a02ee10379d2ecce245d85c49e75d62bf865588c021c408206bb18cd67742306b9d44c786b6ad9a99da2b319f78d19a3da02b5ea597e208c0020521653a050ed39ba49b24e084e4c98d52ab0c946086387b36b320f8140963fd9477b9271a60323a7f8928c5fb44abb6a62f9998b0f672fc7f28627ab761b6258095861a9e620d50dec416d2fd0b8f0d81c8fc794364232ec93f557adda65681487724ac6445c56dab03e584a755bc1242ae5210aa77c9ccc16250c612ff66f1efe3af262d5c9ee6b89bc62d99dd7686e370052f1c6da49e500a5cf9aa90c7dbc2c65dce2f74e2a754974b01bf8dcc066429ec044494f8c7332fcde675a8ab36b622fddcfec0989740812a3456443a70a274ec6abd01e9aa64d0246915b93443e7a516db548a0a2071b1fae220d337c0a444ecfd9769c7a03c251839300210f2aa621706a21d110f8139241fa0ded38f88fdeba549e0ac8d683fc8921fd3342a91ae0c1b30a15dddd32adcfef6497d264564c6b595143ae972033c71ccec92f90c4f09769a24d6d225dd4b958ceb044db512e870b73c84d5aea636e98926b072544c8e64582bb76a2c259aeae4d0674c85530cd021b234957de14da2998278baf587b19fb987909014791e133a5bc03170fb1e474b1a8c12b4794f0038b912c5883b3190ef07cc1ad14b1f86989bb8cf245c7596705e52ca4bd0498014f880714173b3610d5279110895611f9a8dcd3b653012d0523d411993e164a11401285bbe007224d9cc86aa01763f3a4ee02a13864984482f61050b23f57f15028eee973784aa03b4a6a6f6468e3e9c03f26ae2c390946c736dc50832dbaf9740c59f954552c497a61784629b5c09decbcb91cc7005527bb38144443a2019e46e0729ce956ce4f7b9c56a012716183798de658daf4f82f9337260ad0f8f32805b2d88190a19655b74ee43a6d8af6aecd71ad32f5d0dc2312da6a2d446623c01f687bf9fe0c09a73f3bc583d22a4ee9d97eb78c8e18d44d8113173b553a8f9ac56407a39b49d394129fffa20a25953e17643c6d9d97aa7a0ee6b9c23aec54e3a842e96ecb6512c8b6c43dcacb4a03792d555018a38ffcd3c915b98904675e09d177cfe7590b2e469b16755fcd8896c462b8038bfe94e3668f4a8d0a1fc44ea95a2009c0e0e5846df663fe1eb0d1374800b4f0c87321c7905aa5c61be0c0a934206ed412a4e9b4d08ee7215b933fe0807d8b95e174297d4787d26d12df268d20a1500d787d2080601e8a2c1a7c7a57c889d7d7b08b71248825eb0297e524ca3f6d5935ce4749f8ed09c52718f8008bdd8a5581308e6638d4b97bcc8e10401d97257730998c366d829fe96f82b339854553f430ad419f89742fe06115f757d742bf6d3ae630905a9106d2addf5da2a9dff21e49962f4b37b3477e2308914605bdf7185733cc0ec50cf9b9c02d55c9e520c07ff8ad4941885975bdde0d48e9aa6539187464c02db071d575a9f6a95a9d25301e12cf88d4141241099ff670d1d1a5b1d152ccd1f1c0ad9ec0d72ab68cf6b53244f00fa33140059409d4a1f1ac604dcdda0ee6cde7259f8ae32255209c19b59c8d8b24349e02e092a48427192a2c3b8e2ec6b5a1a8acff40ad186f4a7026b17955db315090237b0c5fa68538b5c7c313b91cac115d0a76855eefa8ba84ab85ac03be9e1220f3942b518785f23ed330f913ab13bff1a092c85942334e69c2edcd117d49b785d935340fe60099e648edc43d3b17c54add1097f678362c7e2dc615c513a4942bf4e65e57f6105d6b0b44e8a7c34d6f15f710be6ebf3019b7a4af447fc6e77fb174e2b9a7c922b6b7b391ac14c344db53ea90a0e47459a134052c822d2d853652d1a2a26efda3bd70f5db50934ff96480df5e3317852ae1e2866bfd5920911dd369c33067c9065c6569c0c750f147809f6e05c09cc2d17ea79c19491443f476133d22b88db39235966be720f040a30c0aebb7124119069c7acafd02338711f1342e2f16f8a531bbc2af5fa58de49ecfeb1d4a2fb59e785ac7a076627a8a83406592d4e8156304be368b9d1a5d4d566d18a4a24bd8c520886a69b4149b7ac08215356b8db0eb952aedeca6255f2263cdd8694855eafbbd2a20b2e7b42b418a7828ae37b5698b9afc35cf00d28496955768a3d9d9450cdd6ef514f9bc781270c635c8f0e1d37c75b2f41c35c218b55147feb49a695d9528a2d406263919471a163ee93f1a0b43a245bc676a5d09f2d5171de63844ea1381dee42c152fd26d2d858ec2b4ab34f1d5713ad35419169170656e90a1ae2e2feee208c9a2063083174c9222ee2e33ed0210858992c417266d729ab583d5288093b29a1e530a7f3a95626cabbe8b010f694d01ac3afa60f54fb3222292c6660f3011116ee96720aba018bb695ee373c9bb60dd6671599078db77bbcd3f365c3ce2b88ef01b89be8568c17b384f0ae388799d0489974679e365d304342a3f278249f024d7715b26292905070c1a8e4d23f47d1f51f15a2671c6c466c2003b3af8daca0eb678c78d15c70f0ab61e967e6a7d8b35addfc2f2f363887cb9156ff632e790e6e2fa1d422f6135bd63cb0318bd1a63df3552e40793db508f7fa0ecada2ff7cf0825ea9980158aeada8600406a292f7db5d05719a1d3c42629840424785f37fc9c9417e82135449260ed2c3688fdf1e08958a4e764e30b0b05a5ca6a480eed3e1a2d78aed03383e2f07aa6976faa70bb4ed36a92b138890662f8844946ec6d23efba0689c00a2853641981fccf52780fba03a24fd4dcf40011e2353a88ced001ca2cc6058eb72672629474bc5d267c3aeb615cf5f1521607192fada480a670e2868364996802e0559e1ad0b07b2a05601a11fce3d27d095e99eee507734ad6dc2cea8e56769768726801fc13f3d01c40c1212efe19eaac4990350275eac36100a511ed82a60ca88e3bf731a257f0b66f10390c6b067a43635b6fbbd7308b2dda078189359c70c9bcefba6c0d6b0489c2490248ac9e053ed00289aa6ce618d7160958da7f66f9fe59766e6cf246f585e583082a51cf29a524847386ee83d3436a13c49f0b6484932cf2e4a69957f41f218333ce0dc5c905208dc0cba181eb608aa23f9fcf07893f8cc9434f7d163862e1aacc0f79855f20f122f998a2f634c1a88e0126c79e4b883135c41b4af65164292839e200d83f73ff1505e85a202fd9401cc2451e036195ff860ac012409911044b5a13673bfdb10716b10300f758ddf91d9eaed1287a9f94aae6b54465f3d35468b0d78d89257a36a969bc25155450e900e0ed8854753b16a8a743b89583fa24f52a25028dbe8fa2714396dc6610b242f1c1d362e44b39855667510555bead00be5a5baccf99073870d1367a527eebeb32d0c40da80449ca951e27d41cb80ebf4c53aeedbf0f2a008ee04d3e990018e2d7092c2a4f4110a148496bcbe8fc0f043d352de135b9675ca06c9e473d1640c7534f36325f54b2d71e2d54eeef8c58984cc9b5f3129dcb3717b87e85e0ca743c235fd540f751f4b4836040bc138441f88c9a8bd21ae2329604646c7d0efd663240839e1d143f660058031f85cb363c2d00812aa084f4e6392570a757417276167c498a2af66f9514f1295065ac794073d238b107c18ac480f83a988e1d782c6f21634a913cf3e8e1c8520aa8decaa2c853edf1b8d604fc9b69b5ea11ae217084d0b336e4b2da8b9a8c3680761dca802bc61aa790630d1cb3d80e1684fddd43720480fe6f21c011d8a0e0f7b9d8749571b77e050dab18587b43254f4fa2821350e4946d5891803639da942810f20c98165a5614d77a1c622067843bb40055d990ecba8122fe18b5ab50be26d006b4de9c0cc1f096cff2437443b6005881a0e534de1e019410311401761578cf93691d9fca5c63727a949bf5a09f3d9f8805715df84a735aeaed726e00746913dccd81f35495d44a28570d863496b2753d7d7432f99776a6cf4aa2fae3373ad8f1e267993c2b3d1eecc299cfe272e64b9250e3e05b3639ab648b510b2ef6f92a2ef428d8171364b63feb31662867bf5f80b464ce79e5cc2c914c2b5a18f496975f232de27ab265d805ae906dd5fa973569a56f39261d9a01d67fd14c0fff1fb30359af2428e869067958be1c4349896038db61cdcb84a87c5ceae01fffa6fd4f49b6383bd1dd72dbc254a907054ad4c34c4ea2ed96b48822f0f3451b5b8dc88c3b87062a2e2489aef45efb86311c2ad0c9dc362861e347f41858f84e79991069b5a2662f826d56f5e7329b70135759a6b106b2b8fd80ef962831994c701c74d4e6ce09433437022744cddca30112e6dbcc9e07cd078a716613abc9de5a7c92a30652e5c74c4f14b8e327966c638e9a630112abf1139912c12259f6ba401de71fbcc94c09bf680d06118354c1412469aff8541aad5b16d39bb60bd08a576d53ae1cfc32a34e3838869208239d99808caf56cf810c29bac613e940aaecfbeb389e78d95db8f410749bc3aacce58b842aa765fc02d820fc0c62317201ca1f9b3686a08d3c2f6e5ae5fb4a26dab4b215e0010642c8a08f02ce5795be2da317161b57657a9f0ee14b3fb5b52016e65c9799e2c85b70af4ec05b50d19b0b4ff69009437df6db762f847fe47257c6d0b435754922118de5810b2279664d67ae0898ff235e1073dca5c36b533ae29983efc0f08d67dae85c97ef76f6051aee294081849a04cfeebf89cdd049270ac70f3d88751f9c354aebd9caba94120ea25f81e2a2ca9bb910dbacceac207dc7b0a1a87802f48d664723166abb052d9db3b811d98238f0d1d4a504007ccad3e821ce1a4750bbe0c05f9bdea66bc207b9bc678bac4da816bc2a5ec9079ce995e884b4107194051f6ff570471dee9fd9e628ac8858499056fdfe752282632dbdf84a70be8ed265966909336f3c5c86c38ba4887c4f198cd9d7d04ced89506d039e5693db71569381f028b0c7aaa722c0a9e5fad662a4347006f314a90b69d0139dc03603db6ba5aea88f6b515a32bc7e86dc558a9c7f94a663da13862447004266b6ad1fec40316b5cd40b7a3f3f7b7532acf66c9d887f4419f421aee2e299c27579b5341619428e199861db1b7565a0139a198e0b179223bd765023479d4ec3b7e29ca85ae8650245e8bcc799b3e56f2960cc40c040318a0afe517a7ace32874a1b27f66219e06fcf0ee3909dddad8c050e2a0708fe007c23d5e6b64b2c4ac0e3eaf77dd25b7d9c1829eb1ba0d5a6f065d9a923d697d6ebdbbc885f1b242feb9472654920750a0467883c42416cddbbfd40d8d02a9a9530d9b547e6669b593c4e35f84d0be638732908643a86105448d39fc9602f890eb4503b0547004727b06033c13ccb2c6e6854bc1d35c5483286d34e13490089381b59eaa681b9a27b27d70df81da0b4c05414e03ef8ffcd08005ec5fffff036b5c8e68534190542ed9a5161f90ed0fe7da74c0edb685d34457bed81660b67e900af064302043f3b410d8664da67e78e1ab6fd54bae558c6520ab154c53d6631691bf7eebce1bd08dadbcbed946ee0a23a8a48f1240942bb6d931f3067f9d3507e3960b73404b6971a98b6a2e288c726ac3cd03b70e4760b46e9562a911a3cd98234a168aa703f28486635ac54e37d817a8bb63d362ff8db5d41b7cd235663c2c37667e7b10e3a473c0d5c620c3377f5481dbf59ad255a25493ef846e43088f22ea66cee369f488bd479e6fa718052155b39ea498f3daa6bea009324465860318cad62f5d5c9f1ecbd5dc8daeca28079f33c82e93ae9da915320484d7524f59f0733e1ce5fbb140bbdda5bd289f03aa3283d5912df7950890e93ad9318588dc242d3eb43d719712bbf16235f7575f3c226784dfc1e3795e55e70bc8ce3853e5e13655cab5ec77e41072b9297d4febabef0826b1ff442e9d5d52b8217a1e5eb180536c6b57dafa3fba0e338dfeb388d42fdc9d645a32b4221a13f91afdb9fc250e7b7f8f61e734fc45e030e287e46696200702e6222c639841876b35143c659f85cad1aad1e32fa178b0cf1ce6b243971f3d429ef7c604baf5437d174296a71b988507c410915e55227b9c5e108359da2764b41820211aa15140642e990caaf94afb7562a9062094508460143324498cd45f5a1e6a46b6f287f15ea1ef05fb02880920c550c54f780bad79b6e189a424d812a12d45645011bc22207281a059c2cecdb4a100a381a38e10b7b7201aa1f54538702628c3c02ae8d0252214000d1a280324b0cb38c1e052c7342331416d577fa40967443511305045d9e9de2c6a29abe0c8b17ab85e09fc5d70e2048e024a4d358e166f40c8f6b43ed817ef0ec4331f4131e2baf19ac9d057914a7e0e3eeab7c3db2b208e2509d71c147f86a9f3be50282713d55645f88af7a15be87615d4495bd2dc6a8fb427d232a8214a850ada00e270a9b14f350bd4061a7281d922af14b09144250ed3a0a083a032e3d4e244640b534a921543ea35890ea050509d568a87507a54326e8fe690425010aa125aa0f438b9506a8eb1054df0f7cd40154a20fa583f18c1c3d8132172a9ba1fa3eb58b9222c24b7e84c248a8becf3d2ada0475eea38072f268647c72a212fc25469197164b81e9e37f9b83410b516967b8132b6c44695f149f812806fa540762448b86a5473e1ce550e7f966e453b4efe4cfa52a7d84f6626b1ebc250b45213da1d7d8b04a35e5020c9bfb946b1bff1fe0dc47834ac60ca55d7adbf957100aaedefdcb5384b1ac4a1138699116a4586a672578886349426e834c431650aa69f7f045a5c2f5aeb25d3bb7201d5e17355380e5fa78005fed18818d43382afdac8c11d350533b9bab8845c66afba0b9f2cea83e66273351f5d5a20f94b34587d71fb1862fa036cb7e001b06c088b8a4029187c1896742172d1ff9cac5be32225871dfd7f9a7d86392fa09f3fb413ea229255274d6c46d6ba7b8db43d7f7530a7390f9112461eaeca3dc10077f49bb8f67b43176a4982c2e7b6eb905ef2a864f6781f4c543880ead514f03a783dbbf3e29852f99d0719dac27ab5282e66b3933d858a0c627d215dafb89895430da6f8620b9df8e71d433bc68efac086cd490cc331502c822bb1cfe12104b4271fdbdf3fc83462865c5ec3465acee9a0451429331a115f63509b5ecda5fadb866df81dfbc3155110201537fb67b356d823cef5c23465362e24fd1221039ba7531b49053173ae3427b5305bd7d7a78291cfd00b3adbecc73051fd6e38f133060ea4164295a43debc0dabfbc2bfe144775e04835f3fa5e3431e85c66dc4677201ac0d497898a8c126ab3ca41388604b4bc2c6ad24feed9825b2134a80de1df788e6bb61a9fee2dcc10f6232682ce7e0b4513cf63566900ba69604ce80420a46e04cfc784f3200145212b5b9c87f37de88c8c70fc4dc100004f64d31561b7984cfa6a144dc8daf0aabc5474542afa664f6b227606de89004c3e9c81d4d1098e1b26a6bd20689ffa36ca8464f4f410fa371b0b51538053cf6c0bfb83f6047875a37ba0d979909c4b79549098057a953f6168df99b8393c5f1b4a925830eb7ff085f58340fb5b7c2291e86c35f9012282b6e7b4ea26926e13f89eaa960c2a3338634375f55a0bd7141120b004bb767106d0174c5d5a129481437e7b381745ba094a00548ab30bfa58457319333410b318d92934977c406166522750b523d17b0b5b76f6cab8d15d6314ef8ecea61506e1cff1547e14212a045b86aa38d98d2e3a6cc932674a4541daf848f998421bf044ff8bb4e0cef0481d1c955155939525f0031bb15e9a7535bcb75d7a6f48d3da0229a5a9dafa1114c46a8d0b960024d461b50d03b52df4d4a61988e799b62328d20c0691c3b2ea93d990b60d1d357d02fef1a1919c4df20a36174ce097c124952e103517da305901d0cbe73d9e5721244e1ebef4a1f1e564781099050400404f9c730c50d68040cd277ab6250e2d8e6c1cdb7cd0f6a12c5fef0fae0ae31e9f1cbaf6ffaebd1ac9e9a5ee3d4951b277a5ab3752ec69e551f6d0265161d1ee233c931be066c7922f1b2c0d7728f36ac95dfdc62de49f7d21206d9c2b237c096c93305d6728114958856ab6dc3cac125dc1b9ba148b5860ce98542645847757e2a6b25830b864de20be2d971ce56894beb10e91296872a22e1294121c5b7438601f62c6e374e880f530da3b8d34fb7ca0ddb79bd8b23d8cc4b6ded4d5f840bc5831ced1814178e774b706ed296e0516529a6a8a6afb31679a5bb6ed0f8f58be9a293f1a9529b26675da05c14354111f0c49c5307abb8c1413596dc183a54017a9c8f2428b80d4d8277d95cfe3124b17aea25e28f0bdaff55e443d0d97e7468477d4b89edc0808da7e2f167182090900905a258e42fc32c9af6513789e6fb6f3ca792bdf691ab00838b79be43d6e537485af9934497d4138055c49c24f94caef989e2b738af4dc34b67d95a65f0a956ee4f81a1db335e92682ea6bcc7c72aa1dac88ebd3e8b1334ee77a0aece4e992fb6f4d822f6e8108eba420df73d185279a8559df1751521b8415875338443f5c6566d5e8246b19c690ca1771c1aca226fc26f372cd0fee2e2a87067ce6ef3c6796e78c61f491860bedb2f699699746c9d37302ffc9381982f17cc88d0e402d84184c417785a7326b8fd24ace351ec97b0f3491612d5a33e11df9b3b37b740658067878f696fa1fbab6c2eb5fb50f1b759cc8e888c3c2945314749fbfe1cd7d2c1bc62657003e5c7cc4dad6e00f5b3d2b9b6a6569158805409bbfc87eb385121c3e3992325b1a8bd50fddc237a40e0994d78c39fc9b517ee5ffa70ed8a785e40b453250f08d1c5fecd28eb121de2005a0c3b030b23fd5f0ab120eab7932e316e752ce4a2f328bb715caf12d82113ec7e4e60ffcf1252d48a78d74cb7e7739efe3516d7ff9c7b22bef729617727b4de7b309e39a1b0dd7954dcf9e3e6ae387c66ac4d0c88627619ec25e2218a993e45aaeec23e257fdbfa3c48c5c05e46d2b02dc890de9dc69027e5bdf8040edbdb7007f00a3036346c8db331162fee34b4d61f244497e9e611e2316cc5be715a1206dc1619f5bf2de851dc5cccb8b388e74d0f36ceca4f6dcf5061ecff616f1e743e132d8a55f1cf06c788ef50de070ec446dd48f70b278705a9b45e2f8c788a807df8dddf5e5d8b69f24c4009b69f7f5366441049a50099c6bfbe51cf8397458e7e2c0c058fef0e6ccaca709474eda08f08f458d76f1b43cf10e1879a1905b7ad3d7f70247bfd76583ede143cd279d19252b97e912b9f62c2e3ba249f337c0246e6ca358ce825698af2e00956159e4559d61f53526fd9e3e03621b182f0d62aa10dbd138a13586cc00e22e789f28e76b64219ea2c0a744acc12a0fa90eb06249e82680205e4db92b08e2d2ece25f6fa49d5be31b780867860c0c3cfaff76a8118a167053c1343a218e981760f90c69301d07a70dd3b5407a91040359003086903c43ecc9cc8005802206306b82b49da5c724605dae7a572e2db6fa63a3515c910b81015c4f295d82e84db335e190040b8dcee04d6ba3cc97cdf3e1021320cd99f8158116c03c593f69caea5ecea96c9eb5e07862c3c0b0a535b282788cfd4945a2ae7b1e4ef5db7210d5b42e3653d92b80e18f375849bff5c18855e0da1437c89e0b6623271f3b0026abca69fd44f606d9b07a38fe6b1c3553745062939c4eb7d1f314eb3012f31832801fd74b4437635d5504cf515a98a7a2e47792c19e511dda055a51227097872172d09dbc96ebb02348376922b433d56a4af9772e1b6898a2525372f9f0e69054bc45c3ad1ea97fa3d5f70a013f687280ede6a4b24979f954efd14695ac661642f30a7b841334365f3f4cc95e74d3701de1b71084f60d40e84b9b41931e0a048ce8c742d0c2f866e93ce3dd079219f251e74d4c36d9abb4df5ec7086c0b19e92efaf1da85d777cca341b0726d43825d1a57d8b8b3e611bffc8416075b2691e7f0123d7668795c4b5eae2578f562970637afcb8d2df4372e5edc646d9da6447b39a6187897ecc4f4dea88f93472040c443165286281df7b729579977da34c8a4a8ee25b9c7e7b93e17f6684bff827e48b8b3192fefa8edc6a276ab44d11f0a3c76a6021989eb788bec353c7bb0a2513370849c825d7b0c4f58855ece67e308c5a194bdbb9a5fdff961350c4ad163af8dba19f54079de9b08274513969251d2ac63f10eafad0955c5946961d95ad84a1aae0fe9a880c5472b5742c69406cd586ea500bd530017c1b40231523801d69a773518cdf0759d03a85876d22219e49426e48065736c081b086da99ee171ad2da88bad8b141215eb9958af43a91bba6d0690077ef1a49ceeef7942954a1f9f8c2fa15aa2030d463a8b940b3394f0ec413e86ee6d5dff7b688d492e05a996c8bc5e99823b81f9cd19f3531273dbdce2ae5067b3d3a15ce618001ae80f71dea281320268a3a941457c2f4c4d1b8f69b11ebf27b6b0492dafeb83a8770afb038874a9b54ed2d205ed8bee5d893896dfb9814aa713731fa3bf6270126108de6fe9c28f46a30a69b881d7d10cc0336ad1a72495c89c7cb13bb50b7fd229b60921e0dbcd5ac0b8e955fefead53ee03b12ab3a4b0db9a477296c753ee200d2417797d29a7389343fc4a7f306ee11793cb080d4c925aa7e695f17be035c45289a572daaaa42480000d732eb800346362af97d2c5d772497ef5563ee9987f9405ac80605ba82ffdb7189d1abc4ad5787eb40d55bcc2511c9daea03453b0aa150e47365cfa1b02f61ad6fd03a9faf4c089bc0360e08a0e15bc29d18e298552edba11352b4810c30f67344350c0e27aff690064ec5590d5782fcdda9b17cbc6b716eaeb0710ec04d3a52b87ccbe40e006acdf6e5a630d66f7d4016ccbd8e1f1cc602210333fc45908a37856fe6f77e6d839874988c93d4800fd5ecd8836306637e0fd4a0c3703dce2d747618cbde702c9b67b0d48d397bbee38717fb4cc08350e2317106ec3d5899442e4555026e31c49a0a3040f26439ffffffffffffffffff68ddd3d6eced97494a0107a00494c34d29a594528a54ebdee4ce7c86fd0cbb98cf5c85220a6b0a840a8ec614aec1468e363ae25098902fc64ee65822d20e3854b6151263c2a95aed49b6bbb43469e2e978433985f966f32444103aef7043f9ef3289929f3f1f74b4a19063b0f38cfb39c682d0c18642b44b35255dd735944feedf7e08b7430dc5ff91316bf6c78f49c51f74a4a1187bd63ff5c734a3b7103ad0506270a0e30c253125e4e36dcc2544af3bcc50909e5a4aec8394b352a30c3aca50bebdcc21851295f163c8509239dacdea32ffe93d86c295c91046e994aa698aa118ba3e36c61cc404fd75b5d01186429435dd1da2868c2dd3460e4a07184aa57c5683921e3b843847c7174abda5937fe63105ed05ed3578a6698977a1dc72a5aca4ec678d36d25081c9a1830bc5edb0109751d479e8d842396af893fbf2af3b3a2d9474f29dcf10fd1c3ab2504c2173d431314add8646197e461ac70a0ba5cc5c265574a8c8efafc000d17105a4ffa6a97b8bd661855244cd89216f8520e8a842b14bb375279f9a9d34154ab11ac145783c8572b610e937885fa76aa55050be5a969b614714ca1663fa7faa5aee35502809617ad4b65ebfca2794ad747ed6908b4387134a392ab3e4d0a33be49b5096fb1acd907d261453d6ac88c7897c553b9650f624b664929e3b94505af56d97905eb3ea2809e5644aa65b9173b7294542314a0ecf96eea7b573472899cb66d37d9af396c1a0c308c5cca263fcd7cf31c85884924819d4526e420aa97510a1183e889010f53dc41802816158d6e2915acee65b5496951002c228664f1bb3348e4bfe8351d0a36406cfa571ea074629364bec5af38be2686797a55cc9d5be28c69c1a654246b2cebd28c62fd91b722ed598e545495706f5e91ac36976514c7f7baefe7153a6ba2807d1ab9539cc4531472bb5481b2e4aa26f2447b7165d57ba456994cd4e2ebd4c62255b143fabc389df1bd393548bb259a78b9dacae2392685108b9cb46e424cda2985793781579d9509245494b4822123d4c73522ccae14b820cda437c33c1a29c43870e6293d445ff8a52ff781e8dbf2bca3123674f93df4ff7ad286cdc6913fa643345ac28e4fd0f317a3fc70aada2a0b6b466888899095245713347cb2ff166a5938ad2c79032530491d51d5414de32e78d161af4daa728c59d560f49dfaf98a6286ddda89b91396784a52857793e2136aa228ea428e8dcbc0f9d789ac151944cf47c5367b748de8ba22436ecbfe82d14e5d27ba7746e5bf1d483a218b3e4147fee9234e43f5134395e3a889c8469e43d51ce342d39226fbcff3b518ee18426a1399c28c8b8eb52b5d1a3f64d1474744d3699d8bc194d94c3c374da70268abeeb9ac22cef56c64429432edd2c6d3ba54b94b6473f09bd6ecdd71225cfb3ae08afe94f9528beaef5b5afecfd4a89d2d866473b731265efe4a3a54d499472eb3cfd885c9f3012c5f3d22aad7cd39e834431527eda9c31dd9d087a441f26c848724a8e3062ca53230a4148751f8949a8f31951aab198bc3b9a45072da2741974fbb4e4a02ba488b27e18d7ed9335cf494479a26cdecdb62cdf20a2205e363b825fe5c81ca290636cc7fa244c89b0218ac1c342d9bb461db710252f2d93c38733d32921cadd3362cace4114644cdee7d964f53c41143fcbb3e8d0bb480a44e9c3c691a94ab6ff80280815bd273f836ee70fa50fb1b5ff3698a7cd0f85a0b55dff3f8910ea43612d536efe1a1d3f7c287cfbf5e8df9424ab3d1433940ca925470f852892e45e270fc5b8f58e61466435090fc538325769758ebcd11dca1b42dafe14214bde0e05a16f22e4dbf81de3752895663febd74c0bd1a1e8a33b954effd1d63914c469efd8f9d6d468e450caf1384ac910fb6fc7a17cfae6b4a73a4dea854339c9b49e5a557f43c926071d1e71754329638ae93da1944dd2db507a0d416a529963f6cfd950d2f9e123f3769ada7c0dc57513f22bc33c85cad55012724f5cec0419a2e569280619b663bccbf960391a4a42ad3fc62c32bd267e869226dbdd1c43b6e9c90cc59f53e9df57695b2a43215988ae293d190a765e9dca2fdf3b3f8692104dd2845c75ecbc180a327a630c51257977c250cc63a249d2959eee6028a88ed21395971e355f2824d5133944cacca7f6424967c9ede5eb9c5077a1e01a719208ed124ecc85c207539e4972caff780be5b0f5ac9f25d5e9ab8562e5c6e81a5b3567f793a7f35d64ad5828bba8bcd91d9d33d32b14e2c40bf5971b6244ad50b8ce263be909a7ffab42d93be9b83f5bcdda51a1b81ff446e579a868660a25a574d22ab78c140a7224a6afdef4592c1385c2ed874df341c3a4080a8538c14275e43ca1304269849139b683dc09a5fe984c88903c41f72614743e51a1a13b9e76261473cea52175b3847227cd71547ce874b6128aa5f1c4e496104ed249287c5f973875915030b5924388a66a847a8492eeb93f0d427c9025048c50ae944f6db31e378c1028424963ea07e13242930e024428eb7a9de6133e1bb61e4641b4e5071d542d8c826cf7bfca6fc74a3a18c50d754ac66e9bbc0f8c82683cff0ff52f8a1b43740c4a72d4185f14c46cffb675d0089e5e94356bd67e0f7951486a5fdeafefa2f06a23364f2c99b3d18596e952c69496735112274992c4f619ed322e0a9fbe448a49f916a5540be9631277a2c9b628984d909d437f5222c9b5280669daa6f7bc3f089916858d9925b3e32951139e4539282d1bda93888b902cca97273a3d8ccc86a05814ebba9476f99c2b826051fc2a95c9a5834ce9794549b76ddad09dadade38ac26a455a8f1d74ffde8a82975b6d6dd69fbcb3a26c726dad2c6d3ce8ab2884f0ee37e14ae868545170dd1f5995d95414cf4bee6d5667897a5151104d5e1e6f94eeb97b8a72dc7e31b139040dd93545d973e26651dea699dc521453c5c914d95c521454aedcbe271174c61d45a96398bc1d39a1425c51142dae94d6b34addd486a224ef3f71b4fea7bb161425911bb4f7e87c9cd0278a2e26fa749e06553a9e284b12d2df44c37dfa74a2bc1e92c4242386248d72a27c9e5644f82053a9573751ca78ba66bf3d4aaeaa8992953c6de239a685a999284fd05e16a362a2ecff2f7aa2e78ff9258a6922a4fa38f968254b14ee75346c0e19edff4a942376b43ecf1e5f3b250a5b2164e8b127519e1c9b240949a218e46a5cfd90c3e670240a4ad5a9c8935ee68144b93609691e57d9e94714c2e5defb9b8e287775c70df935a21c747cbc5f2f1951c8db1b4fc9f6a064968b289650ed6f114b459452b34c2b72fa52aa4c44e9f37ab7a814fdda23228a333327bc54cfe81c3c44e1d6fa5f53e38d7ad0100599748998ddb93ce460218a15bacaba640eca5b244431b8490f51bfce398a83289a8a4832c78782285c7e98dcc40e371906a258ea35b5a6fa0d9f21204ae284675e174f1613fc43e1459b926ded414608faa11076c36587fc1cc2a90f65ad50e222339bebcf8792e6add166be1f25bf87b2e5c49f38b77722d243419bc71c26c4dc9c4279288451edf8fa59dd3f7828c7cc25a3b9e90e458b305a4470ffc8713b14ed7e339ae4dbcdbd0e85382266984c7bf31b3a947653ce3b26cd19f51c8a79f5232e9743316445be516294693a0e45978f1a3bd2a49c150ee5d2a963ca37c6ffdf50cc596f2547c88734cd08b58ef788881b645f2885652cff51db6e78a1206312399c9cac31ee7376a30be78edf525ae5a35d03c20d2e9444bd77d0a63ce41c3b32b6d08d2d74b9a674762ca99ab171430b8810cfab27d3fc290bc49831710f2f319fa4bffa347a030be54ca3eaf3dd154a42c4bcc8ad9a15ca5a1faa1183da6e54a118bb753f414c6e50a1a0f2db1b42e4d48be4c6148aa5496d124ac7e6e07a8e3adc904251c495cc5144c275d26a196e44a15c42a4d451b6de1a8238861b50286592fde9db4f87db9e50cea0646ceb56f373957ce186134adaffa24d856a7dfadc6842418746126966936e30a174d5a321df664fcff124c38d259443b756ad7eee8612ca39caaf7d904105861b4928c8d87bed21f462b88184e28790b09b3d7df2fc8d23946377e4911841b4c67a037682117408e28611ca9a51dfdfb3b8c9b06e14a1149ecc35948a521e4b48871b4428eee78c7952bfe6321c438daacb1181619464c490f93f88ad68100161946c924d1049080f5e3a608800fa64e76c3da5616014b329ed75ed73ea6ce38508fca2789fe5b661a6436c17015f945d5e55ce3a4424c723d08b729b70d35f1d23c08bb29c485254966997392d02bb28cfcc4a947cb2a93f480d11d045b1fe47dc446e79889f5bc3043388402e1c397ae5e2df13015cdce29fb16fa44188c02d18dbde9c5d7266feda88802d0a398b7cf52a99a284522dca23eb35b3356ec2e7694192d3289b3ab7c46751b40e5b9fdae2535976cc88802ccad9343c9dccc5a2782f233a849c8c002c8af7516c3d651c1b8d9c118157d835f7f2e961716e123cde766ab65dbf48045c5190bc9ac4874f6a45313d738f3e0fa9a74aac2855083d253b4f0e39b38a529777cc3d5913c57e55e063b15957b2a928d78b300de9b64b464521868c938966652a3d45310865a33a53e893a3290a263eb54f1231a2c84a51522e31a479d4ee0f795214237c349d5736c73b8da2ac9f3583b80889a268b5c1aa430f45d9b2344848971d7d501425286127b35bfab34f944f878a8e67503b29e489925575f8a07ba4264f270a1fa9326950cd8a3027cab133af4f5c7d9d7513e5bceb1bfbd3e690a389b2f5dfabaa690ff665a2a022c89da821e669eb60a23caa52644c86e910bd4431775d6998d012654f6df910e49528da6d456e917133274e8972081394cebc3e8962292543efac772c5f12e5fb181a84ed9128074dcd9ccdeb3be290289598d7c60fddddcc230a6aa1279a8cf86f6a4714db4255ed29b99def461474d3c4c8ce25a2938c2887fef8a5cc4514d3dfdff376883175461125d350fa1e4d836f661251aa789310474f882869fb24bbb447243f1da23442b8767ca62ed31ba2b45d91743e85289968d9d97dfdfc31210a329f2d7f826e8fc14114a389bd1d6d4d22491005a94977decc7520381342b6feab064441698876de6e1272fa434156f9c651b26264dc0fe58ca3944c267242b8fb504815274ea6e94d36f95052eac38608713de7710f65b7132d6ea5db3d643d143bcd043f551eca214e6e9d84f0df12c643c94de67ca414d99b3b944b86cea1ca3fe9903eaf244f1d4a673a4810f725eed34b8782e62c7a738cedcebd7328cf5a8527edae1cca9b71528b87f8ae228d43c1c243c78f7982ed46e1507c312124a6fa0d25312187d3cd1a7c5f3714e3c4d48fc9e531576d43418bea093f496a4655d9501a95b549c5c850b25f4341a793a0536e926ebb1a8a397ca4eef43f22cd692848c81fb63d086b75d150ec506a74b75567a467287a38954126e76042966628e629d9a3fe3fe9916519ca1372d7aceecd88243214735093444cb2fd161a43b94c4eaed690139e8ba1b82544fa2419d4ba06c350f6537fbe9fb7dd2f0443415675ebd7499e24ff426944c6dff18d170abbd93fafe613b9315d28cc7aa83df972a12454fcc907cf73b36ea1284a44d0f1a5168a7ffd917aafe46c2c0be5ec1054798e1c168a490813b2637a85a25f6a966e8dd1266985824892948acc5bbd477c5f0db22532a34239d4e790fd42080f4253288bd56e881b7672f848a11082d891d3c9e7f5289427c9187f93eedf8843a1ec5a6e3286ebfcb97e423974909326896821612714c434b54663dc9b503c3fa579993af9499950d2d13759949aec49b984c2a40f1e27e69c44664a287f765a3f534a4212a92494bc4fd72ff6fadc8484424c93cbbba3e7c9e808e5382ad3e8f18d103f46287aed88ee3939d375118a49f2d585dedd106d048850124af95cc889494bc3285ce668a7ffe9f4a6c228bbcbebe64fd1486530cae99fcb237e10fb1018658b5425e3bd360715e9f845418698aa79643c0fabc6c85146ebf9c20e21b58d7dcb9bb817458e249a311f325b991927f2babd3955fae474f0a2114d32bbddec8e5d14dd3c87a90d21cefcf9840e5d14334e3695f7ab2317e88d6936efa40329e8e01774e082bcd17424657e8ac8d842ebb845a22d1e1bdfa15e2172e3c8ae830e5b14e67388e1ec74c6c9cb3930848e5a94cf3594dcadf0493a8816869cc69877db6c7b9d6d9abb933b4137f3338be26a9498e366f3111f248bf27e129dddf4b3d0118b6276d77042453279a35503160531f133c6b98e7d59c72b8ab9bba972ea4496bd3a5c510ef164fd933bb3fca615e5fd2f91e3c8fb17ad5941b6ac9814c1be3d2ebe462d84aeafd95c1b5f4571d3241753dac11a6ba8a29cc4248fb964bd88a05ea1231585f3db58cb9be0fd2f2a4abb1927f1cc3a7beda7287e30e5a9426a8af2c556a528cdd56d12bfe65f1a2245c14fc9a844eb46519d96a68779d6243bed124296a43e89a23c22c8f591bf2314e52d2fd5c1c30e8a5227fd90476f9f285dfec73ddd1dfc64f34461a247f4bdbf34f964274a66e263f8f4f426f366a8d1c189bbea4ee36653d29a7b6a7f8610b7e5e94d79878e4d10c49d1075bab4646cf5e071a006b1269a38eaad88a0df4a9d08658297affad4b0b62c9370f61184dc858992f978f0ddd4fe67327589ff2567ffcc1a1d96c82e52a4893139b2758d6889481d42262d69117454a25c5e9bc9357d1938420725ca3926a13a9cb9a4f01831c44042c7244a1b49a6123ac393d49c240a22265a32f3d9a37644a29884c9983d4634899b3a205152c226be99dbdadf48c6561a6c8c81c69b3146d9073a1e51fee4353ae6cf92e3d50e471425a9f7c7c8fb2849ec6844b93e6af977690a397934664479ed5a2b248c32a0822b3678f0b7818e45eca12639d56bbb458612932b37e215ca418722ca95125deeded39108a2aa8433130d8dd51aafd638923409a5317623a26ced499b52d3f09b443b7834c658238d31b8ae4ad07188d265c4fbcd9c4e79a686505398db281502dba83f62ce4a673b11a21c1274c7ede4e918c432496bb44ac6abf3c4c40d79f3a8a8a4431025912468d2bf0dd0306b8c61d4c831468eb701fa319279721d2bf0001bc60665e8c0a0c1aee80844f135b8e95ed1b14d66d450a30d4014c39af49ceb438a30331a6da4e163acf13b48c38c8e0d74fca19823a99724b2c90fa5d8ebce16dacc44471f0a314569f9505c4fef3946e8d41ff18e3d94f3c92ca6d3df9c909fefd04349ceb55ae59bd7d5df918742720fddb959b6466936e8c0431a1e51ebe64e443fa752dd0f2266e81d0af9c3c793e33966e246a5c30ea5f5a063af696bcda61d75387c27bd928d339275d0a13041860966d2c3935076cca174aa3225c8601dd388a1d02187e27f4c4a8d0c061e6220febff7cc664979e229788481c190c3e30b2af0f0824717dc13b149d926f32f079e223cb850543b530de77983fa881e5bf0d082d2c0230b6c786041cb2a53396bad8c8bd5b83cf14c22798cda35f0b84259d3cfe79f88b34ed50a851c6d84491f8baddb3caa50ce1141d3f40451a12c693ec49b182d35bd29146692f58ac849fe7d8e62c1430ac5cf692d841a8d47148a7e25acee268756f0804241ed7734488c1133a9c713ca5ebe2554c37959f07042e9b74bc96c59b2b51e6c42b145c6ce301d31aa3a0f26944644fee89e4bdcc62ca118ef45dc9a899450d2d89a3b7f699250bc8d94debbe9de348d84927c92e95f8277778a3d8e50126b53726635424908a5494dc6e85b393d8a50384d9dd3ae6a3ce04184831edf8eab700c03f131e7d2b954cebd2dc0210c15e008463a740826435685c6c06803c72fd217ba5c248bb7129bdbb61725a5730c727c933878514ce741fac577765108396a7fd9aaba284d180d7eba9994569b8bb2a9f967cc49cfa3dec7818b8238fd99c6fbba4549e3a9efce16e54d4a879e9cb1643aa15a943e6dd86c9ab373020e5a14ef730cd3fcd4e02bba9b45c9b4a42bf1349e2ccaed1263be451c993f6f093890b9b3c553a7f22e71c0a2904cd6e9567f0cafb79180e315252154c9a025f77d89da15e5d3ee210711a2e7a0dc56144fad936b44119fe75b1170b0a21c23627a6c844d323f52c0b18a82694b92eda3220825c221e05045b1c4a9667d1b254ac871a4a2b8f1d9438ac42c0e5494a486d1d9c3341ca728c693a7c74efa8b876ca628b56651a396bd1962689421022b4539b53ddd64e89b1cd4468a826ed00e22ef8e378a92b65c9a85a67ecc9e2c0e5114e26ae8776cc9041ca128c859bbd33d39384051b40edae7afd24f947f833ecdeea9b141e5896210694d4fccba13a5d0b2595e7af2468c878313690a1193129713fa0a706ca2f29c21284f9ab4d5d1811474f01d7068a294d157fc7b7f3c4f08195b6288713ae036aaac802313870313382e819628c6a89fd7c354899246cd3f2f75ea92744a94435e5dd1d8f1b6f33d8962be8ed0f85e133a842581d01dcd09b2d94894bc24d9bc6b87e8101285ac59d2e2a467da943ea224334bb0d17e19ea7d1c8e28fcc6f7583f214fbbd888e5347e7466949c630472563536735693229bd7a70f2f6993887c882da294b1c4272594aed586c0a18862a80832abb9c95ffb89288d7d7e493561e384d21d010722caf6aa9a515682eecf87288be88fe1e757e33bc5618872ce7963981215a224d994dcf48ea15d1d218a97e53596a74395781005cd7709bac2df35523804519220b37b8920e2084439d28e5abfdd0051d011e52641ad697811ff509ce0a9374813151470f8a194311e6d4b275e7787a30fa533b7aff0529327e0e043b14444194d991c03c71e0ae163f492fd61c3a187723e1965c39418471e0a326889e11f9259c0914c12825bbea98fe30e05d9d9f43a3fa162531c7628c9cd103dc78f4989ff5c01471dcafd13354428b5b7933a35e0a043319386d012d4ef8d1e6b0a38e65012d3f21d746ed80c3729f0010e3994cfe4d3c55b8cc9fc6aa600471cf00638e0508827573c2cdd5b5d52061c6f28e87c95d09c565428251c6e289a0e12545644f89e0f8e3614929e989a44bdad4c0e071b4a918459773ef1d1e521634b71acc130df7415f96da6cc81430d79f0fafc8dafbd91b1956ce0488349c664b8968c2d1c68289c47ac7111961965b800c719d80cb623b3bf946f86f2a4d18c24c3a8f435d90047194afa9ee4345637c9242243e1a4085f1162196e1a4349d43fa5d98510713e62c04d2cdd2ecc3e7c378edd66d3e008434989bc2459ebd3ec1c188a41262144c5bc38be5062180e7078a178bd5173dee45a7aa60b251336271efbc424b972a17c2263b6971eb750901d4cd86a66add8d43a0e2d14f4c93cf518268ce93f0bc551aeafa9f3cb24a562a120b433662a5da5d64f1c572878ce990ee623e3ac638582d4b2d5ef72cf90af423999aaba7e4c3a6962a8f0a89a9698f2e0984221a91919f245676f7738a4500e5225c63e21bbb7c41185237aaa45ba332d8bd7a4627f0f7040a11c44828c7393b473869f50489741f96c4c2325c49c5074096a3478b6fdf7ab09052b15fd9e31e34126389850909e43bd4f3e2da11c224ddc3053e250423987741d722879752d8e24149212aaefe7314868547dd0133eee2314931e37f364fa3a723642216586fe20b73ad4af388a50ec1cc66af3b35a5f868308e53989fe7baaac4c88dc184631839e519abfbf7356710a37845188a974cf9a860a1531c128db96acb27d9d2fa54368bc0a6e00a3d4721e9ffd4bfd07911bbf28f5c6081ef4b68650fa0bdcf0453979d0dcd3d350a65e9425bce8ccb7ace8cf8b52e74eefc174d22e0ac23ee6a0ee41a68b424ced398da033b37e2e0a3a2d463b4352c91d17a5ca74daea47a7b4cdb945496ab69a9c7d4e9be7b6287a6eeeac63a5dad4a616c5acdc3c1bbb6951483275eaa0c9d486cd6fcca234775ae2368409a665591426c947f975bd7b7363519074a54c6bae4fd781453169ed1cb74ea8b5385f51d8205ce28d2e35d2465714731435a544e738fa6945d16cf3a585cce872b3e2186fd4487af356515c116ba3c2a48a422eb3d4efd2b671572a3a21e142dad7858a624f5ceb3aa1ffc3e814e58f70c2c4ecc414a5fb70ea8389d7cc7679e146290aea7f669d9b365f45a428cce6883326836cb66714a5b6897972892039ff2e8a82708db921323a44be43b1c70e59a9a50745d9e34679a7dafab13f51ce253bfc52d2881fd313a5f1c8f93e9c30099eeb4479be5227648927b244cc38515ef5c9a3426f44df7d13c5709d12c36d242d9d9d0ea2704313e5798faab339b991899248dbc9e9e42fc20d4c94a3969e95c4dca971e31265d1f5b36dd9b2fdd912450f26e759dadfa844692b4492d31f53c60629513ea1b1ba7bfdc13d796312c55d7bd13f26ffbfaf23dc904471efb3e8bdd2d5ee2a156e44a2a07f6caf242471ea2748e023fa99abdaf06e38a2a46183868ae65c234aa1846be81699bbc18894f69db990a6faf28d45701e6a275f21dfd35ed54e965ef986224a8c44941876831b8828c785879c0f9ba64575e310254daf29ac6f4b686acd10c51c52bcfb568da58cb810a6068ff712a2a875e7b5217a1d4449948ceb3f273c7dce53100521527b437573732e0c44c93d7ad5e6c60f1f4b7703109aaacf7fe30f2506e2e0861fcae971a55746dd87a2c9f44d6f69be39c8eb0337f8b046b8b18792045572629639e149ee6998618331d028830c36cad8811e523237c9d40d9e79f464e468438dcd43e93aede659d1731833c6683c943fe57c4fb6576d63947195a34370e30e6cd98911f1398937ecc0577858b5a6565b8d7d6585ce4fb72e6974d0e8410e5275286ea9f8b4a9a195234287e298ea3f493f1efb437328e418fe9b1ccab6b964460f1e6763c72c0e2529bad6456efb88689bc1a13437aa73c7999c413d666f28fa68896da5b953c868e68682a9cdeebcbb897964ccda50fceecde82933ba556dc686929c9cd91aca31e4ac6e4f93a9a1fc1f4f288f27374b43419e6f9c244fb3c4d46ea0a19827cee9e7cce919ca13e45544c722cc46cfcc50acda8cfde2316f67c96e94a1983e8d4ca339758ad0da0d3294c389e6fb9c105472888da17c6283e74ebf9fdd1043715427ea8c58175d8b8c1b6128e498f24f9d68a481c61839cac82276821b60288bd4036cf0e02b80860dcac8818009e4a8c09be1868d36ce58c0040490e3d768830df3698c055000001f5715a8aac019b107f4d1dafe5e8f07103170c528071c3672b4b1460102d003af800200c0468e3652408000bc0d4c1b3640c398810001e458e34f1b288d35d44800097294a186514000da0080000000a1f16ce4f81d5400030be0000072e47031082000c3864162142000860d83d258438c030060000d48401a6a8c818363d8c831c6180b0040001870802b830d1d9481da18638c044820093d5003090790c011d438e39cf16718400246b832d858838c1c668c3146022450841b630c044880086c7c8e33fe0c037c0ca31872b01ddda5f487241546f9f7439f48224c9c8f05a3b4693532564c7650b233fe8c3de3cf0046e1377b5052eb811ae78c3f43cff833f2e31757061b669091c38c3146023e7c7165b081d238a38d31c648c0472f8a9ab34f4ca23f23ce2363d9aa0dab337e8d317450061a0395715e8d1c5d6ca051c6e540a30c36cc28a30d1f638d37a38ce2453128eddb318a67a8d211dcc00946d0d181147458e26317c69f9841578cd4b02ecaa6b494a5e610c6e40ec1472e78172f2fafb5d74eabb3f2972443e726b963e2a2b4616467da399911823e6e51f6c9b32525870c348c192778944609d816c5121db36869994ff7d7a28b7955af3bc961631ed3a29833dd49f37c612af62cca1b24077dda22d88e289541468ecea1461a63900f5994efd2d74568927e2d2afd884516176b5722afeffbdde3213a58143c795755ee5a9a675f51ccf3b5516eb6d695b9a2983efe49f6edc6994ac6d618080d6386b123c2472b8af174c65bf3b9eac7c0624539c8cc6cf52082dcbe3e565188b9640c16e39b3d44398c1975830f551494f2bdd538ff4845f1c5c773f21da981e775d01fa82889ac1fe7455bdbf4e84cf0718a62a8d8dc5d6fe618363bdf820f5394bd4f899c2072646c8e5294ab675486904da4cb467a33f083146551539b4d6f928f519473d27d5635f9d5639ea2e02cb28964bae15be32314a5c89921537f5216280a3eeaaaba1e33940e6123c7186994400c31de0cf732aa3e3ed19a85bac8d85b6b6b99d79c0e4ff93879d5c1631967a481460fcc48e304ff1d60f344f1c7d55e5e44e6ac3f3a5194d87d523d74be36ffe044b1d327c4f02673305d7d6ca2ac65123b7ba51f9a28879fe7db7c42992854d54526044992236b576deed01bba57f48189c2be2899e7645077f18a21860f3e2e5138a5c397c7ddce0d131c9c34cc604be041c8646a9eddaec1861a9530d5f5694eda84849950a2a4f2f79374cb842c419328c868fa8c04d33907591225ed48717ae39829cf22619c207c247c08a15f32b63e20d1b876a7e496d85d9d564bfc9d901b46c35a8d33cec7234adb279310f921e786b0e1c31105d3ba31c77f9124845023ec0c21d3a9c91c4694d4dd640ff1219cf6f563118d4d3297d312fb3e14510c3246489b9912ff438940442292da6ac32b6d4b8ff5ffbd8bae107f1ca2fc2753c7641d43143b4bb4d53d717a1e2b4431b47ba368fd48699e0f4294a4cc060df7a9053e0651ce6c9a66af564b659020ca9bf39921e7d31a9f1f8128c765ca09f771669b06c4c71f0a623d94fae8f9d3c522632b071b3c7834728d6703046f83359e04676b3c1b65c8e0c30fc5d0216f852991333efac007bd7773b469c8d8aa3d2867f51bd207b95a42157ce8e1230f25ff1a7b91928312268207336ec4ec26898d8a786a2aa563d4901c84c8c71dca51c29a86116f3139fc6187a2ea6d6bf68cab92e0471d6ad55a512f4f5b395da6218ed894a10f3a3422a93a3dae2d6564c2f8e65e73db0dad081f7328c43c217bf698ff904321a9c412951e44d01cff8843e94aed830a19ef83287dc0c150d58de52963c918f38693a6aed0968b587babe1c30de51c944c6a1f4b7af4b90de5a4574d49562da53e3694ac4359a7d0d512a4670da5afd2412999c34612b61ad06b21b369289abed788fad9bfe93ed0f07186f2462aa14aa699ade43ecc808c89f1abbd759a3258dfaad9766676d25d92b2f598de7f90a1142a3dbbc45f7b0e570a1f632827f1e1e488a473ae4a0cc513a9c49d28dd47180aca22fc474a157d1ffd0043c1456ee88728faf84279bb7cee23e731a57d32b6d028e38c03c187178a953ff236728c813e051f5d30c9e8985482cd5d646c3d0e0c1b639861cc8c0f2e9422beaca989570b3eb650ce5affea611992ced142e136a34d921d42a39e4af09185f26a45dc1c6212195b58592cf8c042292fb2e94e7ae6ba5d64461a1fa0c3c715f8081f56b84fced429a1f05105b7bf8403b30231c4c8f1418582eed0acebda1054842ff89842414b628841eb35ed8d52e023adcc668dc997c647140aeabc376c27af091f502875fc8961840ee7aaa3271483483a6fe891f97042612bcdc36a849a50180dd3900da1fb44fec184b2e479bfcda59eb191fb5842c9d55c9350993f9450d226ae1fb1c44b927e120ab6ba39e536089d4f0809e548b761c4feb68a6c3f8e5050dbb6993ded9ad8e4c3088c66cbb04f6d7e14a1a0f523368896edd92602fdc407114aff2fbaa744b4c4581a46795bb5652cdfc4e4d9431805d93a1f53deb36cc8c128c7b4a69b7a3ee4371518854fc276ed639c93d4fda26cb9eb7daf79f8a26c13fc2509ff7b51cc1c4cf7e0456945627617e5d249e910c44d8738395d946283f639dd3517c5338d410f5c143e95a69a48ced324bf45713b8a8889253d9e981eb62806a5a324e1aa7ba34c1eb5280819227f0ca225c8b0072d4ae119723627e9dee979cca2301357cfafaf43c4d44316c530d95427fdb24194dc231665dd51b133e29d1a7758948466b72ad1f21585682392ee0d56bec1f3704539cbe6f491971b7c3766ad284c8c5632ab88a0d46f66ac280617d39dab378e8e6c15c54f9bfec48aad8ab24419db9b7f5351cebca123b5326f574545418968d9c136b7e6bd4e511e5396412c440f53381a43d2b759bdd941a5c84ecdee3c4554c37990497b6e73abc9d8da8107294a9aed45b6388aa2276bd75ad1431465917d61265a92b135865dc70a46d061c782ab367884a2a0e7ebed3555e9a022288ab92bcd3387d63e51d8d1f5214a10f144d963923b19f3dd89f2d6aaf5e69fc6e42f27ca793c7604d3e8260a5123ab281511ed9cd24499b6e1a4954a3c3251f4bcd2161af355b685e78189828e49261193d07eb23c9728c94f3192d227478e901cbd050f4b1443bafbac91ffdd6fa6c1a312c53631d9124ce4e62494286a943ba521b3314acc4da29046ee6bc367f7a49d8724d2d0736d76d946a2783d9aca4a072142a29c2e36a64e4abaa90f1e3ee25d13919eaa537c506a20031e8e2809eb09fa4c7754174f1a8f469494e40f153ac830a2a0737f28eb95902ee6333c16b12ef05004aec0231188f038c4b22e31b33c535286289cb80f15273fa8896b218a7b327cd2e499e447888cad5f6351e04188d2a4fdd5fcbca32e52973c0651ea2fd1e7deba3a121744f93c42f8d872200aea46cae8ba1c1d3d0344b964ccf91f4aba1a6d3e78f8a1d8373a8fad6cb995f43cfa50ce392893d9ea1ec284cdf850c84168b68a51923699720fe5ec22742e6d3e79d3f5d043a55deb33db4ffae41182471e8ad6ddfb396709e5ad93b115030f3cf0dd216dc9a665261662d9a1782f7a5bc3485b931c8f3a9493d050b2e4860ea5ed1c244e4c2268f46c0ea748ca1ce1d400c8c1430ec591ff6cdb2e71061e7128897bb816d712f14c8be8018762ba13e1b3651ebf51f4784331f665a4bee98a921c0f3794e4892472e977108fe0d186724cb2ce50b5fe1a71f4604359f44cba58a4d750feccd94fdb720f35144ef6ec94568f471aca9b4b359b2ccd9c51f64043b16eec3aa8c65133efa1c719ca1e73278f6fcfa896d1c30ca590dbfd575122083cca50bed8b89517094a5c460ed68007194a7274c964e9e135d4308f311494b687a89e27e524a9218b50a824ae0622692c128845e28030180ec469a78701f31308000038228c8682c168a06992f201140003492218362a2822241c10141c1e1a22120b83c17028100a85c1803020140c0402e1b04defac0f40a9a01f865815f2df8f9132ec9a4e7c45842a9d367cafaeb3c90bdb573869bec39f0e0271ac5e95a4becd9a1767d3c1fcffd1fcfb368002da7d0154f37368894c17e1e0d38571a8785c2883e7fa066ccd2f13f20f5f18fc82babf04bb9ffe573d58f69e3526f7b118ac8f26f3c751aa5d099966a32c3258d7ad2fc8da566035aa7f981f28f46542d8ef62c910b509356ec5ac4a81dcd64d5f19b43fa2a8929c944e31e7ee4b771ba6dc12c673c7883022a94ca02fa034dce32e76d84dedc34ffc92b4fa439c81a62f830cce980a02ea56697b7bdc335288fbdc823b395c3042d5c31ff105115dbb096a4594c46dd298ad2177184c4839d0f5afa5f83681f7f581e83cd917da019c01adf5208384321c164aa32a6f4ab7e801fe667eacb6f6a6b4a02b565079055ce46a99d074e168bca04456c384d19aef20a5ccf570eb03e756132c37b7a725d6660c51299495448a3bfa112743def697da199f44dfa11911e230d52b5fc7367d58a4a96140f711cf2d9bf61f05153528ed3d253b50da219e91881ad6aec3e2bb936529227d1c6d580ddb0c0f1c284889f1d8364a77f6143993d5f5e0659d533a90c8e9cb5d8d9a0543d8669a145966ff4a2c584fa1e1d648e8da520d8e7dd76d9c8d1a89000e0db7f5c8418441bf941ace3ebacb88e8e825e3b0c4740b19b3a137ccc9e08dd4e7222fad61fd3fec18524c44d00500883fd1ba044a982be7ee4734e3693761e6ae8b515cba146fd87225bdead0c2bb54f9ec256478ae2973eeab34a660df0feb2ec0614d57911c1ad4cd125b127f40f27d7f5b6ca5ac540d886b227050e64bc50a006dce22d8dee6f64c50333f33da7068499c12fbe0ac5d76d339689f50dde3c674ebcab985e90304b2d194a0c682f9140cfbca6a0bfa5432f96cc7fb66cd7bfaa84b1ba13d086f20e7bc8416bd01c342b3a4b184cfb93ff85a79b8135640d3c1a5907e259836b63434d21d36115c33e6e10df209dd1fe30520f664b65d0c0918e01370e19da3a72add89653b28663860ca2ce7cbe72af59e460ba1d1a244120f5e977290cd8522a98a2a401b170a12bfeb0de0bbea128a53cc03da8c191569ccfd82562b8c2a6a52fff20c7b0aa604a4f9f195f17a5d3fad581e0cafcfef43bdebb0ae6792325cf9c46c6820cad8ec9a4bda07a94c5e9c6637a2edd891543cede49d9de3a75c6306d8e57176d4d4c346d964524865f33069c22375e1735709030f4a80362cd5b2849b29cea9d3a9730b9167795cf17da83271358618cdfca307829d3a321ce42119b3aa2be752fefdae278f180778ca66349751aef60382fb714737fe2fba9884c945876f675e516d7a157cee9093e8213c057495e16fd49582a373de85f01af3c7ae779f96d94a9e8c4886ac2ed8efeac17f43a5497449b4d790b806a18241e9143ec1fa5f232bd0dc4da423eb821a9849a66691a5a0115a2b90019b57d11bf49036ff9935cdf9e7ecadd9613296ace0f43b077f3240cc010aace39ceb4a2c97e8c84111563b6e79e32ae98415e76a5d37178e673cb0b75633ac33b0061379803004b104eb55c8c5a7ef2de8324a9070dcfe73b5b21544a6c51e11e2c46f115b92e31063ddc2ac9ac79f21ae64aac664929e0e6449423d6da371fdd29960a221b63185fadb7c662965eef9ed0c560844006dffc9866b29a4c4b65240974e0e8181aa1e1e1090ef576e2a7d92c62e6958ab308d9b158245f1b27d03f17b68fbba16bf3f5c6df79387ce4c8dfc7157e6e0ae93450e15b813f78b09bbf947fde627120f1b200363536dd96d402e32323c8dfd1c09bd9ecff99b4699f7996e40063d47956fcce4c95a0494ee22b1f6a5894165eae66052d7bed3caabf2c879bddec2298b4f4dfdf8efd8949a7047450cbf651cfe5e6e051c5dbd1d00c3b1699a48e0bd9cd80f1d000a172a8737412bcb2a4acadf2700b2f3df64479b3dbd333e4916600649fcb91c477720968733047c39f1a273aedc71599014bad6296bb50754bc895a1a459d942924664a99291f7c24ddf83fd2ee93adb54412bc06aa2216d011e6776ef52ce5d49540cfc736410600893ecba510979a74c32a5c01f62a1e0ca5785eb78947f5561f87d964892a64966b9f8d2151bfb1be19a11826fef43d196d8781207631bf9ef7ba6d5ac011f16346e47ef770ee1eee002c477c9f79caa4aad7ee225e0756c7f3d0eb05ca56bf86232ee6cb261eaaeb853b1501ef4199f5ca12a3e5be5d0b3937e740a6e440b3d8eeae9877c41a5fadb672556b5a8ffb85ca9c4dad2667a23274ed0754f59b3fe93fd200e71cf9062e191e9d6544944ecd5762f932376ab350edae1d767e7bc4ac43bc10f20bf751f85ebcd03a471dde9b47a7acf611411a9cbe29a9e739af560656e0cc81c0d9b575509253044eeb39a0b78e7ed75d91b813f12721961cd5fa910c57a8645d40f505eb8ee328caafc0822f3b17812d290de611549beab9408481f2176221a4a7485653107fbe5964f97b37c49799946c393370558ff1511678fa3d545ee604b4102a2ff9d2fa7d67a241bba2f50168e19d5ae33435b4c2489c71fa66f9fb4fe7f131eec05591fdedce8c4f0a84717ac6506babed27c915ccb5cfa331e7f5605deadf2372aa60f0fd5e9e7be2d93c035f83f4d636e15820397009f36cf7faa42dd62afa48c0d57e8fbde5ac20429fc1717dec1a3242ed76d495c22f643526f6c387e325dc59e8b8acc478a068b8a315a756d98bd371135921ddd870c29d44c53289a5952cf4943f97191c047c0d891690da29501ec4aded5879c32841191388a4544185cd6c87453de28152709a0cbd659ae17e2802519ca35efeb0b08addbc3a1dcd8c97f96de1b4b1a1433477704630a3b264fb177af952f43ebad4ece29339cdde9f316fd21d736f3f2e13c017cd23948bac849d87100f62617364b75486c0f259450ead543dc3f4bfc2f2480c1e1950e73b889597c21d52a4ddf0e2923841785762cec61d20423ba476812e2c3d0eac00c51250d0c1e81b11d010c4c8a07d7532459900f602b0a24b0e74ad91c5beeb94c3ac3f98b79b818d8689f9dc735963fa1b2efa5e1b2bd327420c55b589c1f6c440d9b5bf8850214211b3966286c6b67721a77e02b220b43c6c0b65b926a2b41ddf53bca345559f926a751bf03895cbafcc1e23cc7d3b59634ee35ef5e0e30e09c1002f92444eb7dbd202ae6425cd3e67fc51118e54a2f7072008a162983a1dd804085ab6870b626d121a81553003708e6db86146a1cf7d4d3b7719e733a1d71d2ae82f15b32fede9a96f6f85d887e7362afaedd4fb2cd74283bbf4cb9b80624717d146f69de93cbd02dcaa56448b516f04455e079e04ba24d0358a0d1747e25d0cf8c5f57e2efa98e2f1be52af0e7dbc48b813f082b1140fa131faf0d0c34cc89026f504cdd619c4946cbbabc400983d73aff9f35ca296423df2d34f415bc7e6365abb8519f1f6c6c9f7b018eb2612abd827c96d1c14e27ef0e0422b6bf3823b66f68c10139a7692366522a999f9ecee27cec29be7a0358fbf6671dd9498cbf6c50f7a00b696447c51293f8012d24cfcb5b49937ff9a61f169c837b68848404b47575957bdf3c12373ce7035eaf15817490b2b23fbe8bfd6f865a9d70adf9372a3bcb3205a311f1868a974144059d936d374cb14d2b2024bc1fbe88cf48cf023d0def31c6abf80614d601b635c379d9f8739700e1e8b2796ce2e023f790cfca2ff0c742cae0f24a47d31782cb229fb98fd0a3e4106ec9d0f711c8f8aea7226c259792e8201da1aede1e909b63ee7fda0a4a71f0b36556d3a35431893bb48f3e3dee81db5256133d6d205eac2a799d94c6a4763fb7434512d899cfb479c2e318b947ee81fade83481fc504c0058c9d2593d1362f28ffe0d27705272236316735bc3f930b4d9a99b5e23ddd52c61f6917ed347ecb59b1910b39f4cbc22797c12a3fb572314dca1670d8d701d653ca4278d8a27e59bc2aff298882aeced3c9508f402f7a9ee9b77b3c0eb9ba2a321679234453429a567f6a1b7a0de0dd0487712a53f1b499a756a6849a5c1d7fd8c2c2e8e1888e0ddfaf2a2b6cf8d2fb856db04eb4e4acc40e4d87aeebf8e08ddbf2445c69d79b04ae2d4c0eb25ec1ddb8c9fe90616aaace858acaf8686adadbc81e0f3ac2314e4bbf5fdbb97d0da4d8b4eda5f951dd553bad64a19f754004c49f8a074b0f90d9d9a3451d11c4cfbfa5b0084edda65aaeae2cb5010904ab0f46182642a6c421b558f6ddc63c6e902cee0caf72bf2a503e286311e1cdc8e21ee2e685ad8fab9185857dad90e3ed9465db49b3bc5689a0dbe0bf801be58e0f3e8f7299e1572499f1a06ee2f3384851e9e6ec999dbab705882a1883e18fe05c56b992a6d8a220fa7144b4cd620a77cc03f201a28fac0cb4c8fd5790e9921d32bc4c3e1cf662965d81bd0d47e5852c9d27452a208057979a28dd358b9f8c6c2c64aa5f196608782286086fc638e5c1a255085bff3b971b93ce87f3a3e9ec85c2f6efe1e1659b20f8c32bd8e53a94cb1e97ea6a6484e7117411b5465cceaba0033d1bde642216db596e89889be57c2b1cefa6aa74f23ba04c2dc2c1683806bb6b8c140a52151cfdb98c965b2808cbacba3d9f9248b684ea4740bd9ec24792527998d31c61f84072dd846f3be0e895baa3f89506d6edb19038b726059c136de7bbaa11808e8589962131aee81674b47a6e309d396c0aa7a4256896c76e14947e5cdbd81473fa5a907d2a49252fdb210193285b4e9a17b7c4565aec10a2c3b0a143763eb8a6cdec235448fd9ddcaa716ac794dac9ac540512768553d9514fd3b3c24a53579014166b7bf265bb7e13f79453b71f4339ecd48e8685b9146ca24a21380e7655102dcb8147f60b483ce8914354964df71213fb4495269a432ec2f73242de2f7f245335814d23e8012b568700d46728d000161a8d6c80a3670d18ae3d7c2a98124e4628b1a36346a228fa7c9dd43404a2ed15c4038902649229b2e5f1772ce96a3cf141c153cd184043d7e7fcd4d17133e67af4c631841699e1d0dff123252d67f6586b386b307cf842a0e125a00f9063a9abe0e9d201440974fc4cfcf12829e444f2027b1c5735de713cb8af5696b40949b5e265319496f61039abc62411d0b5112d0946bc4e324ffe423739a969f3a83808da538f4ffd05d8c37d747b104d80dcd508bff88575db324bb96752da9c13961710abaef7ae5e29e76cab698b358d61e2fd995886984d40208a2ad3d69553eae2e9aab1989a32751b9d4ec645d89407d801b41aa1148054684e89264775564b996071964870a52c4ab2d8d64158ebd3fc948b0770d2b3e3c1d24051e39d9a8ce88df9dad46b5228a3eb1a5d4d16b2bcda1e9daab28797e12280306f8c8544f961651dd8e80260324df1fa68a6862af3fed9014538d3e28e5e1f039285899f396143076a46112e6cb0b89028638efb8e3a3c9062570c355f56e35dc6f2d4e98468b067964fc9a11e182b1796da16a41287a70ee27c1ba54002e4c0a792861b1ddbdaf61ce40d8c340512ff4a642c29e157c03077d8ddc8da54c46a28ce44a923732c44b32377ab3b1123de07c0638840871c7cdac663521f43d420484c8d14f7c972985271bc59712f17c2fbd401e242ee0c7095021328365c707e8ecffc630843d8160d71b934e6503acdd4cd08ced11b5dcf2d08137d1c0113d36b3636aceb8ffcde8d1f44d519691385f33b282750771da65788ce759d31ca158d85920f1e33af5232b90ede49caef50bca28430ee310a46ea81a6d58cba45c62a10cc8cca181ded9093db489f5e0f8d626c74a10b499b79092f58a2be97d76de018e1d961d64ebd2348dd396762c3f99c7a9cfa76420f71b79ee8a1e4dfbe7558bb86180593c525df26213b739a919d99d19266eb2fc6a4fe4a88a1d8dabe19b82fd6c2043d493db5d911fdb6ee06305a1c8141bc601a04258ae498477d130550b3510dbfcdb1408a52a9bc9876a25cd47bb4a50c0338cb91d9d5496fbe23b5dba137d21373586024ec55e6089bb0d1e7c772fb564f25d65cfb59b6d1761aeb47e67057890ae5838a2d82b4e80c20cee515065e0a5c0ff014a9e569772e68f58899a114420971967964382aeba7c58d7ecf33e4d8d894b3d326d3718284381cf29e0ff6b5e7370ea3e1e5c02584cffd6098a427c1bc406f4b87436845017cabbdb4d6038b89fc1984fc0cd95d98afeb46c817471b62cdb7857890c2968e34de6f377bac138fbae7adafd434d67dc792bc0f15914f12e4c3b2bd98569b486094a07a51a453ac8e0bbd2556cfc707a57c5f9eac500e16e1f89343e2bd3ce98042df87feea31fe3053b4307b4478d6dd30074366af05fa1e1212256bbb049e5c72115f9b7a8d5d5b052b4cddf5aa02fd6f8b7eac483452ad0b717c3d2ae683b313421ba5c767fafd53570a325c4020141f81065ef61268dda35e727a3a284506e15ad958270cb42cc4e326f61301a2a82b5696462e62169d58296a4c088bf1465a48a268201e791a88082dc75fd1f10bd4905eb0a8d77127bdd88070d6c69ca8df2470d7e04d2e37a047b4345a93139efd0f72d66c64d5f7985d90a62bd406ff778b0d1d6a330011dfeb55a14dc0aa2a51205638a7c8599e913ef4afc6986c40923201a5def91295c94985e3eb77d64311c97318a84483118ce2090a9dac25843c08529965632e0302634ab7906e46d2db4002197154c95c7d97946048fa887b7ef2ae3e14e9dec7a2552a57ad95a3fa8d773b192262e6136a3df5aa3bf8fb2b42a9d815fbd42a47e6b443a88d7512ad984a7975000533f7efca0dea7316afdc9c9ecd74183fb3a1966c5c478dc4907bd441790fbebea82159013780508cf5d523972882903dd7a893cb8e77492c64c89d9e7858637384b3c8c2dba298a77441c13b0d1d73f46fb72f32f3adf1766782c9c424fa990f93129642e4fc9ff25e04185459bfa12f0197d346bd079f3516d5ccfa37df9523edbc350c0b93835cc24275b7d8d704e244594bd3ce6d2af58211c4e0f25aeb7a669af938e7cbce395a8868d771fe91d41e1d144123d0f149511582e141c10501bb7a694d95b81c655338934f1a5febca2aae60d96707334134a0f051fb06b5b2c22b23096199a4606be514cce6a1e82e4d589aa4f876d7b7ceb3b386684c9fbbbe1995973f7ab54389a087c1617a9443d144dff1bda6c2e9f6db64e9ca672523fba4745a54d88c854bc256935e5e0ece66bde507a65862fd3bf9059458268492ada6545cdf53c5e84c169784b21a01a4b2273d79036d44b13ed5a1e8a439625b3245e4be58a5919bfde7f5aeb76a012ea9fac83cb08d1bf444fee572a39127d3306630437b986d594581278e34fbc427fff2dd3069d294a58b4b86e10fbf6d6dca43c13399247ea6708b54bb48799533ccfce2ef1b46d08dce79cf127c23d06b2e5c41cc6378d2b187afc36de1bd0fa8b9022da88b732ca39a118c0609ad68ce0315f544fe633e802368a28bb7a5c31937af3669174b23a16ac092034ae052db9c4c3894f08929d9d5a4f951a2d3c042b6dd11f550e5e726fb806d7a61e99528c158a58e32d834a181def489bc37f00e2733d547fc08cda12051006c77b0a2627b0a55b93ddc9003eaca6296e57a3b7d74bfd2308d835637187969c87d1a22969e41ed629a114091e0dd82397c0c0f0098f1a411252e70871c2a3ba274c0df305e43b21af1e6cadc78ca0df3eb32e6cae0b1d113cc50efc3d53c65125f14e1b475918c65dd4f8e271a47c5d3c2cb452b8ae5d508cd80fcb6820b18419115c774233400f8fe410fbfdcdd7c62d16bb91264601cff45fadaeb0a49e9697b75f5c9bc2a7a3019415eda76e67d712079bfce8189f918db213de05504d2b08bb6a7e9f4643f12f4ef66201393019c79df380d3ab0eff405d3ab8ec7aa5229976b4c912f2ace265a86c8bfa4ba7093ac9bd3c5d2bd04b4a1700b0b3a19c1b07d54a8566fd4fd5ef5e18c8d808cfe93d29393bf476d1ed5400462d1f0ef34276b85b1908a1c7c7b403bccbb9a5702e8976e8f2228f235b7b14002abc1b0eeb9e8ed9e0675e87ef4b9073291af44ecc8387b86d6cc5acd2981a7640adaeffe5144c94e8ffd33669fa90675f71785074cf1c0075a155e660d0c2d83ee9296d4657906e46c805ee54551b6ab5a6985e833ef3fd82a143505311125de4a885ae64926fdea61cd99d4b242ae26ba063e6a71cca9da6d2d95189e3bf885fe01a4ce13c19949f91786c0138e07932c25f7213a4e847e4f02b7a13caebdb7f133b5a67618ae10a4c87114184cd198c7035affcb08ffa78bffb6c7ff9639ff5af4cf1ce0dd6485824b1d282b6aa4e11a58cb1c67a8ab5f206101ff10ff0d22ee7952e311fb6136db8e53430f2a3c2a6850988af50173cdf47d2be89e44c9d6c935e2a65f705108be889fa63ae6b093f8ecbbbe40bb70824ecd8d51144287451542a82819b5d8677e7b9b48fd43bc2e6cf3031e1894b01b3744901d4cade23321ff92dcdd33beb337922f3239fd272ba133533d22dc15fc421679db49c2f5d640fc2be813dacbc48c3f613d1ceb0534047f7fd7728ac87754debb37a75ee2dc09d53f3d7d37a99f9f22e448bea8ad7bbc096ae6e977f3d2f77bd2b33bb2ee7f23665bd09c776cf9d9178e4bc08b7d44cbf9c68ee5129fcc4ebc4fb0aa2765d1afa610a932229ced27487ecd542c58ea27310e5e9ad47ff6705117d62ea21d891cc80c5c044b62117100ac5f0044e8d2953e3047aed3eab19914ef0b831e9a7bb35529548b26b01cb4540aecccadb429a7b0efe1f429eba0c80927de754759cd0c4d67f24709339c8d37ba12d5c2b5f347f2158810a81cb7727046a16782c5c34d7db2135077552ac948130974cd417428c13e9c5c3aeca02c970c159d45817605f6cd8b13bfa644b0f0708e440c316dfb6ce9904e365dab8f0e39c5fa1e69515684d2f8b7d4017ebb22e5a46ceadcc26e75e01020d15652da53ca490106d751ea68e0879bbbd43911b9c8eefadcaea005a72f6de39ed768a651b2b69b2f74d02ba2c945defe38b5ab8d3acefb474f50bbd9da4da712622102db1518f152434720f74919e8ed000b56c34a7aa3523fb3b31070b6d8e8e91ebace4ccc639d79e8e31a89a55c8d8ec9138fc19b8813f654f91e97234c23533656c09a97e2b8df4d21ce20e61aaf051965f417da0cd709155d424eb0bd93f2d6f9eb3abc611f738a58db55a9b5bb2ef731cb564bdf7f15613c2ca913c28349e8f1fb9bb4ee6dd20d903cebd02b9b8530dd908282e7155fcb30b9f0f8aee2ba56be3ea2f51f99bf0ef5ab871c140af2cd5b3041d29d4d0c6919b348b8cfe69e1b9a0dbd23887da0752ba1482ca7e232fe79d74e05f97f12c9742dee722369091142b2dfadd49c4c31669992edebd3db8e991c330ebd23db4cdfce2b551cdca08d061d97ace56c0d2fae80eb6f27b8222717d10d1ab8495fbc66b22cde1740a7a0bbe0fd1b449ac39abbe21d226d38670c50b01bac44490c9af9cef697214dceb1110f3a14579cb62430446a53688cf0c7f0009f7ed7598bc9cf4bdff54fe98682aeb2866124b6b33a22d37ee9a82808c0c5ff9c6d5361f917c8c20e338849b2ae32fe4692f012ff319a50fc9092fbbd252ac32804a08490cbf5fa76a7bea9edc1c14f20b12c57cce0a641d8dcc50670ff87c227b031232c266b0cb36684a0667bc1e1c667ade5a7e089ede81c7450d7f6752813b35366c516e299c742bc01d5c181609946af531191611af0f1bd54ae4d67b56fdd64adf15f06b082f310cbe366ea3575a7da1dc838511ea950a9cbfeef9f5ef2738b57e4c1f6f0bab3bd5e62349240b63381dd10ec6fa5fc8de2abb8a4a58660a7f28ece32c136698a7553d074c4e7d164681e63215f9cb75c5fb561f5b3b114ef965636d90f4e4441b7fd087d6d98cbd9f5c1e226862114353234e7aa9bea849fa448cfbd02e9e81b152fc63191874bbe0fe20c48e1cea50826d43afa2a72a96e9f24fa97a9d69925d554e663adf65a9fa24c4a2b1b67bc7e4b76421cd5ffe9e20105dbb302f20982c0957814506053b543233a2c7590d0480cdb0b3438e0b548520f0fc2711c22a1f767d354d8c38b425774275df6de4b407b91ff8b30dccc9c6db9b851fc660e1e21d0935836b4728cfe45f51c3da2c39898de35f0e5b4ecd0e0c24f98d9d6bf7a846d078e2e7c2ef0fd14d0e31bbb6e4554d6d9853caedf08a224240acdcc67a8c28a0062623befadc038060588ecf6de5af498784f5e0f8fd2bc5f6e6baef5831ffd27ab1dc35f09b0159ba74bc834c30c88e8ebe0476b38535e78cd6837a87471d1e9ac0b684b70a04c7f69ccb0acfd4ddb2a7e2613cfbb959fa87bd16461c6aadb01af448f8527b0bbbc6395a084aa6c1bd3c775d413dbfe95713d162017846aa7f05493869d34f0114c24833b46daea03705317963e662df4824ad7c4c6b46ecffaac0415f496639517c09261cc67fd5e098c341a8c2225ad411379a938cc0098fc30661e1bda1459c50036112b21a4a90afd0dc7ba085678d9c3118b0fa1197105672e8579866656240a2fc3a12ae3ba44ea1fe87a40c22d632402afd2eae70127748badf13f7b2d2c57856ee6e8389910e639806ed00e4341a76687c65207a6795855a6e13303c94c4bdf8caaf77404b3b8ad6c2d3e9a8a6789ae5393b1dd2b86fc7187f6aa6d6482078a4ccbbd09590f1cfed30e4529c12db2cd681ae2c748bb956132becabf5bd1edfb559ff8cf3947c23e61a11cf4ca7e82852ad4f1769993e5e6687e453125d5c2773da4f0fe6d9bd881f349fef305fde85b8d855bebe0b71b9eb7c790b07c92c428bdc9301a73b6dd6510f2ba408bfc0f3da9d1cac75ea1df87e923fe39c7dd623e339255f232e430cf601ffe425ba6b382b53510ac1ae9337dd25030dc118c89809e079278f779d2fef725ce94a5ffce5f304d17acd19f3ef69d0b0139b32c7b2634686e794d4351368542899f2d1fe8448c97141969dfd2d13fb32fb7a5ea84dc9e33e03e86b2523cae600732a910cc9491d299290c494eec4965deda04409f99617ed2c4feb644efa799cdb299fbed3738394c69277af037df9d763692c6cf9224d5d5456f3b78f4e990846d6559edc29edb010a1bdc9c9759e7c1ff63b1b68fa03767bdaa1f33a1cc2caacf3ad8db2d4a51856cd6640ac5b5daaed29e479768c95453219c3ac7b8d5a6497b35d41d2fb5279511ecfa62510099186fc97e2f93cddf93ecdf33a9df37ece3033bb26bf7812217dd3998e6b5d0d2e39711792d0325aa5698dbefddbb10b1dd0009d1f99dde87aaabc1156ca9cf193c3148556ba93a9dc071501b81ba5cb7863fea8c095bf7d97f5149957dc35f18bf03dd60a88bebd766ce8e1e6a0a050d2911e594838f9494c05b1455d0d06d308f1293fa52bd8d55fce8b5de98b676706bc5940fdec6ef54dde811ec4b042cda949ced3329fd4d581a2c120", + "0x3a65787472696e7369635f696e646578": "0x00000000", + "0x3c311d57d4daf52904616cf69648081e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x3c311d57d4daf52904616cf69648081e5e0621c4869aa60c02be9adcc98a0d1d": "0x084acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28bc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c", + "0x3f1467a096bcd71a5b6a0c8155e20810308ce9615de0775a82f8a94dc3d285a1": "0x01", + "0x3f1467a096bcd71a5b6a0c8155e208103f2edf3bdf381debe331ab7446addfdc": "0x000064a7b3b6e00d0000000000000000", + "0x3f1467a096bcd71a5b6a0c8155e208104e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x45323df7cc47150b3930e2666b0aa3134e7b9012096b41c4eb3aaf947f6ea429": "0x0200", + "0x4dcb50595177a3177648411a42aca0f54e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x57f8dc2f5ab09467896f47300f0424384e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x57f8dc2f5ab09467896f47300f0424385e0621c4869aa60c02be9adcc98a0d1d": "0x084acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28bc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c", + "0x7474449cca95dc5d0c00e71735a6d17d4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x79e2fe5d327165001f8232643023ed8b4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x7b3237373ffdfeb1cab4222e3b520d6b4e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0xb8753e9383841da95f7b8871e5de32694e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xc2261276cc9d1f8598ea4b6a74b15c2f4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xc2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80": "0x00000000000000000000000000000000", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb333f71360101c6556bc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c": "0xbc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3fe54f28cc5d1fc584acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28": "0x4acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28", + "0xcec5070d609dd3497f72bde07fc96ba04e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950133cc54b617fa6116175726180bc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c": "0xbc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950b8cd4c3513e0fa5761757261804acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28": "0x4acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28", + "0xcec5070d609dd3497f72bde07fc96ba088dcde934c658227ee1dfafcd6e16903": "0x084acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28bc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c", + "0xcec5070d609dd3497f72bde07fc96ba0e0cdd062e6eaf24295ad4ccfc41d4609": "0x084acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe284acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28bc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252cbc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c", + "0xd57bce545fb382c34570e5dfbf338f5e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xd5e1a2fa16732ce6906189438c0a82c64e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xe38f185207498abb5c213d0fb059b3d84e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xe38f185207498abb5c213d0fb059b3d86323ae84c43568be0d1394d5d0d522c4": "0x04000000", + "0xf0c365c3cf59d671eb72da0e7a4113c44e7b9012096b41c4eb3aaf947f6ea429": "0x0000" + }, + "childrenDefault": {} + } + } +} \ No newline at end of file diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml index 60b8ef54f06..4c141fc691c 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml @@ -56,7 +56,6 @@ sp-version = { path = "../../../../../substrate/primitives/version", default-fea # Polkadot pallet-xcm = { path = "../../../../../polkadot/xcm/pallet-xcm", default-features = false } pallet-xcm-benchmarks = { path = "../../../../../polkadot/xcm/pallet-xcm-benchmarks", default-features = false, optional = true } -polkadot-core-primitives = { path = "../../../../../polkadot/core-primitives", default-features = false } polkadot-parachain-primitives = { path = "../../../../../polkadot/parachain", default-features = false } polkadot-runtime-common = { path = "../../../../../polkadot/runtime/common", default-features = false } rococo-runtime-constants = { path = "../../../../../polkadot/runtime/rococo/constants", default-features = false } @@ -115,7 +114,6 @@ std = [ "pallet-xcm/std", "parachain-info/std", "parachains-common/std", - "polkadot-core-primitives/std", "polkadot-parachain-primitives/std", "polkadot-runtime-common/std", "rococo-runtime-constants/std", diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs index 3e47b1bc4cb..742dd50f6fa 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs @@ -29,10 +29,9 @@ use pallet_broker::{CoreAssignment, CoreIndex, CoretimeInterface, PartsOf57600, use parachains_common::{AccountId, Balance, BlockNumber}; use xcm::latest::prelude::*; -// TODO: check AccountId import pub struct CreditToCollatorPot; impl OnUnbalanced> for CreditToCollatorPot { - fn on_nonzero_unbalanced(credit: Credit) { + fn on_nonzero_unbalanced(credit: Credit) { let staking_pot = CollatorSelection::account_id(); let _ = >::resolve(&staking_pot, credit); } @@ -81,7 +80,7 @@ pub struct CoretimeAllocator; impl CoretimeInterface for CoretimeAllocator { type AccountId = AccountId; type Balance = Balance; - type RealyChainBlockNumberProvider = RelaychainDataProvider; + type RelayChainBlockNumberProvider = RelaychainDataProvider; fn request_core_count(count: CoreIndex) { use crate::coretime::CoretimeProviderCalls::RequestCoreCount; diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/mod.rs index 8815312f304..9f79cea831a 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/mod.rs @@ -196,7 +196,7 @@ impl XcmWeightInfo for CoretimeRococoXcmWeight { XcmGeneric::::clear_transact_status() } fn universal_origin(_: &Junction) -> Weight { - XcmGeneric::::universal_origin() + Weight::MAX } fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> Weight { Weight::MAX diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index a11d049a3fc..719e7543e88 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -312,16 +312,6 @@ impl WeightInfo { // Minimum execution time: 2_082_000 picoseconds. Weight::from_parts(2_144_000, 0) } - // Storage: `ParachainInfo::ParachainId` (r:1 w:0) - // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - pub fn universal_origin() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `1489` - // Minimum execution time: 4_275_000 picoseconds. - Weight::from_parts(4_381_000, 1489) - .saturating_add(T::DbWeight::get().reads(1)) - } pub fn set_fees_mode() -> Weight { // Proof Size summary in bytes: // Measured: `0` diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml index a34521e9ee8..135ce95f30f 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml @@ -31,9 +31,9 @@ pallet-aura = { path = "../../../../../substrate/frame/aura", default-features = pallet-authorship = { path = "../../../../../substrate/frame/authorship", default-features = false } pallet-balances = { path = "../../../../../substrate/frame/balances", default-features = false } pallet-message-queue = { path = "../../../../../substrate/frame/message-queue", default-features = false } +pallet-broker = { path = "../../../../../substrate/frame/broker", default-features = false } pallet-multisig = { path = "../../../../../substrate/frame/multisig", default-features = false } pallet-session = { path = "../../../../../substrate/frame/session", default-features = false } -pallet-sudo = { path = "../../../../../substrate/frame/sudo", default-features = false } pallet-timestamp = { path = "../../../../../substrate/frame/timestamp", default-features = false } pallet-transaction-payment = { path = "../../../../../substrate/frame/transaction-payment", default-features = false } pallet-transaction-payment-rpc-runtime-api = { path = "../../../../../substrate/frame/transaction-payment/rpc/runtime-api", default-features = false } @@ -55,7 +55,6 @@ sp-version = { path = "../../../../../substrate/primitives/version", default-fea # Polkadot pallet-xcm = { path = "../../../../../polkadot/xcm/pallet-xcm", default-features = false } pallet-xcm-benchmarks = { path = "../../../../../polkadot/xcm/pallet-xcm-benchmarks", default-features = false, optional = true } -polkadot-core-primitives = { path = "../../../../../polkadot/core-primitives", default-features = false } polkadot-parachain-primitives = { path = "../../../../../polkadot/parachain", default-features = false } polkadot-runtime-common = { path = "../../../../../polkadot/runtime/common", default-features = false } westend-runtime-constants = { path = "../../../../../polkadot/runtime/westend/constants", default-features = false } @@ -100,11 +99,11 @@ std = [ "pallet-aura/std", "pallet-authorship/std", "pallet-balances/std", + "pallet-broker/std", "pallet-collator-selection/std", "pallet-message-queue/std", "pallet-multisig/std", "pallet-session/std", - "pallet-sudo/std", "pallet-timestamp/std", "pallet-transaction-payment-rpc-runtime-api/std", "pallet-transaction-payment/std", @@ -113,7 +112,6 @@ std = [ "pallet-xcm/std", "parachain-info/std", "parachains-common/std", - "polkadot-core-primitives/std", "polkadot-parachain-primitives/std", "polkadot-runtime-common/std", "scale-info/std", @@ -150,10 +148,10 @@ runtime-benchmarks = [ "frame-system-benchmarking/runtime-benchmarks", "frame-system/runtime-benchmarks", "pallet-balances/runtime-benchmarks", + "pallet-broker/runtime-benchmarks", "pallet-collator-selection/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", - "pallet-sudo/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-utility/runtime-benchmarks", "pallet-xcm-benchmarks/runtime-benchmarks", @@ -178,11 +176,11 @@ try-runtime = [ "pallet-aura/try-runtime", "pallet-authorship/try-runtime", "pallet-balances/try-runtime", + "pallet-broker/try-runtime", "pallet-collator-selection/try-runtime", "pallet-message-queue/try-runtime", "pallet-multisig/try-runtime", "pallet-session/try-runtime", - "pallet-sudo/try-runtime", "pallet-timestamp/try-runtime", "pallet-transaction-payment/try-runtime", "pallet-utility/try-runtime", @@ -193,3 +191,5 @@ try-runtime = [ ] experimental = ["pallet-aura/experimental"] + +fast-runtime = [] diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/build.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/build.rs index 60f8a125129..28dacd20cf3 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/build.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/build.rs @@ -19,7 +19,15 @@ fn main() { .with_current_project() .export_heap_base() .import_memory() - .build() + .build(); + + substrate_wasm_builder::WasmBuilder::new() + .with_current_project() + .set_file_name("fast_runtime_binary.rs") + .enable_feature("fast-runtime") + .import_memory() + .export_heap_base() + .build(); } #[cfg(not(feature = "std"))] diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/coretime.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/coretime.rs new file mode 100644 index 00000000000..41cbc62fa21 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/coretime.rs @@ -0,0 +1,249 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +use crate::*; +use codec::{Decode, Encode}; +use cumulus_pallet_parachain_system::RelaychainDataProvider; +use cumulus_primitives_core::relay_chain; +use frame_support::{ + parameter_types, + traits::{ + fungible::{Balanced, Credit}, + OnUnbalanced, + }, +}; +use pallet_broker::{CoreAssignment, CoreIndex, CoretimeInterface, PartsOf57600, RCBlockNumberOf}; +use parachains_common::{AccountId, Balance, BlockNumber}; +use xcm::latest::prelude::*; + +pub struct CreditToCollatorPot; +impl OnUnbalanced> for CreditToCollatorPot { + fn on_nonzero_unbalanced(credit: Credit) { + let staking_pot = CollatorSelection::account_id(); + let _ = >::resolve(&staking_pot, credit); + } +} + +/// A type containing the encoding of the coretime pallet in the Relay chain runtime. Used to +/// construct any remote calls. The codec index must correspond to the index of `Coretime` in the +/// `construct_runtime` of the Relay chain. +#[derive(Encode, Decode)] +enum RelayRuntimePallets { + #[codec(index = 66)] + Coretime(CoretimeProviderCalls), +} + +/// Call encoding for the calls needed from the relay coretime pallet. +#[derive(Encode, Decode)] +enum CoretimeProviderCalls { + #[codec(index = 1)] + RequestCoreCount(CoreIndex), + #[codec(index = 2)] + RequestRevenueInfoAt(relay_chain::BlockNumber), + #[codec(index = 3)] + CreditAccount(AccountId, Balance), + #[codec(index = 4)] + AssignCore( + CoreIndex, + relay_chain::BlockNumber, + Vec<(CoreAssignment, PartsOf57600)>, + Option, + ), +} + +parameter_types! { + pub const BrokerPalletId: PalletId = PalletId(*b"py/broke"); +} + +parameter_types! { + pub storage CoreCount: Option = None; + pub storage CoretimeRevenue: Option<(BlockNumber, Balance)> = None; +} + +/// Type that implements the `CoretimeInterface` for the allocation of Coretime. Meant to operate +/// from the parachain context. That is, the parachain provides a market (broker) for the sale of +/// coretime, but assumes a `CoretimeProvider` (i.e. a Relay Chain) to actually provide cores. +pub struct CoretimeAllocator; +impl CoretimeInterface for CoretimeAllocator { + type AccountId = AccountId; + type Balance = Balance; + type RelayChainBlockNumberProvider = RelaychainDataProvider; + + fn request_core_count(count: CoreIndex) { + use crate::coretime::CoretimeProviderCalls::RequestCoreCount; + let request_core_count_call = RelayRuntimePallets::Coretime(RequestCoreCount(count)); + + // Weight for `request_core_count` from westend benchmarks: + // `ref_time` = 7889000 + (3 * 25000000) + (1 * 100000000) = 182889000 + // `proof_size` = 1636 + // Add 5% to each component and round to 2 significant figures. + let call_weight = Weight::from_parts(190_000_000, 1700); + + let message = Xcm(vec![ + Instruction::UnpaidExecution { + weight_limit: WeightLimit::Unlimited, + check_origin: None, + }, + Instruction::Transact { + origin_kind: OriginKind::Native, + require_weight_at_most: call_weight, + call: request_core_count_call.encode().into(), + }, + ]); + + match PolkadotXcm::send_xcm(Here, Location::parent(), message.clone()) { + Ok(_) => log::debug!( + target: "runtime::coretime", + "Request to update schedulable cores sent successfully." + ), + Err(e) => log::error!( + target: "runtime::coretime", + "Failed to send request to update schedulable cores: {:?}", + e + ), + } + } + + fn request_revenue_info_at(when: RCBlockNumberOf) { + use crate::coretime::CoretimeProviderCalls::RequestRevenueInfoAt; + let request_revenue_info_at_call = + RelayRuntimePallets::Coretime(RequestRevenueInfoAt(when)); + + let message = Xcm(vec![ + Instruction::UnpaidExecution { + weight_limit: WeightLimit::Unlimited, + check_origin: None, + }, + Instruction::Transact { + origin_kind: OriginKind::Native, + require_weight_at_most: Weight::from_parts(1000000000, 200000), + call: request_revenue_info_at_call.encode().into(), + }, + ]); + + match PolkadotXcm::send_xcm(Here, Location::parent(), message.clone()) { + Ok(_) => log::debug!( + target: "runtime::coretime", + "Request for revenue information sent successfully." + ), + Err(e) => log::error!( + target: "runtime::coretime", + "Request for revenue information failed to send: {:?}", + e + ), + } + } + + fn credit_account(who: Self::AccountId, amount: Self::Balance) { + use crate::coretime::CoretimeProviderCalls::CreditAccount; + let credit_account_call = RelayRuntimePallets::Coretime(CreditAccount(who, amount)); + + let message = Xcm(vec![ + Instruction::UnpaidExecution { + weight_limit: WeightLimit::Unlimited, + check_origin: None, + }, + Instruction::Transact { + origin_kind: OriginKind::Native, + require_weight_at_most: Weight::from_parts(1000000000, 200000), + call: credit_account_call.encode().into(), + }, + ]); + + match PolkadotXcm::send_xcm(Here, Location::parent(), message.clone()) { + Ok(_) => log::debug!( + target: "runtime::coretime", + "Instruction to credit account sent successfully." + ), + Err(e) => log::error!( + target: "runtime::coretime", + "Instruction to credit account failed to send: {:?}", + e + ), + } + } + + fn assign_core( + core: CoreIndex, + begin: RCBlockNumberOf, + assignment: Vec<(CoreAssignment, PartsOf57600)>, + end_hint: Option>, + ) { + use crate::coretime::CoretimeProviderCalls::AssignCore; + let assign_core_call = + RelayRuntimePallets::Coretime(AssignCore(core, begin, assignment, end_hint)); + + // Weight for `assign_core` from westend benchmarks: + // `ref_time` = 10177115 + (1 * 25000000) + (2 * 100000000) + (57600 * 13932) = 937660315 + // `proof_size` = 3612 + // Add 5% to each component and round to 2 significant figures. + let call_weight = Weight::from_parts(980_000_000, 3800); + + let message = Xcm(vec![ + Instruction::UnpaidExecution { + weight_limit: WeightLimit::Unlimited, + check_origin: None, + }, + Instruction::Transact { + origin_kind: OriginKind::Native, + require_weight_at_most: call_weight, + call: assign_core_call.encode().into(), + }, + ]); + + match PolkadotXcm::send_xcm(Here, Location::parent(), message.clone()) { + Ok(_) => log::debug!( + target: "runtime::coretime", + "Core assignment sent successfully." + ), + Err(e) => log::error!( + target: "runtime::coretime", + "Core assignment failed to send: {:?}", + e + ), + } + } + + fn check_notify_revenue_info() -> Option<(RCBlockNumberOf, Self::Balance)> { + let revenue = CoretimeRevenue::get(); + CoretimeRevenue::set(&None); + revenue + } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_notify_revenue_info(when: RCBlockNumberOf, revenue: Self::Balance) { + CoretimeRevenue::set(&Some((when, revenue))); + } +} + +impl pallet_broker::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type OnRevenue = CreditToCollatorPot; + #[cfg(feature = "fast-runtime")] + type TimeslicePeriod = ConstU32<10>; + #[cfg(not(feature = "fast-runtime"))] + type TimeslicePeriod = ConstU32<80>; + // We don't actually need any leases at launch but set to 10 in case we want to sudo some in. + type MaxLeasedCores = ConstU32<10>; + type MaxReservedCores = ConstU32<10>; + type Coretime = CoretimeAllocator; + type ConvertBalance = sp_runtime::traits::Identity; + type WeightInfo = weights::pallet_broker::WeightInfo; + type PalletId = BrokerPalletId; + type AdminOrigin = EnsureRoot; + type PriceAdapter = pallet_broker::Linear; +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs index edc06688d0f..85d70d2f17b 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs @@ -21,6 +21,15 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +/// Provides the `WASM_BINARY` build with `fast-runtime` feature enabled. +/// +/// This is for example useful for local test chains. +#[cfg(feature = "std")] +pub mod fast_runtime_binary { + include!(concat!(env!("OUT_DIR"), "/fast_runtime_binary.rs")); +} + +mod coretime; mod weights; pub mod xcm_config; @@ -65,7 +74,7 @@ use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::Weig use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; use xcm::latest::prelude::*; use xcm_config::{ - FellowshipLocation, GovernanceLocation, WndRelayLocation, XcmOriginToTransactDispatchOrigin, + FellowshipLocation, GovernanceLocation, TokenRelayLocation, XcmOriginToTransactDispatchOrigin, }; /// The address format for describing accounts. @@ -98,6 +107,7 @@ pub type UncheckedExtrinsic = /// Migrations to apply on runtime upgrade. pub type Migrations = ( + cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, ); @@ -123,7 +133,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("coretime-westend"), impl_name: create_runtime_str!("coretime-westend"), authoring_version: 1, - spec_version: 1_007_000, + spec_version: 1_007_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 0, @@ -270,8 +280,6 @@ type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< UNINCLUDED_SEGMENT_CAPACITY, >; -impl parachain_info::Config for Runtime {} - parameter_types! { pub MessageQueueServiceWeight: Weight = Perbill::from_percent(35) * RuntimeBlockWeights::get().max_block; } @@ -298,6 +306,8 @@ impl pallet_message_queue::Config for Runtime { type ServiceWeight = MessageQueueServiceWeight; } +impl parachain_info::Config for Runtime {} + impl cumulus_pallet_aura_ext::Config for Runtime {} parameter_types! { @@ -313,7 +323,7 @@ pub type RootOrFellows = EitherOfDiverse< parameter_types! { /// The asset ID for the asset that we use to pay for message delivery fees. - pub FeeAssetId: AssetId = AssetId(WndRelayLocation::get()); + pub FeeAssetId: AssetId = AssetId(TokenRelayLocation::get()); /// The base fee for the message delivery fees. pub const BaseDeliveryFee: u128 = CENTS.saturating_mul(3); } @@ -416,12 +426,6 @@ impl pallet_utility::Config for Runtime { type WeightInfo = weights::pallet_utility::WeightInfo; } -impl pallet_sudo::Config for Runtime { - type RuntimeCall = RuntimeCall; - type RuntimeEvent = RuntimeEvent; - type WeightInfo = pallet_sudo::weights::SubstrateWeight; -} - // Create the runtime by composing the FRAME pallets that were previously configured. construct_runtime!( pub enum Runtime @@ -453,8 +457,8 @@ construct_runtime!( Utility: pallet_utility = 40, Multisig: pallet_multisig = 41, - // Sudo - Sudo: pallet_sudo = 100, + // The main stage. + Broker: pallet_broker = 50, } ); @@ -465,6 +469,7 @@ mod benches { [cumulus_pallet_parachain_system, ParachainSystem] [pallet_timestamp, Timestamp] [pallet_balances, Balances] + [pallet_broker, Broker] [pallet_collator_selection, CollatorSelection] [pallet_session, SessionBench::] [cumulus_pallet_xcmp_queue, XcmpQueue] @@ -697,7 +702,7 @@ impl_runtime_apis! { impl cumulus_pallet_session_benchmarking::Config for Runtime {} use xcm::latest::prelude::*; - use xcm_config::WndRelayLocation; + use xcm_config::TokenRelayLocation; use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; impl pallet_xcm::benchmarking::Config for Runtime { @@ -724,7 +729,7 @@ impl_runtime_apis! { parameter_types! { pub ExistentialDepositAsset: Option = Some(( - WndRelayLocation::get(), + TokenRelayLocation::get(), ExistentialDeposit::get() ).into()); } @@ -738,13 +743,13 @@ impl_runtime_apis! { >; type AccountIdConverter = xcm_config::LocationToAccountId; fn valid_destination() -> Result { - Ok(WndRelayLocation::get()) + Ok(TokenRelayLocation::get()) } fn worst_case_holding(_depositable_count: u32) -> Assets { // just concrete assets according to relay chain. let assets: Vec = vec![ Asset { - id: AssetId(WndRelayLocation::get()), + id: AssetId(TokenRelayLocation::get()), fun: Fungible(1_000_000 * UNITS), } ]; @@ -754,8 +759,8 @@ impl_runtime_apis! { parameter_types! { pub const TrustedTeleporter: Option<(Location, Asset)> = Some(( - WndRelayLocation::get(), - Asset { fun: Fungible(UNITS), id: AssetId(WndRelayLocation::get()) }, + TokenRelayLocation::get(), + Asset { fun: Fungible(UNITS), id: AssetId(TokenRelayLocation::get()) }, )); pub const CheckedAccount: Option<(AccountId, xcm_builder::MintLocation)> = None; pub const TrustedReserve: Option<(Location, Asset)> = None; @@ -770,7 +775,7 @@ impl_runtime_apis! { fn get_asset() -> Asset { Asset { - id: AssetId(WndRelayLocation::get()), + id: AssetId(TokenRelayLocation::get()), fun: Fungible(UNITS), } } @@ -793,23 +798,23 @@ impl_runtime_apis! { } fn transact_origin_and_runtime_call() -> Result<(Location, RuntimeCall), BenchmarkError> { - Ok((WndRelayLocation::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) + Ok((TokenRelayLocation::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) } fn subscribe_origin() -> Result { - Ok(WndRelayLocation::get()) + Ok(TokenRelayLocation::get()) } fn claimable_asset() -> Result<(Location, Location, Assets), BenchmarkError> { - let origin = WndRelayLocation::get(); - let assets: Assets = (AssetId(WndRelayLocation::get()), 1_000 * UNITS).into(); + let origin = TokenRelayLocation::get(); + let assets: Assets = (AssetId(TokenRelayLocation::get()), 1_000 * UNITS).into(); let ticket = Location { parents: 0, interior: Here }; Ok((origin, ticket, assets)) } fn fee_asset() -> Result { Ok(Asset { - id: AssetId(WndRelayLocation::get()), + id: AssetId(TokenRelayLocation::get()), fun: Fungible(1_000_000 * UNITS), }) } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_parachain_system.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_parachain_system.rs index 0303151d7f8..1c911936198 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_parachain_system.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_parachain_system.rs @@ -1,4 +1,4 @@ -// Copyright Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. // Cumulus is free software: you can redistribute it and/or modify @@ -14,6 +14,31 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . +//! Autogenerated weights for `cumulus_pallet_parachain_system` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// ./target/production/polkadot-parachain +// benchmark +// pallet +// --chain=coretime-westend-dev +// --wasm-execution=compiled +// --pallet=cumulus_pallet_parachain_system +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ + #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -22,32 +47,31 @@ use frame_support::{traits::Get, weights::Weight}; use core::marker::PhantomData; -/// Weight functions for `cumulus_pallet_xcmp_queue`. +/// Weight functions for `cumulus_pallet_parachain_system`. pub struct WeightInfo(PhantomData); impl cumulus_pallet_parachain_system::WeightInfo for WeightInfo { - /// Storage: ParachainSystem LastDmqMqcHead (r:1 w:1) - /// Proof Skipped: ParachainSystem LastDmqMqcHead (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ParachainSystem ReservedDmpWeightOverride (r:1 w:0) - /// Proof Skipped: ParachainSystem ReservedDmpWeightOverride (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: MessageQueue BookStateFor (r:1 w:1) - /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) - /// Storage: MessageQueue ServiceHead (r:1 w:1) - /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen) - /// Storage: ParachainSystem ProcessedDownwardMessages (r:0 w:1) - /// Proof Skipped: ParachainSystem ProcessedDownwardMessages (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: MessageQueue Pages (r:0 w:16) - /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + /// Storage: `ParachainSystem::LastDmqMqcHead` (r:1 w:1) + /// Proof: `ParachainSystem::LastDmqMqcHead` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `MessageQueue::BookStateFor` (r:1 w:1) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::ServiceHead` (r:1 w:1) + /// Proof: `MessageQueue::ServiceHead` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::ProcessedDownwardMessages` (r:0 w:1) + /// Proof: `ParachainSystem::ProcessedDownwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `MessageQueue::Pages` (r:0 w:1000) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 1000]`. fn enqueue_inbound_downward_messages(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `12` - // Estimated: `8013` - // Minimum execution time: 1_645_000 picoseconds. - Weight::from_parts(1_717_000, 0) - .saturating_add(Weight::from_parts(0, 8013)) - // Standard Error: 12_258 - .saturating_add(Weight::from_parts(24_890_934, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `48` + // Estimated: `3517` + // Minimum execution time: 2_080_000 picoseconds. + Weight::from_parts(2_157_000, 0) + .saturating_add(Weight::from_parts(0, 3517)) + // Standard Error: 33_906 + .saturating_add(Weight::from_parts(196_603_239, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) } -} \ No newline at end of file +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_xcmp_queue.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_xcmp_queue.rs index 124571118aa..0b0524339aa 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_xcmp_queue.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_xcmp_queue.rs @@ -1,4 +1,4 @@ -// Copyright Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. // Cumulus is free software: you can redistribute it and/or modify @@ -16,26 +16,28 @@ //! Autogenerated weights for `cumulus_pallet_xcmp_queue` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("coretime-westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./artifacts/westend-parachain +// ./target/production/polkadot-parachain // benchmark // pallet // --chain=coretime-westend-dev -// --execution=wasm // --wasm-execution=compiled // --pallet=cumulus_pallet_xcmp_queue +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_xcmp_queue.rs +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -48,14 +50,14 @@ use core::marker::PhantomData; /// Weight functions for `cumulus_pallet_xcmp_queue`. pub struct WeightInfo(PhantomData); impl cumulus_pallet_xcmp_queue::WeightInfo for WeightInfo { - /// Storage: XcmpQueue QueueConfig (r:1 w:1) - /// Proof Skipped: XcmpQueue QueueConfig (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: `XcmpQueue::QueueConfig` (r:1 w:1) + /// Proof: `XcmpQueue::QueueConfig` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn set_config_with_u32() -> Weight { // Proof Size summary in bytes: // Measured: `76` // Estimated: `1561` - // Minimum execution time: 5_621_000 picoseconds. - Weight::from_parts(5_845_000, 0) + // Minimum execution time: 3_796_000 picoseconds. + Weight::from_parts(4_027_000, 0) .saturating_add(Weight::from_parts(0, 1561)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -74,8 +76,8 @@ impl cumulus_pallet_xcmp_queue::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `82` // Estimated: `3517` - // Minimum execution time: 14_000_000 picoseconds. - Weight::from_parts(15_000_000, 0) + // Minimum execution time: 9_990_000 picoseconds. + Weight::from_parts(10_439_000, 0) .saturating_add(Weight::from_parts(0, 3517)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) @@ -86,8 +88,8 @@ impl cumulus_pallet_xcmp_queue::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `76` // Estimated: `1561` - // Minimum execution time: 3_000_000 picoseconds. - Weight::from_parts(3_000_000, 0) + // Minimum execution time: 2_394_000 picoseconds. + Weight::from_parts(2_493_000, 0) .saturating_add(Weight::from_parts(0, 1561)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -98,8 +100,8 @@ impl cumulus_pallet_xcmp_queue::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `111` // Estimated: `1596` - // Minimum execution time: 4_000_000 picoseconds. - Weight::from_parts(4_000_000, 0) + // Minimum execution time: 3_283_000 picoseconds. + Weight::from_parts(3_388_000, 0) .saturating_add(Weight::from_parts(0, 1596)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -108,14 +110,14 @@ impl cumulus_pallet_xcmp_queue::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 44_000_000 picoseconds. - Weight::from_parts(45_000_000, 0) + // Minimum execution time: 5_974_000 picoseconds. + Weight::from_parts(6_166_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6b345d8e88afa015075c945637c07e8f20` (r:1 w:1) /// Proof: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6b345d8e88afa015075c945637c07e8f20` (r:1 w:1) - /// Storage: `XcmpQueue::InboundXcmpMessages` (r:1 w:1) - /// Proof: `XcmpQueue::InboundXcmpMessages` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6bedc49980ba3aa32b0a189290fd036649` (r:1 w:1) + /// Proof: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6bedc49980ba3aa32b0a189290fd036649` (r:1 w:1) /// Storage: `MessageQueue::BookStateFor` (r:1 w:1) /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `MessageQueue::ServiceHead` (r:1 w:1) @@ -130,20 +132,22 @@ impl cumulus_pallet_xcmp_queue::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `65711` // Estimated: `69176` - // Minimum execution time: 67_000_000 picoseconds. - Weight::from_parts(73_000_000, 0) + // Minimum execution time: 117_856_000 picoseconds. + Weight::from_parts(119_808_000, 0) .saturating_add(Weight::from_parts(0, 69176)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6b345d8e88afa015075c945637c07e8f20` (r:1 w:1) /// Proof: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6b345d8e88afa015075c945637c07e8f20` (r:1 w:1) - fn on_idle_large_msg() -> Weight { + /// Storage: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6bedc49980ba3aa32b0a189290fd036649` (r:1 w:1) + /// Proof: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6bedc49980ba3aa32b0a189290fd036649` (r:1 w:1) + fn on_idle_large_msg() -> Weight { // Proof Size summary in bytes: // Measured: `65710` // Estimated: `69175` - // Minimum execution time: 49_000_000 picoseconds. - Weight::from_parts(55_000_000, 0) + // Minimum execution time: 52_555_000 picoseconds. + Weight::from_parts(54_052_000, 0) .saturating_add(Weight::from_parts(0, 69175)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/frame_system.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/frame_system.rs index 46f8113939e..b4b7cbf05a5 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/frame_system.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/frame_system.rs @@ -1,4 +1,4 @@ -// Copyright Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. // Cumulus is free software: you can redistribute it and/or modify @@ -16,26 +16,28 @@ //! Autogenerated weights for `frame_system` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-05, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("coretime-westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./artifacts/westend-parachain +// ./target/production/polkadot-parachain // benchmark // pallet // --chain=coretime-westend-dev -// --execution=wasm // --wasm-execution=compiled // --pallet=frame_system +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/frame_system.rs +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -53,80 +55,99 @@ impl frame_system::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_432_000 picoseconds. - Weight::from_parts(2_458_000, 0) + // Minimum execution time: 1_584_000 picoseconds. + Weight::from_parts(2_117_975, 0) .saturating_add(Weight::from_parts(0, 0)) // Standard Error: 0 - .saturating_add(Weight::from_parts(367, 0).saturating_mul(b.into())) + .saturating_add(Weight::from_parts(384, 0).saturating_mul(b.into())) } /// The range of component `b` is `[0, 3932160]`. fn remark_with_event(b: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_911_000 picoseconds. - Weight::from_parts(8_031_000, 0) + // Minimum execution time: 4_607_000 picoseconds. + Weight::from_parts(14_948_582, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 0 - .saturating_add(Weight::from_parts(1_405, 0).saturating_mul(b.into())) + // Standard Error: 3 + .saturating_add(Weight::from_parts(1_673, 0).saturating_mul(b.into())) } - /// Storage: System Digest (r:1 w:1) - /// Proof Skipped: System Digest (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: unknown `0x3a686561707061676573` (r:0 w:1) - /// Proof Skipped: unknown `0x3a686561707061676573` (r:0 w:1) + /// Storage: `System::Digest` (r:1 w:1) + /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: UNKNOWN KEY `0x3a686561707061676573` (r:0 w:1) + /// Proof: UNKNOWN KEY `0x3a686561707061676573` (r:0 w:1) fn set_heap_pages() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `1485` - // Minimum execution time: 4_304_000 picoseconds. - Weight::from_parts(4_553_000, 0) + // Minimum execution time: 2_681_000 picoseconds. + Weight::from_parts(2_877_000, 0) .saturating_add(Weight::from_parts(0, 1485)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::UpgradeRestrictionSignal` (r:1 w:0) + /// Proof: `ParachainSystem::UpgradeRestrictionSignal` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingValidationCode` (r:1 w:1) + /// Proof: `ParachainSystem::PendingValidationCode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::NewValidationCode` (r:0 w:1) + /// Proof: `ParachainSystem::NewValidationCode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::DidSetValidationCode` (r:0 w:1) + /// Proof: `ParachainSystem::DidSetValidationCode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn set_code() -> Weight { - Weight::from_parts(1_000_000, 0) + // Proof Size summary in bytes: + // Measured: `164` + // Estimated: `1649` + // Minimum execution time: 95_893_701_000 picoseconds. + Weight::from_parts(98_086_094_000, 0) + .saturating_add(Weight::from_parts(0, 1649)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: Skipped Metadata (r:0 w:0) - /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `i` is `[0, 1000]`. fn set_storage(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_493_000 picoseconds. - Weight::from_parts(2_523_000, 0) + // Minimum execution time: 1_597_000 picoseconds. + Weight::from_parts(1_660_000, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 1_594 - .saturating_add(Weight::from_parts(663_439, 0).saturating_mul(i.into())) + // Standard Error: 1_871 + .saturating_add(Weight::from_parts(748_346, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) } - /// Storage: Skipped Metadata (r:0 w:0) - /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `i` is `[0, 1000]`. fn kill_storage(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_492_000 picoseconds. - Weight::from_parts(2_526_000, 0) + // Minimum execution time: 1_625_000 picoseconds. + Weight::from_parts(1_669_000, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 784 - .saturating_add(Weight::from_parts(493_844, 0).saturating_mul(i.into())) + // Standard Error: 903 + .saturating_add(Weight::from_parts(561_709, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) } - /// Storage: Skipped Metadata (r:0 w:0) - /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `p` is `[0, 1000]`. fn kill_prefix(p: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `68 + p * (69 ±0)` - // Estimated: `66 + p * (70 ±0)` - // Minimum execution time: 4_200_000 picoseconds. - Weight::from_parts(4_288_000, 0) - .saturating_add(Weight::from_parts(0, 66)) - // Standard Error: 1_195 - .saturating_add(Weight::from_parts(1_021_563, 0).saturating_mul(p.into())) + // Measured: `71 + p * (69 ±0)` + // Estimated: `72 + p * (70 ±0)` + // Minimum execution time: 3_306_000 picoseconds. + Weight::from_parts(3_412_000, 0) + .saturating_add(Weight::from_parts(0, 72)) + // Standard Error: 1_366 + .saturating_add(Weight::from_parts(1_138_953, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(p.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into()))) .saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into())) @@ -137,25 +158,33 @@ impl frame_system::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 33_027_000 picoseconds. - Weight::from_parts(33_027_000, 0) + // Minimum execution time: 7_834_000 picoseconds. + Weight::from_parts(8_344_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `System::AuthorizedUpgrade` (r:1 w:1) /// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) - /// Storage: `System::Digest` (r:1 w:1) - /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: UNKNOWN KEY `0x3a636f6465` (r:0 w:1) - /// Proof: UNKNOWN KEY `0x3a636f6465` (r:0 w:1) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::UpgradeRestrictionSignal` (r:1 w:0) + /// Proof: `ParachainSystem::UpgradeRestrictionSignal` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingValidationCode` (r:1 w:1) + /// Proof: `ParachainSystem::PendingValidationCode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::NewValidationCode` (r:0 w:1) + /// Proof: `ParachainSystem::NewValidationCode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::DidSetValidationCode` (r:0 w:1) + /// Proof: `ParachainSystem::DidSetValidationCode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn apply_authorized_upgrade() -> Weight { // Proof Size summary in bytes: - // Measured: `22` - // Estimated: `1518` - // Minimum execution time: 118_101_992_000 picoseconds. - Weight::from_parts(118_101_992_000, 0) - .saturating_add(Weight::from_parts(0, 1518)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(3)) + // Measured: `186` + // Estimated: `1671` + // Minimum execution time: 98_682_277_000 picoseconds. + Weight::from_parts(101_609_257_000, 0) + .saturating_add(Weight::from_parts(0, 1671)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs index 28e708f0e2a..f1050b3ae63 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2023 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,6 +23,7 @@ pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod frame_system; pub mod pallet_balances; +pub mod pallet_broker; pub mod pallet_collator_selection; pub mod pallet_message_queue; pub mod pallet_multisig; diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_balances.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_balances.rs index 2fc1495c804..c4770a7c943 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_balances.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_balances.rs @@ -17,23 +17,25 @@ //! Autogenerated weights for `pallet_balances` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-01-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-8idpd4bs-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: -// target/production/polkadot-parachain +// ./target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 -// --extrinsic=* +// --chain=coretime-westend-dev // --wasm-execution=compiled -// --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json // --pallet=pallet_balances -// --chain=coretime-westend-dev +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json // --header=./cumulus/file_header.txt // --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ @@ -54,8 +56,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 41_147_000 picoseconds. - Weight::from_parts(41_829_000, 0) + // Minimum execution time: 42_773_000 picoseconds. + Weight::from_parts(43_292_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -66,8 +68,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 32_566_000 picoseconds. - Weight::from_parts(33_012_000, 0) + // Minimum execution time: 34_023_000 picoseconds. + Weight::from_parts(34_513_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -78,8 +80,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 11_435_000 picoseconds. - Weight::from_parts(11_717_000, 0) + // Minimum execution time: 11_685_000 picoseconds. + Weight::from_parts(12_103_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -90,8 +92,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 15_941_000 picoseconds. - Weight::from_parts(16_341_000, 0) + // Minimum execution time: 16_233_000 picoseconds. + Weight::from_parts(16_706_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -102,8 +104,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `6196` - // Minimum execution time: 42_592_000 picoseconds. - Weight::from_parts(43_111_000, 0) + // Minimum execution time: 43_909_000 picoseconds. + Weight::from_parts(44_683_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -114,8 +116,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 40_925_000 picoseconds. - Weight::from_parts(41_743_000, 0) + // Minimum execution time: 42_081_000 picoseconds. + Weight::from_parts(42_553_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -126,8 +128,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 14_117_000 picoseconds. - Weight::from_parts(14_418_000, 0) + // Minimum execution time: 14_413_000 picoseconds. + Weight::from_parts(14_827_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -139,11 +141,11 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0 + u * (136 ±0)` // Estimated: `990 + u * (2603 ±0)` - // Minimum execution time: 13_855_000 picoseconds. - Weight::from_parts(14_108_000, 0) + // Minimum execution time: 14_189_000 picoseconds. + Weight::from_parts(14_587_000, 0) .saturating_add(Weight::from_parts(0, 990)) - // Standard Error: 11_673 - .saturating_add(Weight::from_parts(12_675_264, 0).saturating_mul(u.into())) + // Standard Error: 10_909 + .saturating_add(Weight::from_parts(13_040_864, 0).saturating_mul(u.into())) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(u.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(u.into()))) .saturating_add(Weight::from_parts(0, 2603).saturating_mul(u.into())) @@ -154,8 +156,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `1501` - // Minimum execution time: 4_820_000 picoseconds. - Weight::from_parts(5_152_000, 0) + // Minimum execution time: 5_218_000 picoseconds. + Weight::from_parts(5_562_000, 0) .saturating_add(Weight::from_parts(0, 1501)) .saturating_add(T::DbWeight::get().reads(1)) } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_broker.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_broker.rs new file mode 100644 index 00000000000..8727b9633b1 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_broker.rs @@ -0,0 +1,516 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `pallet_broker` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// ./target/production/polkadot-parachain +// benchmark +// pallet +// --chain=coretime-westend-dev +// --wasm-execution=compiled +// --pallet=pallet_broker +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_broker`. +pub struct WeightInfo(PhantomData); +impl pallet_broker::WeightInfo for WeightInfo { + /// Storage: `Broker::Configuration` (r:0 w:1) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + fn configure() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_944_000 picoseconds. + Weight::from_parts(2_045_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Reservations` (r:1 w:1) + /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(12021), added: 12516, mode: `MaxEncodedLen`) + fn reserve() -> Weight { + // Proof Size summary in bytes: + // Measured: `10888` + // Estimated: `13506` + // Minimum execution time: 21_158_000 picoseconds. + Weight::from_parts(21_572_000, 0) + .saturating_add(Weight::from_parts(0, 13506)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Reservations` (r:1 w:1) + /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(12021), added: 12516, mode: `MaxEncodedLen`) + fn unreserve() -> Weight { + // Proof Size summary in bytes: + // Measured: `12090` + // Estimated: `13506` + // Minimum execution time: 20_497_000 picoseconds. + Weight::from_parts(20_995_000, 0) + .saturating_add(Weight::from_parts(0, 13506)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Leases` (r:1 w:1) + /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(81), added: 576, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::LastRelayChainBlockNumber` (r:1 w:0) + /// Proof: `ParachainSystem::LastRelayChainBlockNumber` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn set_lease() -> Weight { + // Proof Size summary in bytes: + // Measured: `146` + // Estimated: `1631` + // Minimum execution time: 10_280_000 picoseconds. + Weight::from_parts(10_686_000, 0) + .saturating_add(Weight::from_parts(0, 1631)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::LastRelayChainBlockNumber` (r:1 w:0) + /// Proof: `ParachainSystem::LastRelayChainBlockNumber` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Broker::InstaPoolIo` (r:3 w:3) + /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Broker::Reservations` (r:1 w:0) + /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(12021), added: 12516, mode: `MaxEncodedLen`) + /// Storage: `Broker::Leases` (r:1 w:1) + /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(81), added: 576, mode: `MaxEncodedLen`) + /// Storage: `Broker::SaleInfo` (r:0 w:1) + /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:0 w:1) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:0 w:20) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + /// The range of component `n` is `[0, 1000]`. + fn start_sales(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `12247` + // Estimated: `13732` + // Minimum execution time: 61_020_000 picoseconds. + Weight::from_parts(63_240_622, 0) + .saturating_add(Weight::from_parts(0, 13732)) + // Standard Error: 102 + .saturating_add(Weight::from_parts(255, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(26)) + } + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::SaleInfo` (r:1 w:1) + /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:0) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Broker::Regions` (r:0 w:1) + /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + fn purchase() -> Weight { + // Proof Size summary in bytes: + // Measured: `316` + // Estimated: `3593` + // Minimum execution time: 30_627_000 picoseconds. + Weight::from_parts(31_648_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::SaleInfo` (r:1 w:1) + /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `Broker::AllowedRenewals` (r:1 w:2) + /// Proof: `Broker::AllowedRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:0) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:0 w:1) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + fn renew() -> Weight { + // Proof Size summary in bytes: + // Measured: `434` + // Estimated: `4698` + // Minimum execution time: 57_701_000 picoseconds. + Weight::from_parts(59_825_000, 0) + .saturating_add(Weight::from_parts(0, 4698)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `Broker::Regions` (r:1 w:1) + /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + fn transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `357` + // Estimated: `3550` + // Minimum execution time: 12_898_000 picoseconds. + Weight::from_parts(13_506_000, 0) + .saturating_add(Weight::from_parts(0, 3550)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Regions` (r:1 w:2) + /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + fn partition() -> Weight { + // Proof Size summary in bytes: + // Measured: `357` + // Estimated: `3550` + // Minimum execution time: 14_284_000 picoseconds. + Weight::from_parts(14_791_000, 0) + .saturating_add(Weight::from_parts(0, 3550)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Broker::Regions` (r:1 w:3) + /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + fn interlace() -> Weight { + // Proof Size summary in bytes: + // Measured: `357` + // Estimated: `3550` + // Minimum execution time: 15_570_000 picoseconds. + Weight::from_parts(16_158_000, 0) + .saturating_add(Weight::from_parts(0, 3550)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::Regions` (r:1 w:1) + /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:1 w:1) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + fn assign() -> Weight { + // Proof Size summary in bytes: + // Measured: `735` + // Estimated: `4681` + // Minimum execution time: 23_329_000 picoseconds. + Weight::from_parts(24_196_000, 0) + .saturating_add(Weight::from_parts(0, 4681)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::Regions` (r:1 w:1) + /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:1 w:1) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + /// Storage: `Broker::InstaPoolIo` (r:2 w:2) + /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Broker::InstaPoolContribution` (r:0 w:1) + /// Proof: `Broker::InstaPoolContribution` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + fn pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `801` + // Estimated: `5996` + // Minimum execution time: 29_288_000 picoseconds. + Weight::from_parts(30_066_000, 0) + .saturating_add(Weight::from_parts(0, 5996)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(5)) + } + /// Storage: `Broker::InstaPoolContribution` (r:1 w:1) + /// Proof: `Broker::InstaPoolContribution` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `Broker::InstaPoolHistory` (r:3 w:1) + /// Proof: `Broker::InstaPoolHistory` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `m` is `[1, 3]`. + fn claim_revenue(m: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `652` + // Estimated: `6196 + m * (2520 ±0)` + // Minimum execution time: 54_833_000 picoseconds. + Weight::from_parts(55_577_423, 0) + .saturating_add(Weight::from_parts(0, 6196)) + // Standard Error: 35_105 + .saturating_add(Weight::from_parts(1_267_911, 0).saturating_mul(m.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(m.into()))) + .saturating_add(T::DbWeight::get().writes(5)) + .saturating_add(Weight::from_parts(0, 2520).saturating_mul(m.into())) + } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn purchase_credit() -> Weight { + // Proof Size summary in bytes: + // Measured: `215` + // Estimated: `3680` + // Minimum execution time: 55_289_000 picoseconds. + Weight::from_parts(56_552_000, 0) + .saturating_add(Weight::from_parts(0, 3680)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::Regions` (r:1 w:1) + /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + fn drop_region() -> Weight { + // Proof Size summary in bytes: + // Measured: `465` + // Estimated: `3550` + // Minimum execution time: 39_736_000 picoseconds. + Weight::from_parts(41_346_000, 0) + .saturating_add(Weight::from_parts(0, 3550)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::InstaPoolContribution` (r:1 w:1) + /// Proof: `Broker::InstaPoolContribution` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + fn drop_contribution() -> Weight { + // Proof Size summary in bytes: + // Measured: `463` + // Estimated: `3533` + // Minimum execution time: 57_319_000 picoseconds. + Weight::from_parts(60_204_000, 0) + .saturating_add(Weight::from_parts(0, 3533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::InstaPoolHistory` (r:1 w:1) + /// Proof: `Broker::InstaPoolHistory` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:0) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn drop_history() -> Weight { + // Proof Size summary in bytes: + // Measured: `857` + // Estimated: `3593` + // Minimum execution time: 85_216_000 picoseconds. + Weight::from_parts(91_144_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::AllowedRenewals` (r:1 w:1) + /// Proof: `Broker::AllowedRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`) + fn drop_renewal() -> Weight { + // Proof Size summary in bytes: + // Measured: `556` + // Estimated: `4698` + // Minimum execution time: 32_331_000 picoseconds. + Weight::from_parts(39_877_000, 0) + .saturating_add(Weight::from_parts(0, 4698)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// The range of component `n` is `[0, 1000]`. + fn request_core_count(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `74` + // Estimated: `3539` + // Minimum execution time: 18_128_000 picoseconds. + Weight::from_parts(19_061_234, 0) + .saturating_add(Weight::from_parts(0, 3539)) + // Standard Error: 48 + .saturating_add(Weight::from_parts(141, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Broker::CoreCountInbox` (r:1 w:1) + /// Proof: `Broker::CoreCountInbox` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// The range of component `n` is `[0, 1000]`. + fn process_core_count(_n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `266` + // Estimated: `1487` + // Minimum execution time: 5_368_000 picoseconds. + Weight::from_parts(5_837_005, 0) + .saturating_add(Weight::from_parts(0, 1487)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) + /// Proof: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) + /// Storage: `Broker::InstaPoolHistory` (r:1 w:1) + /// Proof: `Broker::InstaPoolHistory` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn process_revenue() -> Weight { + // Proof Size summary in bytes: + // Measured: `447` + // Estimated: `6196` + // Minimum execution time: 36_047_000 picoseconds. + Weight::from_parts(37_101_000, 0) + .saturating_add(Weight::from_parts(0, 6196)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `Broker::InstaPoolIo` (r:3 w:3) + /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Broker::Reservations` (r:1 w:0) + /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(12021), added: 12516, mode: `MaxEncodedLen`) + /// Storage: `Broker::Leases` (r:1 w:1) + /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(81), added: 576, mode: `MaxEncodedLen`) + /// Storage: `Broker::SaleInfo` (r:0 w:1) + /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:0 w:20) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + /// The range of component `n` is `[0, 1000]`. + fn rotate_sale(_n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `12194` + // Estimated: `13506` + // Minimum execution time: 48_158_000 picoseconds. + Weight::from_parts(49_891_920, 0) + .saturating_add(Weight::from_parts(0, 13506)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(25)) + } + /// Storage: `Broker::InstaPoolIo` (r:1 w:0) + /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Broker::InstaPoolHistory` (r:0 w:1) + /// Proof: `Broker::InstaPoolHistory` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) + fn process_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `42` + // Estimated: `3493` + // Minimum execution time: 5_911_000 picoseconds. + Weight::from_parts(6_173_000, 0) + .saturating_add(Weight::from_parts(0, 3493)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Workplan` (r:1 w:1) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workload` (r:1 w:1) + /// Proof: `Broker::Workload` (`max_values`: None, `max_size`: Some(1212), added: 3687, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn process_core_schedule() -> Weight { + // Proof Size summary in bytes: + // Measured: `1321` + // Estimated: `4786` + // Minimum execution time: 30_140_000 picoseconds. + Weight::from_parts(30_912_000, 0) + .saturating_add(Weight::from_parts(0, 4786)) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn request_revenue_info_at() -> Weight { + // Proof Size summary in bytes: + // Measured: `74` + // Estimated: `3539` + // Minimum execution time: 13_684_000 picoseconds. + Weight::from_parts(14_252_000, 0) + .saturating_add(Weight::from_parts(0, 3539)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Broker::CoreCountInbox` (r:0 w:1) + /// Proof: `Broker::CoreCountInbox` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + fn notify_core_count() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_718_000 picoseconds. + Weight::from_parts(1_843_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Status` (r:1 w:1) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `Broker::CoreCountInbox` (r:1 w:0) + /// Proof: `Broker::CoreCountInbox` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) + /// Proof: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn do_tick_base() -> Weight { + // Proof Size summary in bytes: + // Measured: `398` + // Estimated: `3863` + // Minimum execution time: 11_771_000 picoseconds. + Weight::from_parts(12_120_000, 0) + .saturating_add(Weight::from_parts(0, 3863)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_collator_selection.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_collator_selection.rs index 2adddecab26..2341890c314 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_collator_selection.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_collator_selection.rs @@ -1,4 +1,4 @@ -// Copyright Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. // Cumulus is free software: you can redistribute it and/or modify @@ -16,26 +16,28 @@ //! Autogenerated weights for `pallet_collator_selection` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("coretime-westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./artifacts/westend-parachain +// ./target/production/polkadot-parachain // benchmark // pallet // --chain=coretime-westend-dev -// --execution=wasm // --wasm-execution=compiled // --pallet=pallet_collator_selection +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_collator_selection.rs +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -48,196 +50,236 @@ use core::marker::PhantomData; /// Weight functions for `pallet_collator_selection`. pub struct WeightInfo(PhantomData); impl pallet_collator_selection::WeightInfo for WeightInfo { - /// Storage: Session NextKeys (r:100 w:0) - /// Proof Skipped: Session NextKeys (max_values: None, max_size: None, mode: Measured) - /// Storage: CollatorSelection Invulnerables (r:0 w:1) - /// Proof: CollatorSelection Invulnerables (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) - /// The range of component `b` is `[1, 100]`. + /// Storage: `Session::NextKeys` (r:20 w:0) + /// Proof: `Session::NextKeys` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `CollatorSelection::Invulnerables` (r:0 w:1) + /// Proof: `CollatorSelection::Invulnerables` (`max_values`: Some(1), `max_size`: Some(641), added: 1136, mode: `MaxEncodedLen`) + /// The range of component `b` is `[1, 20]`. fn set_invulnerables(b: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `214 + b * (78 ±0)` - // Estimated: `1203 + b * (2554 ±0)` - // Minimum execution time: 14_426_000 picoseconds. - Weight::from_parts(14_971_974, 0) - .saturating_add(Weight::from_parts(0, 1203)) - // Standard Error: 2_914 - .saturating_add(Weight::from_parts(2_604_699, 0).saturating_mul(b.into())) + // Measured: `164 + b * (79 ±0)` + // Estimated: `1155 + b * (2555 ±0)` + // Minimum execution time: 11_038_000 picoseconds. + Weight::from_parts(8_347_616, 0) + .saturating_add(Weight::from_parts(0, 1155)) + // Standard Error: 5_166 + .saturating_add(Weight::from_parts(3_025_311, 0).saturating_mul(b.into())) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(b.into()))) .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(Weight::from_parts(0, 2554).saturating_mul(b.into())) + .saturating_add(Weight::from_parts(0, 2555).saturating_mul(b.into())) } - /// Storage: CollatorSelection DesiredCandidates (r:0 w:1) - /// Proof: CollatorSelection DesiredCandidates (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - fn set_desired_candidates() -> Weight { + /// Storage: `Session::NextKeys` (r:1 w:0) + /// Proof: `Session::NextKeys` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `CollatorSelection::Invulnerables` (r:1 w:1) + /// Proof: `CollatorSelection::Invulnerables` (`max_values`: Some(1), `max_size`: Some(641), added: 1136, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::CandidateList` (r:1 w:1) + /// Proof: `CollatorSelection::CandidateList` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `b` is `[1, 19]`. + /// The range of component `c` is `[1, 99]`. + fn add_invulnerable(b: u32, c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 6_977_000 picoseconds. - Weight::from_parts(7_246_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `720 + b * (32 ±0) + c * (53 ±0)` + // Estimated: `6287 + b * (37 ±0) + c * (53 ±0)` + // Minimum execution time: 36_983_000 picoseconds. + Weight::from_parts(37_900_558, 0) + .saturating_add(Weight::from_parts(0, 6287)) + // Standard Error: 6_860 + .saturating_add(Weight::from_parts(94_160, 0).saturating_mul(b.into())) + // Standard Error: 1_300 + .saturating_add(Weight::from_parts(119_010, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(Weight::from_parts(0, 37).saturating_mul(b.into())) + .saturating_add(Weight::from_parts(0, 53).saturating_mul(c.into())) + } + /// Storage: `CollatorSelection::CandidateList` (r:1 w:0) + /// Proof: `CollatorSelection::CandidateList` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::Invulnerables` (r:1 w:1) + /// Proof: `CollatorSelection::Invulnerables` (`max_values`: Some(1), `max_size`: Some(641), added: 1136, mode: `MaxEncodedLen`) + /// The range of component `b` is `[5, 20]`. + fn remove_invulnerable(b: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `82 + b * (32 ±0)` + // Estimated: `6287` + // Minimum execution time: 10_432_000 picoseconds. + Weight::from_parts(10_460_489, 0) + .saturating_add(Weight::from_parts(0, 6287)) + // Standard Error: 2_803 + .saturating_add(Weight::from_parts(143_162, 0).saturating_mul(b.into())) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `CollatorSelection::CandidacyBond` (r:0 w:1) - /// Proof: `CollatorSelection::CandidacyBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) - fn set_candidacy_bond(_c: u32, _k: u32) -> Weight { + /// Storage: `CollatorSelection::DesiredCandidates` (r:0 w:1) + /// Proof: `CollatorSelection::DesiredCandidates` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn set_desired_candidates() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_937_000 picoseconds. - Weight::from_parts(8_161_000, 0) + // Minimum execution time: 4_302_000 picoseconds. + Weight::from_parts(4_508_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: CollatorSelection Candidates (r:1 w:1) - /// Proof: CollatorSelection Candidates (max_values: Some(1), max_size: Some(48002), added: 48497, mode: MaxEncodedLen) - /// Storage: CollatorSelection DesiredCandidates (r:1 w:0) - /// Proof: CollatorSelection DesiredCandidates (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: CollatorSelection Invulnerables (r:1 w:0) - /// Proof: CollatorSelection Invulnerables (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) - /// Storage: Session NextKeys (r:1 w:0) - /// Proof Skipped: Session NextKeys (max_values: None, max_size: None, mode: Measured) - /// Storage: CollatorSelection CandidacyBond (r:1 w:0) - /// Proof: CollatorSelection CandidacyBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: CollatorSelection LastAuthoredBlock (r:0 w:1) - /// Proof: CollatorSelection LastAuthoredBlock (max_values: None, max_size: Some(44), added: 2519, mode: MaxEncodedLen) - /// The range of component `c` is `[1, 999]`. - fn register_as_candidate(c: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `1104 + c * (48 ±0)` - // Estimated: `49487 + c * (49 ±0)` - // Minimum execution time: 42_275_000 picoseconds. - Weight::from_parts(33_742_215, 0) - .saturating_add(Weight::from_parts(0, 49487)) - // Standard Error: 1_291 - .saturating_add(Weight::from_parts(103_381, 0).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(2)) - .saturating_add(Weight::from_parts(0, 49).saturating_mul(c.into())) - } - /// Storage: CollatorSelection Candidates (r:1 w:1) - /// Proof: CollatorSelection Candidates (max_values: Some(1), max_size: Some(48002), added: 48497, mode: MaxEncodedLen) - /// Storage: CollatorSelection LastAuthoredBlock (r:0 w:1) - /// Proof: CollatorSelection LastAuthoredBlock (max_values: None, max_size: Some(44), added: 2519, mode: MaxEncodedLen) - /// The range of component `c` is `[6, 1000]`. - fn leave_intent(c: u32, ) -> Weight { + /// Storage: `CollatorSelection::CandidacyBond` (r:1 w:1) + /// Proof: `CollatorSelection::CandidacyBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::CandidateList` (r:1 w:1) + /// Proof: `CollatorSelection::CandidateList` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:100 w:100) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::LastAuthoredBlock` (r:0 w:100) + /// Proof: `CollatorSelection::LastAuthoredBlock` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + /// The range of component `c` is `[0, 100]`. + /// The range of component `k` is `[0, 100]`. + fn set_candidacy_bond(c: u32, k: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `428 + c * (48 ±0)` - // Estimated: `49487` - // Minimum execution time: 33_404_000 picoseconds. - Weight::from_parts(22_612_617, 0) - .saturating_add(Weight::from_parts(0, 49487)) - // Standard Error: 1_341 - .saturating_add(Weight::from_parts(105_669, 0).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `0 + c * (180 ±0) + k * (112 ±0)` + // Estimated: `6287 + c * (901 ±29) + k * (901 ±29)` + // Minimum execution time: 7_712_000 picoseconds. + Weight::from_parts(7_935_000, 0) + .saturating_add(Weight::from_parts(0, 6287)) + // Standard Error: 153_204 + .saturating_add(Weight::from_parts(5_173_626, 0).saturating_mul(c.into())) + // Standard Error: 153_204 + .saturating_add(Weight::from_parts(4_883_030, 0).saturating_mul(k.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(c.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) + .saturating_add(Weight::from_parts(0, 901).saturating_mul(c.into())) + .saturating_add(Weight::from_parts(0, 901).saturating_mul(k.into())) } + /// Storage: `CollatorSelection::CandidacyBond` (r:1 w:0) + /// Proof: `CollatorSelection::CandidacyBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::CandidateList` (r:1 w:1) + /// Proof: `CollatorSelection::CandidateList` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, mode: `MaxEncodedLen`) + /// The range of component `c` is `[4, 100]`. fn update_bond(c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `306 + c * (50 ±0)` + // Measured: `250 + c * (50 ±0)` // Estimated: `6287` - // Minimum execution time: 34_814_000 picoseconds. - Weight::from_parts(36_371_520, 0) + // Minimum execution time: 22_767_000 picoseconds. + Weight::from_parts(25_594_856, 0) .saturating_add(Weight::from_parts(0, 6287)) - // Standard Error: 2_391 - .saturating_add(Weight::from_parts(201_700, 0).saturating_mul(c.into())) + // Standard Error: 1_814 + .saturating_add(Weight::from_parts(110_451, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `CollatorSelection::CandidateList` (r:1 w:1) + /// Proof: `CollatorSelection::CandidateList` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::Invulnerables` (r:1 w:0) + /// Proof: `CollatorSelection::Invulnerables` (`max_values`: Some(1), `max_size`: Some(641), added: 1136, mode: `MaxEncodedLen`) + /// Storage: `Session::NextKeys` (r:1 w:0) + /// Proof: `Session::NextKeys` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `CollatorSelection::CandidacyBond` (r:1 w:0) + /// Proof: `CollatorSelection::CandidacyBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::LastAuthoredBlock` (r:0 w:1) + /// Proof: `CollatorSelection::LastAuthoredBlock` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + /// The range of component `c` is `[1, 99]`. + fn register_as_candidate(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `687 + c * (52 ±0)` + // Estimated: `6287 + c * (54 ±0)` + // Minimum execution time: 30_792_000 picoseconds. + Weight::from_parts(34_485_582, 0) + .saturating_add(Weight::from_parts(0, 6287)) + // Standard Error: 2_421 + .saturating_add(Weight::from_parts(152_013, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(Weight::from_parts(0, 54).saturating_mul(c.into())) } + /// Storage: `CollatorSelection::Invulnerables` (r:1 w:0) + /// Proof: `CollatorSelection::Invulnerables` (`max_values`: Some(1), `max_size`: Some(641), added: 1136, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::CandidacyBond` (r:1 w:0) + /// Proof: `CollatorSelection::CandidacyBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `Session::NextKeys` (r:1 w:0) + /// Proof: `Session::NextKeys` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `CollatorSelection::CandidateList` (r:1 w:1) + /// Proof: `CollatorSelection::CandidateList` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::LastAuthoredBlock` (r:0 w:2) + /// Proof: `CollatorSelection::LastAuthoredBlock` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + /// The range of component `c` is `[4, 100]`. fn take_candidate_slot(c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `306 + c * (50 ±0)` + // Measured: `855 + c * (52 ±0)` + // Estimated: `6287 + c * (55 ±0)` + // Minimum execution time: 45_538_000 picoseconds. + Weight::from_parts(50_758_223, 0) + .saturating_add(Weight::from_parts(0, 6287)) + // Standard Error: 2_779 + .saturating_add(Weight::from_parts(149_419, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(Weight::from_parts(0, 55).saturating_mul(c.into())) + } + /// Storage: `CollatorSelection::CandidateList` (r:1 w:1) + /// Proof: `CollatorSelection::CandidateList` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::Invulnerables` (r:1 w:0) + /// Proof: `CollatorSelection::Invulnerables` (`max_values`: Some(1), `max_size`: Some(641), added: 1136, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::LastAuthoredBlock` (r:0 w:1) + /// Proof: `CollatorSelection::LastAuthoredBlock` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + /// The range of component `c` is `[4, 100]`. + fn leave_intent(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `277 + c * (48 ±0)` // Estimated: `6287` - // Minimum execution time: 34_814_000 picoseconds. - Weight::from_parts(36_371_520, 0) + // Minimum execution time: 26_356_000 picoseconds. + Weight::from_parts(29_910_328, 0) .saturating_add(Weight::from_parts(0, 6287)) - // Standard Error: 2_391 - .saturating_add(Weight::from_parts(201_700, 0).saturating_mul(c.into())) + // Standard Error: 2_159 + .saturating_add(Weight::from_parts(123_421, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: System Account (r:2 w:2) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) - /// Storage: System BlockWeight (r:1 w:1) - /// Proof: System BlockWeight (max_values: Some(1), max_size: Some(48), added: 543, mode: MaxEncodedLen) - /// Storage: CollatorSelection LastAuthoredBlock (r:0 w:1) - /// Proof: CollatorSelection LastAuthoredBlock (max_values: None, max_size: Some(44), added: 2519, mode: MaxEncodedLen) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::LastAuthoredBlock` (r:0 w:1) + /// Proof: `CollatorSelection::LastAuthoredBlock` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn note_author() -> Weight { // Proof Size summary in bytes: - // Measured: `155` + // Measured: `103` // Estimated: `6196` - // Minimum execution time: 44_415_000 picoseconds. - Weight::from_parts(44_732_000, 0) + // Minimum execution time: 36_377_000 picoseconds. + Weight::from_parts(37_121_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(4)) } - /// Storage: Session NextKeys (r:1 w:0) - /// Proof Skipped: Session NextKeys (max_values: None, max_size: None, mode: Measured) - /// Storage: CollatorSelection Invulnerables (r:1 w:1) - /// Proof: CollatorSelection Invulnerables (max_values: Some(1), max_size: Some(641), added: 1136, mode: MaxEncodedLen) - /// Storage: CollatorSelection Candidates (r:1 w:1) - /// Proof: CollatorSelection Candidates (max_values: Some(1), max_size: Some(4802), added: 5297, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) - /// The range of component `b` is `[1, 19]`. - /// The range of component `c` is `[1, 99]`. - fn add_invulnerable(b: u32, c: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `757 + b * (32 ±0) + c * (53 ±0)` - // Estimated: `6287 + b * (37 ±0) + c * (53 ±0)` - // Minimum execution time: 52_720_000 picoseconds. - Weight::from_parts(56_102_459, 0) - .saturating_add(Weight::from_parts(0, 6287)) - // Standard Error: 12_957 - .saturating_add(Weight::from_parts(26_422, 0).saturating_mul(b.into())) - // Standard Error: 2_456 - .saturating_add(Weight::from_parts(128_528, 0).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) - .saturating_add(Weight::from_parts(0, 37).saturating_mul(b.into())) - .saturating_add(Weight::from_parts(0, 53).saturating_mul(c.into())) - } - /// Storage: CollatorSelection Invulnerables (r:1 w:1) - /// Proof: CollatorSelection Invulnerables (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) - /// The range of component `b` is `[1, 100]`. - fn remove_invulnerable(b: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `119 + b * (32 ±0)` - // Estimated: `4687` - // Minimum execution time: 183_054_000 picoseconds. - Weight::from_parts(197_205_427, 0) - .saturating_add(Weight::from_parts(0, 4687)) - // Standard Error: 13_533 - .saturating_add(Weight::from_parts(376_231, 0).saturating_mul(b.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: CollatorSelection Candidates (r:1 w:0) - /// Proof: CollatorSelection Candidates (max_values: Some(1), max_size: Some(48002), added: 48497, mode: MaxEncodedLen) - /// Storage: CollatorSelection LastAuthoredBlock (r:999 w:0) - /// Proof: CollatorSelection LastAuthoredBlock (max_values: None, max_size: Some(44), added: 2519, mode: MaxEncodedLen) - /// Storage: CollatorSelection Invulnerables (r:1 w:0) - /// Proof: CollatorSelection Invulnerables (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) - /// Storage: System BlockWeight (r:1 w:1) - /// Proof: System BlockWeight (max_values: Some(1), max_size: Some(48), added: 543, mode: MaxEncodedLen) - /// Storage: System Account (r:995 w:995) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) - /// The range of component `r` is `[1, 1000]`. - /// The range of component `c` is `[1, 1000]`. + /// Storage: `CollatorSelection::CandidateList` (r:1 w:0) + /// Proof: `CollatorSelection::CandidateList` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::LastAuthoredBlock` (r:100 w:0) + /// Proof: `CollatorSelection::LastAuthoredBlock` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::Invulnerables` (r:1 w:0) + /// Proof: `CollatorSelection::Invulnerables` (`max_values`: Some(1), `max_size`: Some(641), added: 1136, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::DesiredCandidates` (r:1 w:0) + /// Proof: `CollatorSelection::DesiredCandidates` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:97 w:97) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `r` is `[1, 100]`. + /// The range of component `c` is `[1, 100]`. fn new_session(r: u32, c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `22815 + c * (97 ±0) + r * (116 ±0)` - // Estimated: `49487 + c * (2519 ±0) + r * (2602 ±0)` - // Minimum execution time: 16_765_000 picoseconds. - Weight::from_parts(16_997_000, 0) - .saturating_add(Weight::from_parts(0, 49487)) - // Standard Error: 860_677 - .saturating_add(Weight::from_parts(30_463_094, 0).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `2143 + c * (97 ±0) + r * (112 ±0)` + // Estimated: `6287 + c * (2519 ±0) + r * (2603 ±0)` + // Minimum execution time: 15_761_000 picoseconds. + Weight::from_parts(16_078_000, 0) + .saturating_add(Weight::from_parts(0, 6287)) + // Standard Error: 270_522 + .saturating_add(Weight::from_parts(11_903_266, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(c.into()))) .saturating_add(T::DbWeight::get().writes(1)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(c.into()))) .saturating_add(Weight::from_parts(0, 2519).saturating_mul(c.into())) - .saturating_add(Weight::from_parts(0, 2602).saturating_mul(r.into())) + .saturating_add(Weight::from_parts(0, 2603).saturating_mul(r.into())) } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_message_queue.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_message_queue.rs index 651f27e10e5..e9cdc247876 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_message_queue.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_message_queue.rs @@ -1,4 +1,4 @@ -// Copyright Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. // Cumulus is free software: you can redistribute it and/or modify @@ -14,6 +14,31 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . +//! Autogenerated weights for `pallet_message_queue` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// ./target/production/polkadot-parachain +// benchmark +// pallet +// --chain=coretime-westend-dev +// --wasm-execution=compiled +// --pallet=pallet_message_queue +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ + #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -22,134 +47,140 @@ use frame_support::{traits::Get, weights::Weight}; use core::marker::PhantomData; +/// Weight functions for `pallet_message_queue`. pub struct WeightInfo(PhantomData); impl pallet_message_queue::WeightInfo for WeightInfo { - /// Storage: MessageQueue ServiceHead (r:1 w:0) - /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen) - /// Storage: MessageQueue BookStateFor (r:2 w:2) - /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) + /// Storage: `MessageQueue::ServiceHead` (r:1 w:0) + /// Proof: `MessageQueue::ServiceHead` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::BookStateFor` (r:2 w:2) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) fn ready_ring_knit() -> Weight { // Proof Size summary in bytes: - // Measured: `189` - // Estimated: `7534` - // Minimum execution time: 11_446_000 picoseconds. - Weight::from_parts(11_446_000, 0) - .saturating_add(Weight::from_parts(0, 7534)) + // Measured: `223` + // Estimated: `6044` + // Minimum execution time: 10_918_000 picoseconds. + Weight::from_parts(11_224_000, 0) + .saturating_add(Weight::from_parts(0, 6044)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: MessageQueue BookStateFor (r:2 w:2) - /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) - /// Storage: MessageQueue ServiceHead (r:1 w:1) - /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen) + /// Storage: `MessageQueue::BookStateFor` (r:2 w:2) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::ServiceHead` (r:1 w:1) + /// Proof: `MessageQueue::ServiceHead` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) fn ready_ring_unknit() -> Weight { // Proof Size summary in bytes: - // Measured: `184` - // Estimated: `7534` - // Minimum execution time: 10_613_000 picoseconds. - Weight::from_parts(10_613_000, 0) - .saturating_add(Weight::from_parts(0, 7534)) + // Measured: `218` + // Estimated: `6044` + // Minimum execution time: 9_649_000 picoseconds. + Weight::from_parts(10_056_000, 0) + .saturating_add(Weight::from_parts(0, 6044)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: MessageQueue BookStateFor (r:1 w:1) - /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) + /// Storage: `MessageQueue::BookStateFor` (r:1 w:1) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) fn service_queue_base() -> Weight { // Proof Size summary in bytes: // Measured: `6` // Estimated: `3517` - // Minimum execution time: 4_854_000 picoseconds. - Weight::from_parts(4_854_000, 0) + // Minimum execution time: 3_134_000 picoseconds. + Weight::from_parts(3_197_000, 0) .saturating_add(Weight::from_parts(0, 3517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: MessageQueue Pages (r:1 w:1) - /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + /// Storage: `MessageQueue::Pages` (r:1 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) fn service_page_base_completion() -> Weight { // Proof Size summary in bytes: // Measured: `72` // Estimated: `69050` - // Minimum execution time: 5_748_000 picoseconds. - Weight::from_parts(5_748_000, 0) + // Minimum execution time: 4_915_000 picoseconds. + Weight::from_parts(5_127_000, 0) .saturating_add(Weight::from_parts(0, 69050)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: MessageQueue Pages (r:1 w:1) - /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + /// Storage: `MessageQueue::Pages` (r:1 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) fn service_page_base_no_completion() -> Weight { // Proof Size summary in bytes: // Measured: `72` // Estimated: `69050` - // Minimum execution time: 6_136_000 picoseconds. - Weight::from_parts(6_136_000, 0) + // Minimum execution time: 5_011_000 picoseconds. + Weight::from_parts(5_150_000, 0) .saturating_add(Weight::from_parts(0, 69050)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `MessageQueue::BookStateFor` (r:0 w:1) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::Pages` (r:0 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) fn service_page_item() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 59_505_000 picoseconds. - Weight::from_parts(59_505_000, 0) + // Minimum execution time: 168_806_000 picoseconds. + Weight::from_parts(170_795_000, 0) .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: MessageQueue ServiceHead (r:1 w:1) - /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen) - /// Storage: MessageQueue BookStateFor (r:1 w:0) - /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) + /// Storage: `MessageQueue::ServiceHead` (r:1 w:1) + /// Proof: `MessageQueue::ServiceHead` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::BookStateFor` (r:1 w:0) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) fn bump_service_head() -> Weight { // Proof Size summary in bytes: - // Measured: `99` - // Estimated: `5007` - // Minimum execution time: 6_506_000 picoseconds. - Weight::from_parts(6_506_000, 0) - .saturating_add(Weight::from_parts(0, 5007)) + // Measured: `171` + // Estimated: `3517` + // Minimum execution time: 6_413_000 picoseconds. + Weight::from_parts(6_797_000, 0) + .saturating_add(Weight::from_parts(0, 3517)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: MessageQueue BookStateFor (r:1 w:1) - /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) - /// Storage: MessageQueue Pages (r:1 w:1) - /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + /// Storage: `MessageQueue::BookStateFor` (r:1 w:1) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::Pages` (r:1 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) fn reap_page() -> Weight { // Proof Size summary in bytes: // Measured: `65667` - // Estimated: `72567` - // Minimum execution time: 40_646_000 picoseconds. - Weight::from_parts(40_646_000, 0) - .saturating_add(Weight::from_parts(0, 72567)) + // Estimated: `69050` + // Minimum execution time: 52_734_000 picoseconds. + Weight::from_parts(54_106_000, 0) + .saturating_add(Weight::from_parts(0, 69050)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: MessageQueue BookStateFor (r:1 w:1) - /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) - /// Storage: MessageQueue Pages (r:1 w:1) - /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + /// Storage: `MessageQueue::BookStateFor` (r:1 w:1) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::Pages` (r:1 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) fn execute_overweight_page_removed() -> Weight { // Proof Size summary in bytes: // Measured: `65667` - // Estimated: `72567` - // Minimum execution time: 51_424_000 picoseconds. - Weight::from_parts(51_424_000, 0) - .saturating_add(Weight::from_parts(0, 72567)) + // Estimated: `69050` + // Minimum execution time: 68_400_000 picoseconds. + Weight::from_parts(70_336_000, 0) + .saturating_add(Weight::from_parts(0, 69050)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: MessageQueue BookStateFor (r:1 w:1) - /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) - /// Storage: MessageQueue Pages (r:1 w:1) - /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + /// Storage: `MessageQueue::BookStateFor` (r:1 w:1) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::Pages` (r:1 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) fn execute_overweight_page_updated() -> Weight { // Proof Size summary in bytes: // Measured: `65667` - // Estimated: `72567` - // Minimum execution time: 81_153_000 picoseconds. - Weight::from_parts(81_153_000, 0) - .saturating_add(Weight::from_parts(0, 72567)) + // Estimated: `69050` + // Minimum execution time: 109_496_000 picoseconds. + Weight::from_parts(111_632_000, 0) + .saturating_add(Weight::from_parts(0, 69050)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } -} \ No newline at end of file +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs index 4130e05bf7c..1aaf3f4a6fb 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs @@ -1,4 +1,4 @@ -// Copyright Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. // Cumulus is free software: you can redistribute it and/or modify @@ -16,26 +16,28 @@ //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("coretime-westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./artifacts/westend-parachain +// ./target/production/polkadot-parachain // benchmark // pallet // --chain=coretime-westend-dev -// --execution=wasm // --wasm-execution=compiled // --pallet=pallet_multisig +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -53,110 +55,110 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_337_000 picoseconds. - Weight::from_parts(11_960_522, 0) + // Minimum execution time: 11_938_000 picoseconds. + Weight::from_parts(13_021_007, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 9 - .saturating_add(Weight::from_parts(504, 0).saturating_mul(z.into())) + // Standard Error: 4 + .saturating_add(Weight::from_parts(482, 0).saturating_mul(z.into())) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `263 + s * (2 ±0)` + // Measured: `262 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 41_128_000 picoseconds. - Weight::from_parts(35_215_592, 0) + // Minimum execution time: 37_643_000 picoseconds. + Weight::from_parts(27_088_068, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 429 - .saturating_add(Weight::from_parts(65_959, 0).saturating_mul(s.into())) - // Standard Error: 4 - .saturating_add(Weight::from_parts(1_230, 0).saturating_mul(z.into())) + // Standard Error: 828 + .saturating_add(Weight::from_parts(123_693, 0).saturating_mul(s.into())) + // Standard Error: 8 + .saturating_add(Weight::from_parts(1_456, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[3, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 26_878_000 picoseconds. - Weight::from_parts(21_448_577, 0) + // Minimum execution time: 25_825_000 picoseconds. + Weight::from_parts(15_698_835, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 354 - .saturating_add(Weight::from_parts(60_286, 0).saturating_mul(s.into())) - // Standard Error: 3 - .saturating_add(Weight::from_parts(1_236, 0).saturating_mul(z.into())) + // Standard Error: 568 + .saturating_add(Weight::from_parts(111_928, 0).saturating_mul(s.into())) + // Standard Error: 5 + .saturating_add(Weight::from_parts(1_421, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `388 + s * (33 ±0)` + // Measured: `385 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 45_716_000 picoseconds. - Weight::from_parts(38_332_947, 0) + // Minimum execution time: 43_587_000 picoseconds. + Weight::from_parts(29_740_539, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 554 - .saturating_add(Weight::from_parts(81_026, 0).saturating_mul(s.into())) - // Standard Error: 5 - .saturating_add(Weight::from_parts(1_265, 0).saturating_mul(z.into())) + // Standard Error: 771 + .saturating_add(Weight::from_parts(154_861, 0).saturating_mul(s.into())) + // Standard Error: 7 + .saturating_add(Weight::from_parts(1_557, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `263 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 32_089_000 picoseconds. - Weight::from_parts(33_664_508, 0) + // Minimum execution time: 24_966_000 picoseconds. + Weight::from_parts(25_879_458, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 487 - .saturating_add(Weight::from_parts(67_443, 0).saturating_mul(s.into())) + // Standard Error: 777 + .saturating_add(Weight::from_parts(122_823, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 18_631_000 picoseconds. - Weight::from_parts(19_909_964, 0) + // Minimum execution time: 14_450_000 picoseconds. + Weight::from_parts(14_607_858, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 434 - .saturating_add(Weight::from_parts(62_989, 0).saturating_mul(s.into())) + // Standard Error: 471 + .saturating_add(Weight::from_parts(107_007, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `454 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 32_486_000 picoseconds. - Weight::from_parts(34_303_784, 0) + // Minimum execution time: 26_861_000 picoseconds. + Weight::from_parts(27_846_825, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 585 - .saturating_add(Weight::from_parts(69_979, 0).saturating_mul(s.into())) + // Standard Error: 714 + .saturating_add(Weight::from_parts(116_914, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_session.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_session.rs index d132ef17bbd..b0d993d68a6 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_session.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_session.rs @@ -1,4 +1,4 @@ -// Copyright Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. // Cumulus is free software: you can redistribute it and/or modify @@ -16,26 +16,28 @@ //! Autogenerated weights for `pallet_session` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("coretime-westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./artifacts/westend-parachain +// ./target/production/polkadot-parachain // benchmark // pallet // --chain=coretime-westend-dev -// --execution=wasm // --wasm-execution=compiled // --pallet=pallet_session +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_session.rs +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -48,31 +50,31 @@ use core::marker::PhantomData; /// Weight functions for `pallet_session`. pub struct WeightInfo(PhantomData); impl pallet_session::WeightInfo for WeightInfo { - /// Storage: Session NextKeys (r:1 w:1) - /// Proof Skipped: Session NextKeys (max_values: None, max_size: None, mode: Measured) - /// Storage: Session KeyOwner (r:1 w:1) - /// Proof Skipped: Session KeyOwner (max_values: None, max_size: None, mode: Measured) + /// Storage: `Session::NextKeys` (r:1 w:1) + /// Proof: `Session::NextKeys` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Session::KeyOwner` (r:1 w:1) + /// Proof: `Session::KeyOwner` (`max_values`: None, `max_size`: None, mode: `Measured`) fn set_keys() -> Weight { // Proof Size summary in bytes: - // Measured: `297` - // Estimated: `3762` - // Minimum execution time: 17_353_000 picoseconds. - Weight::from_parts(18_005_000, 0) - .saturating_add(Weight::from_parts(0, 3762)) + // Measured: `271` + // Estimated: `3736` + // Minimum execution time: 15_149_000 picoseconds. + Weight::from_parts(16_053_000, 0) + .saturating_add(Weight::from_parts(0, 3736)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Session NextKeys (r:1 w:1) - /// Proof Skipped: Session NextKeys (max_values: None, max_size: None, mode: Measured) - /// Storage: Session KeyOwner (r:0 w:1) - /// Proof Skipped: Session KeyOwner (max_values: None, max_size: None, mode: Measured) + /// Storage: `Session::NextKeys` (r:1 w:1) + /// Proof: `Session::NextKeys` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Session::KeyOwner` (r:0 w:1) + /// Proof: `Session::KeyOwner` (`max_values`: None, `max_size`: None, mode: `Measured`) fn purge_keys() -> Weight { // Proof Size summary in bytes: - // Measured: `279` - // Estimated: `3744` - // Minimum execution time: 13_039_000 picoseconds. - Weight::from_parts(13_341_000, 0) - .saturating_add(Weight::from_parts(0, 3744)) + // Measured: `243` + // Estimated: `3708` + // Minimum execution time: 11_159_000 picoseconds. + Weight::from_parts(11_504_000, 0) + .saturating_add(Weight::from_parts(0, 3708)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_timestamp.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_timestamp.rs index 722858a3a46..ad8924d9ce7 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_timestamp.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_timestamp.rs @@ -1,4 +1,4 @@ -// Copyright Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. // Cumulus is free software: you can redistribute it and/or modify @@ -16,26 +16,28 @@ //! Autogenerated weights for `pallet_timestamp` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("coretime-westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./artifacts/westend-parachain +// ./target/production/polkadot-parachain // benchmark // pallet // --chain=coretime-westend-dev -// --execution=wasm // --wasm-execution=compiled // --pallet=pallet_timestamp +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_timestamp.rs +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -48,16 +50,16 @@ use core::marker::PhantomData; /// Weight functions for `pallet_timestamp`. pub struct WeightInfo(PhantomData); impl pallet_timestamp::WeightInfo for WeightInfo { - /// Storage: Timestamp Now (r:1 w:1) - /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) - /// Storage: Aura CurrentSlot (r:1 w:0) - /// Proof: Aura CurrentSlot (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: `Timestamp::Now` (r:1 w:1) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + /// Storage: `Aura::CurrentSlot` (r:1 w:0) + /// Proof: `Aura::CurrentSlot` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) fn set() -> Weight { // Proof Size summary in bytes: - // Measured: `49` + // Measured: `86` // Estimated: `1493` - // Minimum execution time: 7_986_000 picoseconds. - Weight::from_parts(8_134_000, 0) + // Minimum execution time: 5_552_000 picoseconds. + Weight::from_parts(5_821_000, 0) .saturating_add(Weight::from_parts(0, 1493)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -66,8 +68,8 @@ impl pallet_timestamp::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `57` // Estimated: `0` - // Minimum execution time: 3_257_000 picoseconds. - Weight::from_parts(3_366_000, 0) + // Minimum execution time: 2_848_000 picoseconds. + Weight::from_parts(2_953_000, 0) .saturating_add(Weight::from_parts(0, 0)) } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_utility.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_utility.rs index dacd469ebb7..0f5340843bd 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_utility.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_utility.rs @@ -1,4 +1,4 @@ -// Copyright Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. // Cumulus is free software: you can redistribute it and/or modify @@ -16,26 +16,28 @@ //! Autogenerated weights for `pallet_utility` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("coretime-westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./artifacts/westend-parachain +// ./target/production/polkadot-parachain // benchmark // pallet // --chain=coretime-westend-dev -// --execution=wasm // --wasm-execution=compiled // --pallet=pallet_utility +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_utility.rs +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -53,18 +55,18 @@ impl pallet_utility::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_697_000 picoseconds. - Weight::from_parts(11_859_145, 0) + // Minimum execution time: 3_721_000 picoseconds. + Weight::from_parts(7_071_852, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 3_146 - .saturating_add(Weight::from_parts(4_300_555, 0).saturating_mul(c.into())) + // Standard Error: 746 + .saturating_add(Weight::from_parts(2_767_352, 0).saturating_mul(c.into())) } fn as_derivative() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_979_000 picoseconds. - Weight::from_parts(5_066_000, 0) + // Minimum execution time: 3_631_000 picoseconds. + Weight::from_parts(3_836_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// The range of component `c` is `[0, 1000]`. @@ -72,18 +74,18 @@ impl pallet_utility::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_741_000 picoseconds. - Weight::from_parts(15_928_547, 0) + // Minimum execution time: 3_817_000 picoseconds. + Weight::from_parts(2_683_003, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 3_310 - .saturating_add(Weight::from_parts(4_527_996, 0).saturating_mul(c.into())) + // Standard Error: 782 + .saturating_add(Weight::from_parts(3_059_987, 0).saturating_mul(c.into())) } fn dispatch_as() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_717_000 picoseconds. - Weight::from_parts(8_909_000, 0) + // Minimum execution time: 5_463_000 picoseconds. + Weight::from_parts(5_701_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// The range of component `c` is `[0, 1000]`. @@ -91,10 +93,10 @@ impl pallet_utility::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_814_000 picoseconds. - Weight::from_parts(13_920_831, 0) + // Minimum execution time: 3_771_000 picoseconds. + Weight::from_parts(5_714_929, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 7_605 - .saturating_add(Weight::from_parts(4_306_193, 0).saturating_mul(c.into())) + // Standard Error: 740 + .saturating_add(Weight::from_parts(2_800_888, 0).saturating_mul(c.into())) } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs index d96ee43463a..d821a581b0d 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs @@ -1,4 +1,4 @@ -// Copyright Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. // Cumulus is free software: you can redistribute it and/or modify @@ -16,26 +16,28 @@ //! Autogenerated weights for `pallet_xcm` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("coretime-westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./artifacts/westend-parachain +// ./target/production/polkadot-parachain // benchmark // pallet // --chain=coretime-westend-dev -// --execution=wasm // --wasm-execution=compiled // --pallet=pallet_xcm +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -48,39 +50,50 @@ use core::marker::PhantomData; /// Weight functions for `pallet_xcm`. pub struct WeightInfo(PhantomData); impl pallet_xcm::WeightInfo for WeightInfo { - /// Storage: PolkadotXcm SupportedVersion (r:1 w:0) - /// Proof Skipped: PolkadotXcm SupportedVersion (max_values: None, max_size: None, mode: Measured) - /// Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) - /// Proof Skipped: PolkadotXcm VersionDiscoveryQueue (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) - /// Proof Skipped: PolkadotXcm SafeXcmVersion (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ParachainSystem HostConfiguration (r:1 w:0) - /// Proof Skipped: ParachainSystem HostConfiguration (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) - /// Proof Skipped: ParachainSystem PendingUpwardMessages (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn send() -> Weight { // Proof Size summary in bytes: - // Measured: `38` - // Estimated: `3503` - // Minimum execution time: 25_783_000 picoseconds. - Weight::from_parts(26_398_000, 0) - .saturating_add(Weight::from_parts(0, 3503)) + // Measured: `74` + // Estimated: `3539` + // Minimum execution time: 17_946_000 picoseconds. + Weight::from_parts(18_398_000, 0) + .saturating_add(Weight::from_parts(0, 3539)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: ParachainInfo ParachainId (r:1 w:0) - /// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) + /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `32` - // Estimated: `1489` - // Minimum execution time: 25_511_000 picoseconds. - Weight::from_parts(26_120_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 47_982_000 picoseconds. + Weight::from_parts(49_215_000, 0) + .saturating_add(Weight::from_parts(0, 3571)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Benchmark Override (r:0 w:0) - /// Proof Skipped: Benchmark Override (max_values: None, max_size: None, mode: Measured) + /// Storage: `Benchmark::Override` (r:0 w:0) + /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) fn reserve_transfer_assets() -> Weight { // Proof Size summary in bytes: // Measured: `0` @@ -89,18 +102,18 @@ impl pallet_xcm::WeightInfo for WeightInfo { Weight::from_parts(18_446_744_073_709_551_000, 0) .saturating_add(Weight::from_parts(0, 0)) } + /// Storage: `Benchmark::Override` (r:0 w:0) + /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) fn transfer_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `496` - // Estimated: `6208` - // Minimum execution time: 146_932_000 picoseconds. - Weight::from_parts(153_200_000, 0) - .saturating_add(Weight::from_parts(0, 6208)) - .saturating_add(T::DbWeight::get().reads(12)) - .saturating_add(T::DbWeight::get().writes(7)) + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. + Weight::from_parts(18_446_744_073_709_551_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: Benchmark Override (r:0 w:0) - /// Proof Skipped: Benchmark Override (max_values: None, max_size: None, mode: Measured) + /// Storage: `Benchmark::Override` (r:0 w:0) + /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) fn execute() -> Weight { // Proof Size summary in bytes: // Measured: `0` @@ -109,189 +122,189 @@ impl pallet_xcm::WeightInfo for WeightInfo { Weight::from_parts(18_446_744_073_709_551_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: PolkadotXcm SupportedVersion (r:0 w:1) - /// Proof Skipped: PolkadotXcm SupportedVersion (max_values: None, max_size: None, mode: Measured) + /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_xcm_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_707_000 picoseconds. - Weight::from_parts(9_874_000, 0) + // Minimum execution time: 6_042_000 picoseconds. + Weight::from_parts(6_257_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: PolkadotXcm SafeXcmVersion (r:0 w:1) - /// Proof Skipped: PolkadotXcm SafeXcmVersion (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:0 w:1) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn force_default_xcm_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_073_000 picoseconds. - Weight::from_parts(3_183_000, 0) + // Minimum execution time: 1_845_000 picoseconds. + Weight::from_parts(1_993_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: PolkadotXcm VersionNotifiers (r:1 w:1) - /// Proof Skipped: PolkadotXcm VersionNotifiers (max_values: None, max_size: None, mode: Measured) - /// Storage: PolkadotXcm QueryCounter (r:1 w:1) - /// Proof Skipped: PolkadotXcm QueryCounter (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: PolkadotXcm SupportedVersion (r:1 w:0) - /// Proof Skipped: PolkadotXcm SupportedVersion (max_values: None, max_size: None, mode: Measured) - /// Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) - /// Proof Skipped: PolkadotXcm VersionDiscoveryQueue (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) - /// Proof Skipped: PolkadotXcm SafeXcmVersion (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ParachainSystem HostConfiguration (r:1 w:0) - /// Proof Skipped: ParachainSystem HostConfiguration (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) - /// Proof Skipped: ParachainSystem PendingUpwardMessages (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: PolkadotXcm Queries (r:0 w:1) - /// Proof Skipped: PolkadotXcm Queries (max_values: None, max_size: None, mode: Measured) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) + /// Proof: `PolkadotXcm::QueryCounter` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::Queries` (r:0 w:1) + /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_subscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `38` - // Estimated: `3503` - // Minimum execution time: 30_999_000 picoseconds. - Weight::from_parts(31_641_000, 0) - .saturating_add(Weight::from_parts(0, 3503)) + // Measured: `74` + // Estimated: `3539` + // Minimum execution time: 24_062_000 picoseconds. + Weight::from_parts(24_666_000, 0) + .saturating_add(Weight::from_parts(0, 3539)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) } - /// Storage: PolkadotXcm VersionNotifiers (r:1 w:1) - /// Proof Skipped: PolkadotXcm VersionNotifiers (max_values: None, max_size: None, mode: Measured) - /// Storage: PolkadotXcm SupportedVersion (r:1 w:0) - /// Proof Skipped: PolkadotXcm SupportedVersion (max_values: None, max_size: None, mode: Measured) - /// Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) - /// Proof Skipped: PolkadotXcm VersionDiscoveryQueue (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) - /// Proof Skipped: PolkadotXcm SafeXcmVersion (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ParachainSystem HostConfiguration (r:1 w:0) - /// Proof Skipped: ParachainSystem HostConfiguration (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) - /// Proof Skipped: ParachainSystem PendingUpwardMessages (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: PolkadotXcm Queries (r:0 w:1) - /// Proof Skipped: PolkadotXcm Queries (max_values: None, max_size: None, mode: Measured) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::Queries` (r:0 w:1) + /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_unsubscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `220` - // Estimated: `3685` - // Minimum execution time: 33_036_000 picoseconds. - Weight::from_parts(33_596_000, 0) - .saturating_add(Weight::from_parts(0, 3685)) + // Measured: `292` + // Estimated: `3757` + // Minimum execution time: 26_486_000 picoseconds. + Weight::from_parts(27_528_000, 0) + .saturating_add(Weight::from_parts(0, 3757)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } - /// Storage: PolkadotXcm XcmExecutionSuspended (r:0 w:1) - /// Proof Skipped: PolkadotXcm XcmExecutionSuspended (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: `PolkadotXcm::XcmExecutionSuspended` (r:0 w:1) + /// Proof: `PolkadotXcm::XcmExecutionSuspended` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn force_suspension() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_035_000 picoseconds. - Weight::from_parts(3_154_000, 0) + // Minimum execution time: 1_881_000 picoseconds. + Weight::from_parts(2_008_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: PolkadotXcm SupportedVersion (r:4 w:2) - /// Proof Skipped: PolkadotXcm SupportedVersion (max_values: None, max_size: None, mode: Measured) + /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: - // Measured: `95` - // Estimated: `10985` - // Minimum execution time: 14_805_000 picoseconds. - Weight::from_parts(15_120_000, 0) - .saturating_add(Weight::from_parts(0, 10985)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `89` + // Estimated: `13454` + // Minimum execution time: 15_971_000 picoseconds. + Weight::from_parts(16_455_000, 0) + .saturating_add(Weight::from_parts(0, 13454)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: PolkadotXcm VersionNotifiers (r:4 w:2) - /// Proof Skipped: PolkadotXcm VersionNotifiers (max_values: None, max_size: None, mode: Measured) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) + /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: - // Measured: `99` - // Estimated: `10989` - // Minimum execution time: 14_572_000 picoseconds. - Weight::from_parts(14_909_000, 0) - .saturating_add(Weight::from_parts(0, 10989)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `93` + // Estimated: `13458` + // Minimum execution time: 16_603_000 picoseconds. + Weight::from_parts(17_037_000, 0) + .saturating_add(Weight::from_parts(0, 13458)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: PolkadotXcm VersionNotifyTargets (r:5 w:0) - /// Proof Skipped: PolkadotXcm VersionNotifyTargets (max_values: None, max_size: None, mode: Measured) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) + /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `106` - // Estimated: `13471` - // Minimum execution time: 15_341_000 picoseconds. - Weight::from_parts(15_708_000, 0) - .saturating_add(Weight::from_parts(0, 13471)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15946` + // Minimum execution time: 17_821_000 picoseconds. + Weight::from_parts(18_200_000, 0) + .saturating_add(Weight::from_parts(0, 15946)) + .saturating_add(T::DbWeight::get().reads(6)) } - /// Storage: PolkadotXcm VersionNotifyTargets (r:2 w:1) - /// Proof Skipped: PolkadotXcm VersionNotifyTargets (max_values: None, max_size: None, mode: Measured) - /// Storage: PolkadotXcm SupportedVersion (r:1 w:0) - /// Proof Skipped: PolkadotXcm SupportedVersion (max_values: None, max_size: None, mode: Measured) - /// Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) - /// Proof Skipped: PolkadotXcm VersionDiscoveryQueue (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) - /// Proof Skipped: PolkadotXcm SafeXcmVersion (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ParachainSystem HostConfiguration (r:1 w:0) - /// Proof Skipped: ParachainSystem HostConfiguration (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) - /// Proof Skipped: ParachainSystem PendingUpwardMessages (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) + /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn notify_current_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `106` - // Estimated: `6046` - // Minimum execution time: 27_840_000 picoseconds. - Weight::from_parts(28_248_000, 0) - .saturating_add(Weight::from_parts(0, 6046)) + // Measured: `142` + // Estimated: `6082` + // Minimum execution time: 23_878_000 picoseconds. + Weight::from_parts(24_721_000, 0) + .saturating_add(Weight::from_parts(0, 6082)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: PolkadotXcm VersionNotifyTargets (r:3 w:0) - /// Proof Skipped: PolkadotXcm VersionNotifyTargets (max_values: None, max_size: None, mode: Measured) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) + /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: // Measured: `136` - // Estimated: `8551` - // Minimum execution time: 8_245_000 picoseconds. - Weight::from_parts(8_523_000, 0) - .saturating_add(Weight::from_parts(0, 8551)) - .saturating_add(T::DbWeight::get().reads(3)) + // Estimated: `11026` + // Minimum execution time: 10_566_000 picoseconds. + Weight::from_parts(11_053_000, 0) + .saturating_add(Weight::from_parts(0, 11026)) + .saturating_add(T::DbWeight::get().reads(4)) } - /// Storage: PolkadotXcm VersionNotifyTargets (r:4 w:2) - /// Proof Skipped: PolkadotXcm VersionNotifyTargets (max_values: None, max_size: None, mode: Measured) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `106` - // Estimated: `10996` - // Minimum execution time: 14_780_000 picoseconds. - Weight::from_parts(15_173_000, 0) - .saturating_add(Weight::from_parts(0, 10996)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `100` + // Estimated: `13465` + // Minimum execution time: 16_020_000 picoseconds. + Weight::from_parts(16_619_000, 0) + .saturating_add(Weight::from_parts(0, 13465)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: PolkadotXcm VersionNotifyTargets (r:4 w:2) - /// Proof Skipped: PolkadotXcm VersionNotifyTargets (max_values: None, max_size: None, mode: Measured) - /// Storage: PolkadotXcm SupportedVersion (r:1 w:0) - /// Proof Skipped: PolkadotXcm SupportedVersion (max_values: None, max_size: None, mode: Measured) - /// Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) - /// Proof Skipped: PolkadotXcm VersionDiscoveryQueue (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) - /// Proof Skipped: PolkadotXcm SafeXcmVersion (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ParachainSystem HostConfiguration (r:1 w:0) - /// Proof Skipped: ParachainSystem HostConfiguration (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) - /// Proof Skipped: ParachainSystem PendingUpwardMessages (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `112` - // Estimated: `11002` - // Minimum execution time: 33_422_000 picoseconds. - Weight::from_parts(34_076_000, 0) - .saturating_add(Weight::from_parts(0, 11002)) - .saturating_add(T::DbWeight::get().reads(9)) + // Measured: `142` + // Estimated: `13507` + // Minimum execution time: 32_136_000 picoseconds. + Weight::from_parts(32_610_000, 0) + .saturating_add(Weight::from_parts(0, 13507)) + .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) @@ -300,11 +313,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn new_query() -> Weight { // Proof Size summary in bytes: - // Measured: `69` - // Estimated: `1554` - // Minimum execution time: 4_512_000 picoseconds. - Weight::from_parts(4_671_000, 0) - .saturating_add(Weight::from_parts(0, 1554)) + // Measured: `32` + // Estimated: `1517` + // Minimum execution time: 3_336_000 picoseconds. + Weight::from_parts(3_434_000, 0) + .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -312,11 +325,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn take_response() -> Weight { // Proof Size summary in bytes: - // Measured: `7706` - // Estimated: `11171` - // Minimum execution time: 26_473_000 picoseconds. - Weight::from_parts(26_960_000, 0) - .saturating_add(Weight::from_parts(0, 11171)) + // Measured: `7669` + // Estimated: `11134` + // Minimum execution time: 23_977_000 picoseconds. + Weight::from_parts(24_413_000, 0) + .saturating_add(Weight::from_parts(0, 11134)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs index a14da7c7a38..99af88812da 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs @@ -196,7 +196,7 @@ impl XcmWeightInfo for CoretimeWestendXcmWeight { XcmGeneric::::clear_transact_status() } fn universal_origin(_: &Junction) -> Weight { - XcmGeneric::::universal_origin() + Weight::MAX } fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> Weight { Weight::MAX diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs index eaf07aac52c..6f5a52de98c 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs @@ -1,41 +1,44 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 +// This file is part of Cumulus. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . //! Autogenerated weights for `pallet_xcm_benchmarks::fungible` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-10-26, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-vmdtonbz-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! WASM-EXECUTION: Compiled, CHAIN: Some("asset-hub-westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: Compiled, CHAIN: Some("coretime-westend-dev"), DB CACHE: 1024 // Executed Command: -// target/production/polkadot-parachain +// ./target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 -// --extrinsic=* +// --template=./cumulus/templates/xcm-bench-template.hbs +// --chain=coretime-westend-dev // --wasm-execution=compiled -// --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json // --pallet=pallet_xcm_benchmarks::fungible -// --chain=asset-hub-westend-dev +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json // --header=./cumulus/file_header.txt -// --template=./cumulus/templates/xcm-bench-template.hbs -// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/ +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -53,8 +56,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `3593` - // Minimum execution time: 20_295_000 picoseconds. - Weight::from_parts(21_142_000, 3593) + // Minimum execution time: 19_401_000 picoseconds. + Weight::from_parts(19_768_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -64,17 +67,15 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `6196` - // Minimum execution time: 42_356_000 picoseconds. - Weight::from_parts(43_552_000, 6196) + // Minimum execution time: 42_452_000 picoseconds. + Weight::from_parts(43_126_000, 6196) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - // Storage: `System::Account` (r:3 w:3) + // Storage: `System::Account` (r:2 w:2) // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) - // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) @@ -87,54 +88,49 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn transfer_reserve_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `246` - // Estimated: `8799` - // Minimum execution time: 85_553_000 picoseconds. - Weight::from_parts(87_177_000, 8799) - .saturating_add(T::DbWeight::get().reads(10)) - .saturating_add(T::DbWeight::get().writes(5)) + // Measured: `207` + // Estimated: `6196` + // Minimum execution time: 58_090_000 picoseconds. + Weight::from_parts(59_502_000, 6196) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(4)) } - // Storage: `ParachainInfo::ParachainId` (r:1 w:0) - // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Benchmark::Override` (r:0 w:0) + // Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) pub fn reserve_asset_deposited() -> Weight { // Proof Size summary in bytes: // Measured: `0` - // Estimated: `1489` - // Minimum execution time: 6_166_000 picoseconds. - Weight::from_parts(6_352_000, 1489) - .saturating_add(T::DbWeight::get().reads(1)) + // Estimated: `0` + // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. + Weight::from_parts(18_446_744_073_709_551_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) - // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `System::Account` (r:2 w:2) - // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn initiate_reserve_withdraw() -> Weight { // Proof Size summary in bytes: - // Measured: `246` - // Estimated: `6196` - // Minimum execution time: 184_462_000 picoseconds. - Weight::from_parts(189_593_000, 6196) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 23_569_000 picoseconds. + Weight::from_parts(24_598_000, 3571) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) } pub fn receive_teleported_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_018_000 picoseconds. - Weight::from_parts(3_098_000, 0) + // Minimum execution time: 2_546_000 picoseconds. + Weight::from_parts(2_674_000, 0) } // Storage: `System::Account` (r:1 w:1) // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) @@ -142,59 +138,53 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 18_583_000 picoseconds. - Weight::from_parts(19_057_000, 3593) + // Minimum execution time: 16_889_000 picoseconds. + Weight::from_parts(17_350_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: `System::Account` (r:2 w:2) - // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - // Storage: `ParachainInfo::ParachainId` (r:1 w:0) - // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) - // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ParachainInfo::ParachainId` (r:1 w:0) + // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn deposit_reserve_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `145` - // Estimated: `6196` - // Minimum execution time: 56_666_000 picoseconds. - Weight::from_parts(58_152_000, 6196) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `106` + // Estimated: `3593` + // Minimum execution time: 43_964_000 picoseconds. + Weight::from_parts(45_293_000, 3593) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) - // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `System::Account` (r:1 w:1) - // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn initiate_teleport() -> Weight { // Proof Size summary in bytes: - // Measured: `145` - // Estimated: `3610` - // Minimum execution time: 44_197_000 picoseconds. - Weight::from_parts(45_573_000, 3610) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(3)) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 20_704_000 picoseconds. + Weight::from_parts(21_266_000, 3571) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index fc196abea0f..74254814bca 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -1,41 +1,44 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 +// This file is part of Cumulus. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . //! Autogenerated weights for `pallet_xcm_benchmarks::generic` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-10-26, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-vmdtonbz-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! WASM-EXECUTION: Compiled, CHAIN: Some("asset-hub-westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: Compiled, CHAIN: Some("coretime-westend-dev"), DB CACHE: 1024 // Executed Command: -// target/production/polkadot-parachain +// ./target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 -// --extrinsic=* +// --template=./cumulus/templates/xcm-bench-template.hbs +// --chain=coretime-westend-dev // --wasm-execution=compiled -// --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json // --pallet=pallet_xcm_benchmarks::generic -// --chain=asset-hub-westend-dev +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json // --header=./cumulus/file_header.txt -// --template=./cumulus/templates/xcm-bench-template.hbs -// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/ +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -49,128 +52,120 @@ pub struct WeightInfo(PhantomData); impl WeightInfo { // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) - // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `System::Account` (r:2 w:2) - // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn report_holding() -> Weight { // Proof Size summary in bytes: - // Measured: `246` - // Estimated: `6196` - // Minimum execution time: 415_033_000 picoseconds. - Weight::from_parts(429_573_000, 6196) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 22_424_000 picoseconds. + Weight::from_parts(23_208_000, 3571) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) } pub fn buy_execution() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_193_000 picoseconds. - Weight::from_parts(3_620_000, 0) + // Minimum execution time: 1_194_000 picoseconds. + Weight::from_parts(1_306_000, 0) } // Storage: `PolkadotXcm::Queries` (r:1 w:0) // Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) pub fn query_response() -> Weight { // Proof Size summary in bytes: - // Measured: `103` - // Estimated: `3568` - // Minimum execution time: 8_045_000 picoseconds. - Weight::from_parts(8_402_000, 3568) + // Measured: `32` + // Estimated: `3497` + // Minimum execution time: 6_359_000 picoseconds. + Weight::from_parts(6_585_000, 3497) .saturating_add(T::DbWeight::get().reads(1)) } pub fn transact() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_827_000 picoseconds. - Weight::from_parts(10_454_000, 0) + // Minimum execution time: 6_297_000 picoseconds. + Weight::from_parts(6_661_000, 0) } pub fn refund_surplus() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_330_000 picoseconds. - Weight::from_parts(3_677_000, 0) + // Minimum execution time: 1_778_000 picoseconds. + Weight::from_parts(1_923_000, 0) } pub fn set_error_handler() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_947_000 picoseconds. - Weight::from_parts(2_083_000, 0) + // Minimum execution time: 1_212_000 picoseconds. + Weight::from_parts(1_314_000, 0) } pub fn set_appendix() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_915_000 picoseconds. - Weight::from_parts(1_993_000, 0) + // Minimum execution time: 1_165_000 picoseconds. + Weight::from_parts(1_247_000, 0) } pub fn clear_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_918_000 picoseconds. - Weight::from_parts(2_048_000, 0) + // Minimum execution time: 1_173_000 picoseconds. + Weight::from_parts(1_275_000, 0) } pub fn descend_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_683_000 picoseconds. - Weight::from_parts(3_064_000, 0) + // Minimum execution time: 1_247_000 picoseconds. + Weight::from_parts(1_332_000, 0) } pub fn clear_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_893_000 picoseconds. - Weight::from_parts(2_159_000, 0) + // Minimum execution time: 1_170_000 picoseconds. + Weight::from_parts(1_237_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) - // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `System::Account` (r:2 w:2) - // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn report_error() -> Weight { // Proof Size summary in bytes: - // Measured: `246` - // Estimated: `6196` - // Minimum execution time: 53_116_000 picoseconds. - Weight::from_parts(54_154_000, 6196) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 19_872_000 picoseconds. + Weight::from_parts(20_453_000, 3571) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) } // Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) // Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) pub fn claim_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `160` - // Estimated: `3625` - // Minimum execution time: 12_381_000 picoseconds. - Weight::from_parts(12_693_000, 3625) + // Measured: `90` + // Estimated: `3555` + // Minimum execution time: 9_105_000 picoseconds. + Weight::from_parts(9_365_000, 3555) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -178,13 +173,11 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_933_000 picoseconds. - Weight::from_parts(1_983_000, 0) + // Minimum execution time: 1_228_000 picoseconds. + Weight::from_parts(1_293_000, 0) } // Storage: `PolkadotXcm::VersionNotifyTargets` (r:1 w:1) // Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) - // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) @@ -197,11 +190,11 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn subscribe_version() -> Weight { // Proof Size summary in bytes: - // Measured: `145` - // Estimated: `3610` - // Minimum execution time: 24_251_000 picoseconds. - Weight::from_parts(24_890_000, 3610) - .saturating_add(T::DbWeight::get().reads(7)) + // Measured: `74` + // Estimated: `3539` + // Minimum execution time: 19_535_000 picoseconds. + Weight::from_parts(20_139_000, 3539) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } // Storage: `PolkadotXcm::VersionNotifyTargets` (r:0 w:1) @@ -210,145 +203,127 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_850_000 picoseconds. - Weight::from_parts(4_082_000, 0) + // Minimum execution time: 3_158_000 picoseconds. + Weight::from_parts(3_275_000, 0) .saturating_add(T::DbWeight::get().writes(1)) } pub fn burn_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 112_248_000 picoseconds. - Weight::from_parts(124_454_000, 0) + // Minimum execution time: 1_539_000 picoseconds. + Weight::from_parts(1_607_000, 0) } pub fn expect_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_457_000 picoseconds. - Weight::from_parts(12_060_000, 0) + // Minimum execution time: 1_317_000 picoseconds. + Weight::from_parts(1_427_000, 0) } pub fn expect_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_959_000 picoseconds. - Weight::from_parts(2_076_000, 0) + // Minimum execution time: 1_176_000 picoseconds. + Weight::from_parts(1_250_000, 0) } pub fn expect_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_920_000 picoseconds. - Weight::from_parts(1_994_000, 0) + // Minimum execution time: 1_202_000 picoseconds. + Weight::from_parts(1_279_000, 0) } pub fn expect_transact_status() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_149_000 picoseconds. - Weight::from_parts(2_394_000, 0) + // Minimum execution time: 1_411_000 picoseconds. + Weight::from_parts(1_463_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) - // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `System::Account` (r:2 w:2) - // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn query_pallet() -> Weight { // Proof Size summary in bytes: - // Measured: `246` - // Estimated: `6196` - // Minimum execution time: 58_011_000 picoseconds. - Weight::from_parts(59_306_000, 6196) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 22_991_000 picoseconds. + Weight::from_parts(23_820_000, 3571) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) } pub fn expect_pallet() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_031_000 picoseconds. - Weight::from_parts(5_243_000, 0) + // Minimum execution time: 3_534_000 picoseconds. + Weight::from_parts(3_708_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) - // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `System::Account` (r:2 w:2) - // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn report_transact_status() -> Weight { // Proof Size summary in bytes: - // Measured: `246` - // Estimated: `6196` - // Minimum execution time: 53_078_000 picoseconds. - Weight::from_parts(54_345_000, 6196) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 20_025_000 picoseconds. + Weight::from_parts(20_463_000, 3571) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) } pub fn clear_transact_status() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_936_000 picoseconds. - Weight::from_parts(2_002_000, 0) + // Minimum execution time: 1_213_000 picoseconds. + Weight::from_parts(1_290_000, 0) } pub fn set_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_855_000 picoseconds. - Weight::from_parts(1_950_000, 0) + // Minimum execution time: 1_207_000 picoseconds. + Weight::from_parts(1_265_000, 0) } pub fn clear_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_882_000 picoseconds. - Weight::from_parts(1_977_000, 0) - } - // Storage: `ParachainInfo::ParachainId` (r:1 w:0) - // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - pub fn universal_origin() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `1489` - // Minimum execution time: 3_912_000 picoseconds. - Weight::from_parts(4_167_000, 1489) - .saturating_add(T::DbWeight::get().reads(1)) + // Minimum execution time: 1_195_000 picoseconds. + Weight::from_parts(1_231_000, 0) } pub fn set_fees_mode() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_911_000 picoseconds. - Weight::from_parts(1_971_000, 0) + // Minimum execution time: 1_182_000 picoseconds. + Weight::from_parts(1_265_000, 0) } pub fn unpaid_execution() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_990_000 picoseconds. - Weight::from_parts(2_076_000, 0) + // Minimum execution time: 1_165_000 picoseconds. + Weight::from_parts(1_252_000, 0) } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs index 3adef5fb7cf..44049adf027 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs @@ -15,11 +15,12 @@ // along with Cumulus. If not, see . use super::{ - AccountId, AllPalletsWithSystem, Balances, BaseDeliveryFee, FeeAssetId, ParachainInfo, + AccountId, AllPalletsWithSystem, Balances, BaseDeliveryFee, Broker, FeeAssetId, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, TransactionByteFee, WeightToFee, XcmpQueue, }; use frame_support::{ + pallet_prelude::PalletInfoAccess, parameter_types, traits::{ConstU32, Contains, Equals, Everything, Nothing}, }; @@ -41,20 +42,22 @@ use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, IsConcrete, - ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, - XcmFeeToAccount, + NonFungibleAdapter, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; parameter_types! { - pub const WndRelayLocation: Location = Location::parent(); + pub const TokenRelayLocation: Location = Location::parent(); pub const RelayNetwork: Option = Some(NetworkId::Westend); pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); pub UniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get().unwrap()), Parachain(ParachainInfo::parachain_id().into())].into(); + pub BrokerPalletLocation: Location = + PalletInstance(::index() as u8).into(); pub const MaxInstructions: u32 = 100; pub const MaxAssetsIntoHolding: u32 = 64; pub FellowshipLocation: Location = Location::new(1, Parachain(1001)); @@ -74,11 +77,11 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. -pub type CurrencyTransactor = FungibleAdapter< +pub type FungibleTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: - IsConcrete, + IsConcrete, // Do a simple punn to convert an `AccountId32` `Location` into a native chain // `AccountId`: LocationToAccountId, @@ -88,6 +91,23 @@ pub type CurrencyTransactor = FungibleAdapter< (), >; +/// Means for transacting coretime regions on this chain. +pub type RegionTransactor = NonFungibleAdapter< + // Use this non-fungible implementation: + Broker, + // This adapter will handle coretime regions from the broker pallet. + IsConcrete, + // Convert an XCM Location into a local account id: + LocationToAccountId, + // Our chain's account ID type (we can't get away without mentioning it explicitly): + AccountId, + // We don't track any teleports. + (), +>; + +/// Means for transacting assets on this chain. +pub type AssetTransactors = (FungibleTransactor, RegionTransactor); + /// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, /// ready for dispatching a transaction with XCM's `Transact`. There is an `OriginKind` that can /// bias the kind of local `Origin` it will become. @@ -150,18 +170,20 @@ impl Contains for SafeCallFilter { pallet_xcm::Call::force_default_xcm_version { .. } ) | RuntimeCall::System( frame_system::Call::set_heap_pages { .. } | - frame_system::Call::set_code { .. } | - frame_system::Call::set_code_without_checks { .. } | - frame_system::Call::authorize_upgrade { .. } | - frame_system::Call::authorize_upgrade_without_checks { .. } | - frame_system::Call::kill_prefix { .. }, + frame_system::Call::set_code { .. } | + frame_system::Call::set_code_without_checks { .. } | + frame_system::Call::authorize_upgrade { .. } | + frame_system::Call::authorize_upgrade_without_checks { .. } | + frame_system::Call::kill_prefix { .. } | + // Should not be in Polkadot/Kusama. Here in order to speed up testing. + frame_system::Call::set_storage { .. }, ) | RuntimeCall::ParachainSystem(..) | RuntimeCall::Timestamp(..) | RuntimeCall::Balances(..) | RuntimeCall::CollatorSelection(..) | - RuntimeCall::Sudo(..) | RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | - RuntimeCall::XcmpQueue(..) + RuntimeCall::XcmpQueue(..) | + RuntimeCall::Broker(..) ) } } @@ -208,13 +230,13 @@ pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; type XcmSender = XcmRouter; - type AssetTransactor = CurrencyTransactor; + type AssetTransactor = AssetTransactors; type OriginConverter = XcmOriginToTransactDispatchOrigin; - // Coretime chain does not recognize a reserve location for any asset. Users must teleport WND + // Coretime chain does not recognize a reserve location for any asset. Users must teleport ROC // where allowed (e.g. with the Relay Chain). type IsReserve = (); /// Only allow teleportation of WND. - type IsTeleporter = ConcreteAssetFromSystem; + type IsTeleporter = ConcreteAssetFromSystem; type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = WeightInfoBounds< @@ -222,8 +244,13 @@ impl xcm_executor::Config for XcmConfig { RuntimeCall, MaxInstructions, >; - type Trader = - UsingComponents>; + type Trader = UsingComponents< + WeightToFee, + TokenRelayLocation, + AccountId, + Balances, + ToStakingPot, + >; type ResponseHandler = PolkadotXcm; type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; diff --git a/cumulus/polkadot-parachain/chain-specs/coretime-rococo.json b/cumulus/polkadot-parachain/chain-specs/coretime-rococo.json new file mode 120000 index 00000000000..6e47a6ad08c --- /dev/null +++ b/cumulus/polkadot-parachain/chain-specs/coretime-rococo.json @@ -0,0 +1 @@ +../../parachains/chain-specs/coretime-rococo.json \ No newline at end of file diff --git a/cumulus/polkadot-parachain/chain-specs/coretime-westend.json b/cumulus/polkadot-parachain/chain-specs/coretime-westend.json new file mode 120000 index 00000000000..80dd771db54 --- /dev/null +++ b/cumulus/polkadot-parachain/chain-specs/coretime-westend.json @@ -0,0 +1 @@ +../../parachains/chain-specs/coretime-westend.json \ No newline at end of file diff --git a/cumulus/polkadot-parachain/src/chain_spec/coretime.rs b/cumulus/polkadot-parachain/src/chain_spec/coretime.rs index f7c362cbd45..42d56fc80eb 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/coretime.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/coretime.rs @@ -29,6 +29,8 @@ pub enum CoretimeRuntimeType { // Benchmarks RococoDevelopment, + // Live + Westend, // Local WestendLocal, // Benchmarks @@ -43,6 +45,7 @@ impl FromStr for CoretimeRuntimeType { rococo::CORETIME_ROCOCO => Ok(CoretimeRuntimeType::Rococo), rococo::CORETIME_ROCOCO_LOCAL => Ok(CoretimeRuntimeType::RococoLocal), rococo::CORETIME_ROCOCO_DEVELOPMENT => Ok(CoretimeRuntimeType::RococoDevelopment), + westend::CORETIME_WESTEND => Ok(CoretimeRuntimeType::Westend), westend::CORETIME_WESTEND_LOCAL => Ok(CoretimeRuntimeType::WestendLocal), westend::CORETIME_WESTEND_DEVELOPMENT => Ok(CoretimeRuntimeType::WestendDevelopment), _ => Err(format!("Value '{}' is not configured yet", value)), @@ -56,6 +59,7 @@ impl From for &str { CoretimeRuntimeType::Rococo => rococo::CORETIME_ROCOCO, CoretimeRuntimeType::RococoLocal => rococo::CORETIME_ROCOCO_LOCAL, CoretimeRuntimeType::RococoDevelopment => rococo::CORETIME_ROCOCO_DEVELOPMENT, + CoretimeRuntimeType::Westend => westend::CORETIME_WESTEND, CoretimeRuntimeType::WestendLocal => westend::CORETIME_WESTEND_LOCAL, CoretimeRuntimeType::WestendDevelopment => westend::CORETIME_WESTEND_DEVELOPMENT, } @@ -65,7 +69,7 @@ impl From for &str { impl From for ChainType { fn from(runtime_type: CoretimeRuntimeType) -> Self { match runtime_type { - CoretimeRuntimeType::Rococo => ChainType::Live, + CoretimeRuntimeType::Rococo | CoretimeRuntimeType::Westend => ChainType::Live, CoretimeRuntimeType::RococoLocal | CoretimeRuntimeType::WestendLocal => ChainType::Local, CoretimeRuntimeType::RococoDevelopment | CoretimeRuntimeType::WestendDevelopment => @@ -82,12 +86,15 @@ impl CoretimeRuntimeType { pub fn load_config(&self) -> Result, String> { match self { CoretimeRuntimeType::Rococo => Ok(Box::new(GenericChainSpec::from_json_bytes( - &include_bytes!("../../../parachains/chain-specs/coretime-rococo.json")[..], + &include_bytes!("../../chain-specs/coretime-rococo.json")[..], )?)), CoretimeRuntimeType::RococoLocal => Ok(Box::new(rococo::local_config(*self, "rococo-local"))), CoretimeRuntimeType::RococoDevelopment => Ok(Box::new(rococo::local_config(*self, "rococo-dev"))), + CoretimeRuntimeType::Westend => Ok(Box::new(GenericChainSpec::from_json_bytes( + &include_bytes!("../../../parachains/chain-specs/coretime-westend.json")[..], + )?)), CoretimeRuntimeType::WestendLocal => Ok(Box::new(westend::local_config(*self, "westend-local"))), CoretimeRuntimeType::WestendDevelopment => @@ -213,6 +220,7 @@ pub mod westend { use parachains_common::{AccountId, AuraId, Balance}; use sp_core::sr25519; + pub(crate) const CORETIME_WESTEND: &str = "coretime-westend"; pub(crate) const CORETIME_WESTEND_LOCAL: &str = "coretime-westend-local"; pub(crate) const CORETIME_WESTEND_DEVELOPMENT: &str = "coretime-westend-dev"; const CORETIME_WESTEND_ED: Balance = coretime_westend_runtime::ExistentialDeposit::get(); diff --git a/cumulus/polkadot-parachain/src/command.rs b/cumulus/polkadot-parachain/src/command.rs index 3be232ce698..acba32f048b 100644 --- a/cumulus/polkadot-parachain/src/command.rs +++ b/cumulus/polkadot-parachain/src/command.rs @@ -794,6 +794,7 @@ pub fn run() -> Result<()> { chain_spec::coretime::CoretimeRuntimeType::Rococo | chain_spec::coretime::CoretimeRuntimeType::RococoLocal | chain_spec::coretime::CoretimeRuntimeType::RococoDevelopment | + chain_spec::coretime::CoretimeRuntimeType::Westend | chain_spec::coretime::CoretimeRuntimeType::WestendLocal | chain_spec::coretime::CoretimeRuntimeType::WestendDevelopment => crate::service::start_generic_aura_lookahead_node::< diff --git a/cumulus/scripts/create_coretime_westend_spec.sh b/cumulus/scripts/create_coretime_westend_spec.sh index 90996f4a74f..516dc06ac66 100755 --- a/cumulus/scripts/create_coretime_westend_spec.sh +++ b/cumulus/scripts/create_coretime_westend_spec.sh @@ -39,13 +39,7 @@ cat chain-spec-plain.json | jq --rawfile code rt-hex.txt '.genesis.runtimeGenesi | jq '.chainType = "Live"' \ | jq '.bootNodes = [ "/dns/westend-coretime-collator-0.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWP93Dzk8T7GWxyWw9jhLcz8Pksokk3R9vL2eEH337bNkT", - "/dns/westend-coretime-collator-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWMh2imeAzsZKGQgm2cv6Uoep3GBYtwGfujt1bs5YfVzkH", - "/dns/westend-coretime-collator-2.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWAys2hVpF7AN8hYGnu1T6XYFRGKeBFqD8q5LUcvWXRLg8", - "/dns/westend-coretime-collator-3.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWSGgmiRryoi7A3qAmeYWgmVeGQkk66PrhDjJ6ZPP555as", - "/dns/westend-coretime-connect-0.polkadot.io/tcp/443/wss/p2p/12D3KooWP93Dzk8T7GWxyWw9jhLcz8Pksokk3R9vL2eEH337bNkT", - "/dns/westend-coretime-connect-1.polkadot.io/tcp/443/wss/p2p/12D3KooWMh2imeAzsZKGQgm2cv6Uoep3GBYtwGfujt1bs5YfVzkH", - "/dns/westend-coretime-connect-2.polkadot.io/tcp/443/wss/p2p/12D3KooWAys2hVpF7AN8hYGnu1T6XYFRGKeBFqD8q5LUcvWXRLg8", - "/dns/westend-coretime-connect-3.polkadot.io/tcp/443/wss/p2p/12D3KooWSGgmiRryoi7A3qAmeYWgmVeGQkk66PrhDjJ6ZPP555as", + "/dns/westend-coretime-collator-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWMh2imeAzsZKGQgm2cv6Uoep3GBYtwGfujt1bs5YfVzkH" ]' \ | jq '.relay_chain = "westend"' \ | jq --argjson para_id $para_id '.para_id = $para_id' \ @@ -53,37 +47,21 @@ cat chain-spec-plain.json | jq --rawfile code rt-hex.txt '.genesis.runtimeGenesi | jq '.genesis.runtimeGenesis.patch.balances.balances = []' \ | jq '.genesis.runtimeGenesis.patch.collatorSelection.invulnerables = [ "5GKXTtB7RG3mLJ2kT4AkDXoxvKCFDVUdwyRmeMEbX3gBwcGi", - "5DknBCD1h49nc8eqnm6XtHz3bMQm5hfMuGYcLenRfCmpnBJG", - "5D52g9Mt9jQnZn6hwYhv649QYqGwhjygxkpb6rm3FYzYHEs3", - "5Egx2B41PYj8uvuhkNJeucA54h6Xmi7ZH9wqrZLwj3CuvQKA" + "5DknBCD1h49nc8eqnm6XtHz3bMQm5hfMuGYcLenRfCmpnBJG" ]' \ | jq '.genesis.runtimeGenesis.patch.session.keys = [ [ "5GKXTtB7RG3mLJ2kT4AkDXoxvKCFDVUdwyRmeMEbX3gBwcGi", "5GKXTtB7RG3mLJ2kT4AkDXoxvKCFDVUdwyRmeMEbX3gBwcGi", { - "aura": "0xbc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c" + "aura": "5GKXTtB7RG3mLJ2kT4AkDXoxvKCFDVUdwyRmeMEbX3gBwcGi" } ], [ "5DknBCD1h49nc8eqnm6XtHz3bMQm5hfMuGYcLenRfCmpnBJG", "5DknBCD1h49nc8eqnm6XtHz3bMQm5hfMuGYcLenRfCmpnBJG", { - "aura": "0x4acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28" - } - ], - [ - "5D52g9Mt9jQnZn6hwYhv649QYqGwhjygxkpb6rm3FYzYHEs3", - "5D52g9Mt9jQnZn6hwYhv649QYqGwhjygxkpb6rm3FYzYHEs3", - { - "aura": "0x2c7b95155708c10616b6f1a77a84f3d92c9a0272609ed24dbb7e6bdb81b53e76" - } - ], - [ - "5Egx2B41PYj8uvuhkNJeucA54h6Xmi7ZH9wqrZLwj3CuvQKA", - "5Egx2B41PYj8uvuhkNJeucA54h6Xmi7ZH9wqrZLwj3CuvQKA", - { - "aura": "0x741cfb39ec61bc76824ccec62d61670a80a890e0e21d58817f84040d3ec54474" + "aura": "5DknBCD1h49nc8eqnm6XtHz3bMQm5hfMuGYcLenRfCmpnBJG" } ] ]' \ diff --git a/prdoc/pr_3272.prdoc b/prdoc/pr_3272.prdoc new file mode 100644 index 00000000000..a03113cf973 --- /dev/null +++ b/prdoc/pr_3272.prdoc @@ -0,0 +1,11 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Westend Coretime Runtime + +doc: + - audience: Runtime User + description: | + Add the Broker pallet to the Westend Coretime Chain runtime for the main functionality and sales to start. + +crates: [ ] diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 02d5bb55429..24c8f6f48e9 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -2027,7 +2027,7 @@ pub struct CoretimeProvider; impl CoretimeInterface for CoretimeProvider { type AccountId = AccountId; type Balance = Balance; - type RealyChainBlockNumberProvider = System; + type RelayChainBlockNumberProvider = System; fn request_core_count(_count: CoreIndex) {} fn request_revenue_info_at(_when: u32) {} fn credit_account(_who: Self::AccountId, _amount: Self::Balance) {} diff --git a/substrate/frame/broker/src/coretime_interface.rs b/substrate/frame/broker/src/coretime_interface.rs index 9e853e8f3fe..58efa7fa92b 100644 --- a/substrate/frame/broker/src/coretime_interface.rs +++ b/substrate/frame/broker/src/coretime_interface.rs @@ -51,7 +51,7 @@ pub enum CoreAssignment { pub type RCBlockNumberOf = as BlockNumberProvider>::BlockNumber; /// Relay chain block number provider of `T` that implements [`CoretimeInterface`]. -pub type RCBlockNumberProviderOf = ::RealyChainBlockNumberProvider; +pub type RCBlockNumberProviderOf = ::RelayChainBlockNumberProvider; /// Type able to accept Coretime scheduling instructions and provide certain usage information. /// Generally implemented by the Relay-chain or some means of communicating with it. @@ -65,7 +65,7 @@ pub trait CoretimeInterface { type Balance: AtLeast32BitUnsigned; /// A provider for the relay chain block number. - type RealyChainBlockNumberProvider: BlockNumberProvider; + type RelayChainBlockNumberProvider: BlockNumberProvider; /// Requests the Relay-chain to alter the number of schedulable cores to `count`. Under normal /// operation, the Relay-chain SHOULD send a `notify_core_count(count)` message back. @@ -128,7 +128,7 @@ pub trait CoretimeInterface { impl CoretimeInterface for () { type AccountId = (); type Balance = u64; - type RealyChainBlockNumberProvider = (); + type RelayChainBlockNumberProvider = (); fn request_core_count(_count: CoreIndex) {} fn request_revenue_info_at(_when: RCBlockNumberOf) {} diff --git a/substrate/frame/broker/src/mock.rs b/substrate/frame/broker/src/mock.rs index 19c72340353..ac327c4143e 100644 --- a/substrate/frame/broker/src/mock.rs +++ b/substrate/frame/broker/src/mock.rs @@ -77,7 +77,7 @@ pub struct TestCoretimeProvider; impl CoretimeInterface for TestCoretimeProvider { type AccountId = u64; type Balance = u64; - type RealyChainBlockNumberProvider = System; + type RelayChainBlockNumberProvider = System; fn request_core_count(count: CoreIndex) { CoreCountInbox::::put(count); } -- GitLab From e84ec9f22d93a2cd174be701621479992083a5b6 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Fri, 16 Feb 2024 15:11:42 +0100 Subject: [PATCH 075/133] Various nits and alignments for SP testnets found during bumping `polkadot-fellows` repo (#3359) --- .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 4 +--- .../bridge-hubs/bridge-hub-westend/src/lib.rs | 4 +--- .../runtimes/people/people-rococo/src/xcm_config.rs | 12 +++--------- .../runtimes/people/people-westend/src/xcm_config.rs | 12 +++--------- 4 files changed, 8 insertions(+), 24 deletions(-) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 066e37826b2..d1eac089f67 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -73,8 +73,6 @@ use testnet_parachains_constants::rococo::{ consensus::*, currency::*, fee::WeightToFee, snowbridge::INBOUND_QUEUE_PALLET_INDEX, time::*, }; -#[cfg(feature = "runtime-benchmarks")] -use bp_runtime::Chain; use bp_runtime::HeaderId; use bridge_hub_common::{ message_queue::{NarrowOriginToSibling, ParaIdToSibling}, @@ -1298,7 +1296,7 @@ impl_runtime_apis! { impl BridgeMessagesConfig for Runtime { fn is_relayer_rewarded(relayer: &Self::AccountId) -> bool { let bench_lane_id = >::bench_lane_id(); - let bridged_chain_id = bp_bridge_hub_westend::BridgeHubWestend::ID; + let bridged_chain_id = bridge_to_westend_config::BridgeHubWestendChainId::get(); pallet_bridge_relayers::Pallet::::relayer_reward( relayer, bp_relayers::RewardsAccountParams::new( diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 79d251517c6..8ffea24b3cc 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -69,8 +69,6 @@ pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; pub use sp_runtime::{MultiAddress, Perbill, Permill}; use xcm_config::{XcmOriginToTransactDispatchOrigin, XcmRouter}; -#[cfg(feature = "runtime-benchmarks")] -use bp_runtime::Chain; use bp_runtime::HeaderId; #[cfg(any(feature = "std", test))] @@ -991,7 +989,7 @@ impl_runtime_apis! { impl BridgeMessagesConfig for Runtime { fn is_relayer_rewarded(relayer: &Self::AccountId) -> bool { let bench_lane_id = >::bench_lane_id(); - let bridged_chain_id = bp_bridge_hub_rococo::BridgeHubRococo::ID; + let bridged_chain_id = bridge_to_rococo_config::BridgeHubRococoChainId::get(); pallet_bridge_relayers::Pallet::::relayer_reward( relayer, bp_relayers::RewardsAccountParams::new( diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs index e75b048b3b3..311128a17ca 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs @@ -27,7 +27,8 @@ use pallet_xcm::XcmPassthrough; use parachains_common::{ impls::ToStakingPot, xcm_config::{ - AllSiblingSystemParachains, ConcreteAssetFromSystem, RelayOrOtherSystemParachains, + AllSiblingSystemParachains, ConcreteAssetFromSystem, ParentRelayOrSiblingParachains, + RelayOrOtherSystemParachains, }, TREASURY_PALLET_ID, }; @@ -147,13 +148,6 @@ impl Contains for ParentOrParentsPlurality { } } -pub struct ParentOrSiblings; -impl Contains for ParentOrSiblings { - fn contains(location: &Location) -> bool { - matches!(location.unpack(), (1, []) | (1, [Parachain(_)])) - } -} - /// A call filter for the XCM Transact instruction. This is a temporary measure until we properly /// account for proof size weights. /// @@ -219,7 +213,7 @@ pub type Barrier = TrailingSetTopicAsId< // Parent and its pluralities (i.e. governance bodies) get free execution. AllowExplicitUnpaidExecutionFrom, // Subscriptions for version tracking are OK. - AllowSubscriptionsFrom, + AllowSubscriptionsFrom, ), UniversalLocation, ConstU32<8>, diff --git a/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs index ee0691fd996..4b7da91c17e 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs @@ -27,7 +27,8 @@ use pallet_xcm::XcmPassthrough; use parachains_common::{ impls::ToStakingPot, xcm_config::{ - AllSiblingSystemParachains, ConcreteAssetFromSystem, RelayOrOtherSystemParachains, + AllSiblingSystemParachains, ConcreteAssetFromSystem, ParentRelayOrSiblingParachains, + RelayOrOtherSystemParachains, }, TREASURY_PALLET_ID, }; @@ -147,13 +148,6 @@ impl Contains for ParentOrParentsPlurality { } } -pub struct ParentOrSiblings; -impl Contains for ParentOrSiblings { - fn contains(location: &Location) -> bool { - matches!(location.unpack(), (1, []) | (1, [Parachain(_)])) - } -} - pub struct FellowsPlurality; impl Contains for FellowsPlurality { fn contains(location: &Location) -> bool { @@ -227,7 +221,7 @@ pub type Barrier = TrailingSetTopicAsId< // get free execution. AllowExplicitUnpaidExecutionFrom<(ParentOrParentsPlurality, FellowsPlurality)>, // Subscriptions for version tracking are OK. - AllowSubscriptionsFrom, + AllowSubscriptionsFrom, ), UniversalLocation, ConstU32<8>, -- GitLab From 25988bed0bdce5784a8906b19b73dd16d4a30d9f Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Fri, 16 Feb 2024 13:03:04 -0300 Subject: [PATCH 076/133] fix(zombienet): docker `img` version to use in merge queues for bridges (#3337) Fix image tag to use in merge queues tests, currently is failing because is using the wrong tag (e.g https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/jobs/5224342). Thx! --- .gitlab/pipeline/zombienet/bridges.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab/pipeline/zombienet/bridges.yml b/.gitlab/pipeline/zombienet/bridges.yml index f7c32d21c55..4278f59b1e9 100644 --- a/.gitlab/pipeline/zombienet/bridges.yml +++ b/.gitlab/pipeline/zombienet/bridges.yml @@ -7,10 +7,11 @@ - .kubernetes-env - .zombienet-refs rules: - - !reference [.build-refs, rules] + # Docker images have different tag in merge queues - if: $CI_COMMIT_REF_NAME =~ /^gh-readonly-queue.*$/ variables: DOCKER_IMAGES_VERSION: ${CI_COMMIT_SHORT_SHA} + - !reference [.build-refs, rules] before_script: - echo "Zombienet Tests Config" - echo "${ZOMBIENET_IMAGE}" -- GitLab From 612587b7b6d51e04706a4b25097eb05f19fecde8 Mon Sep 17 00:00:00 2001 From: ordian Date: Sat, 17 Feb 2024 03:00:22 +0100 Subject: [PATCH 077/133] do not block finality for "disabled" disputes (#3358) - [x] test with zombienet-sdk - [x] prdoc Relevant Issues: https://github.com/paritytech/polkadot-sdk/issues/3314 (connected to the cause) https://github.com/paritytech/polkadot-sdk/issues/3345 (solves) --------- Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- .../core/dispute-coordinator/src/import.rs | 44 +++++++++++++----- .../dispute-coordinator/src/initialized.rs | 34 +++----------- .../node/core/dispute-coordinator/src/lib.rs | 9 ++-- .../core/dispute-coordinator/src/tests.rs | 45 ++++++++++++++----- prdoc/pr_3358.prdoc | 11 +++++ 5 files changed, 92 insertions(+), 51 deletions(-) create mode 100644 prdoc/pr_3358.prdoc diff --git a/polkadot/node/core/dispute-coordinator/src/import.rs b/polkadot/node/core/dispute-coordinator/src/import.rs index 278561d5d00..d3a4625f0d2 100644 --- a/polkadot/node/core/dispute-coordinator/src/import.rs +++ b/polkadot/node/core/dispute-coordinator/src/import.rs @@ -52,7 +52,8 @@ pub struct CandidateEnvironment<'a> { executor_params: &'a ExecutorParams, /// Validator indices controlled by this node. controlled_indices: HashSet, - /// Indices of disabled validators at the `relay_parent`. + /// Indices of on-chain disabled validators at the `relay_parent` combined + /// with the off-chain state. disabled_indices: HashSet, } @@ -67,16 +68,15 @@ impl<'a> CandidateEnvironment<'a> { runtime_info: &'a mut RuntimeInfo, session_index: SessionIndex, relay_parent: Hash, + disabled_offchain: impl IntoIterator, ) -> Option> { - let disabled_indices = runtime_info + let disabled_onchain = runtime_info .get_disabled_validators(ctx.sender(), relay_parent) .await .unwrap_or_else(|err| { gum::info!(target: LOG_TARGET, ?err, "Failed to get disabled validators"); Vec::new() - }) - .into_iter() - .collect(); + }); let (session, executor_params) = match runtime_info .get_session_info_by_index(ctx.sender(), relay_parent, session_index) @@ -87,6 +87,24 @@ impl<'a> CandidateEnvironment<'a> { Err(_) => return None, }; + let n_validators = session.validators.len(); + let byzantine_threshold = polkadot_primitives::byzantine_threshold(n_validators); + // combine on-chain with off-chain disabled validators + // process disabled validators in the following order: + // - on-chain disabled validators + // - prioritized order of off-chain disabled validators + // deduplicate the list and take at most `byzantine_threshold` validators + let disabled_indices = { + let mut d: HashSet = HashSet::new(); + for v in disabled_onchain.into_iter().chain(disabled_offchain.into_iter()) { + if d.len() == byzantine_threshold { + break + } + d.insert(v); + } + d + }; + let controlled_indices = find_controlled_validator_indices(keystore, &session.validators); Some(Self { session_index, session, executor_params, controlled_indices, disabled_indices }) } @@ -116,7 +134,7 @@ impl<'a> CandidateEnvironment<'a> { &self.controlled_indices } - /// Indices of disabled validators at the `relay_parent`. + /// Indices of off-chain and on-chain disabled validators. pub fn disabled_indices(&'a self) -> &'a HashSet { &self.disabled_indices } @@ -237,13 +255,19 @@ impl CandidateVoteState { let supermajority_threshold = polkadot_primitives::supermajority_threshold(n_validators); - // We have a dispute, if we have votes on both sides: - let is_disputed = !votes.invalid.is_empty() && !votes.valid.raw().is_empty(); + // We have a dispute, if we have votes on both sides, with at least one invalid vote + // from non-disabled validator or with votes on both sides and confirmed. + let has_non_disabled_invalid_votes = + votes.invalid.keys().any(|i| !env.disabled_indices().contains(i)); + let byzantine_threshold = polkadot_primitives::byzantine_threshold(n_validators); + let votes_on_both_sides = !votes.valid.raw().is_empty() && !votes.invalid.is_empty(); + let is_confirmed = + votes_on_both_sides && (votes.voted_indices().len() > byzantine_threshold); + let is_disputed = + is_confirmed || (has_non_disabled_invalid_votes && !votes.valid.raw().is_empty()); let (dispute_status, byzantine_threshold_against) = if is_disputed { let mut status = DisputeStatus::active(); - let byzantine_threshold = polkadot_primitives::byzantine_threshold(n_validators); - let is_confirmed = votes.voted_indices().len() > byzantine_threshold; if is_confirmed { status = status.confirm(); }; diff --git a/polkadot/node/core/dispute-coordinator/src/initialized.rs b/polkadot/node/core/dispute-coordinator/src/initialized.rs index a1bcc1f0170..54e0410268f 100644 --- a/polkadot/node/core/dispute-coordinator/src/initialized.rs +++ b/polkadot/node/core/dispute-coordinator/src/initialized.rs @@ -17,7 +17,7 @@ //! Dispute coordinator subsystem in initialized state (after first active leaf is received). use std::{ - collections::{BTreeMap, HashSet, VecDeque}, + collections::{BTreeMap, VecDeque}, sync::Arc, }; @@ -970,6 +970,7 @@ impl Initialized { &mut self.runtime_info, session, relay_parent, + self.offchain_disabled_validators.iter(session), ) .await { @@ -1099,36 +1100,14 @@ impl Initialized { let new_state = import_result.new_state(); - let byzantine_threshold = polkadot_primitives::byzantine_threshold(n_validators); - // combine on-chain with off-chain disabled validators - // process disabled validators in the following order: - // - on-chain disabled validators - // - prioritized order of off-chain disabled validators - // deduplicate the list and take at most `byzantine_threshold` validators - let disabled_validators = { - let mut d: HashSet = HashSet::new(); - for v in env - .disabled_indices() - .iter() - .cloned() - .chain(self.offchain_disabled_validators.iter(session)) - { - if d.len() == byzantine_threshold { - break - } - d.insert(v); - } - d - }; - let is_included = self.scraper.is_candidate_included(&candidate_hash); let is_backed = self.scraper.is_candidate_backed(&candidate_hash); let own_vote_missing = new_state.own_vote_missing(); let is_disputed = new_state.is_disputed(); let is_confirmed = new_state.is_confirmed(); - let potential_spam = is_potential_spam(&self.scraper, &new_state, &candidate_hash, |v| { - disabled_validators.contains(v) - }); + let is_disabled = |v: &ValidatorIndex| env.disabled_indices().contains(v); + let potential_spam = + is_potential_spam(&self.scraper, &new_state, &candidate_hash, is_disabled); let allow_participation = !potential_spam; gum::trace!( @@ -1139,7 +1118,7 @@ impl Initialized { ?candidate_hash, confirmed = ?new_state.is_confirmed(), has_invalid_voters = ?!import_result.new_invalid_voters().is_empty(), - n_disabled_validators = ?disabled_validators.len(), + n_disabled_validators = ?env.disabled_indices().len(), "Is spam?" ); @@ -1439,6 +1418,7 @@ impl Initialized { &mut self.runtime_info, session, candidate_receipt.descriptor.relay_parent, + self.offchain_disabled_validators.iter(session), ) .await { diff --git a/polkadot/node/core/dispute-coordinator/src/lib.rs b/polkadot/node/core/dispute-coordinator/src/lib.rs index c3038fc0953..4b511e7430a 100644 --- a/polkadot/node/core/dispute-coordinator/src/lib.rs +++ b/polkadot/node/core/dispute-coordinator/src/lib.rs @@ -341,6 +341,8 @@ impl DisputeCoordinatorSubsystem { runtime_info, highest_session, leaf_hash, + // on startup we don't have any off-chain disabled state + std::iter::empty(), ) .await { @@ -370,10 +372,9 @@ impl DisputeCoordinatorSubsystem { }, }; let vote_state = CandidateVoteState::new(votes, &env, now); - let onchain_disabled = env.disabled_indices(); - let potential_spam = is_potential_spam(&scraper, &vote_state, candidate_hash, |v| { - onchain_disabled.contains(v) - }); + let is_disabled = |v: &ValidatorIndex| env.disabled_indices().contains(v); + let potential_spam = + is_potential_spam(&scraper, &vote_state, candidate_hash, is_disabled); let is_included = scraper.is_candidate_included(&vote_state.votes().candidate_receipt.hash()); diff --git a/polkadot/node/core/dispute-coordinator/src/tests.rs b/polkadot/node/core/dispute-coordinator/src/tests.rs index af384256c4f..0360e357bee 100644 --- a/polkadot/node/core/dispute-coordinator/src/tests.rs +++ b/polkadot/node/core/dispute-coordinator/src/tests.rs @@ -2645,14 +2645,23 @@ fn participation_with_onchain_disabling_unconfirmed() { .await; handle_disabled_validators_queries(&mut virtual_overseer, vec![disabled_index]).await; - handle_approval_vote_request(&mut virtual_overseer, &candidate_hash, HashMap::new()) - .await; - assert_eq!(confirmation_rx.await, Ok(ImportStatementsResult::ValidImport)); // we should not participate due to disabled indices on chain assert!(virtual_overseer.recv().timeout(TEST_TIMEOUT).await.is_none()); + { + // make sure the dispute is not active + let (tx, rx) = oneshot::channel(); + virtual_overseer + .send(FromOrchestra::Communication { + msg: DisputeCoordinatorMessage::ActiveDisputes(tx), + }) + .await; + + assert_eq!(rx.await.unwrap().len(), 0); + } + // Scenario 2: unconfirmed dispute with non-disabled validator against. // Expectation: even if the dispute is unconfirmed, we should participate // once we receive an invalid vote from a non-disabled validator. @@ -2679,6 +2688,9 @@ fn participation_with_onchain_disabling_unconfirmed() { }) .await; + handle_approval_vote_request(&mut virtual_overseer, &candidate_hash, HashMap::new()) + .await; + assert_eq!(confirmation_rx.await, Ok(ImportStatementsResult::ValidImport)); participation_with_distribution( @@ -2710,7 +2722,7 @@ fn participation_with_onchain_disabling_unconfirmed() { .await; let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone(); - assert_eq!(votes.valid.raw().len(), 2); // 3+1 => we have participated + assert_eq!(votes.valid.raw().len(), 2); // 1+1 => we have participated assert_eq!(votes.invalid.len(), 2); } @@ -2832,6 +2844,7 @@ fn participation_with_onchain_disabling_confirmed() { #[test] fn participation_with_offchain_disabling() { + sp_tracing::init_for_tests(); test_harness(|mut test_state, mut virtual_overseer| { Box::pin(async move { let session = 1; @@ -2968,17 +2981,23 @@ fn participation_with_offchain_disabling() { // let's disable validators 3, 4 on chain, but this should not affect this import let disabled_validators = vec![ValidatorIndex(3), ValidatorIndex(4)]; handle_disabled_validators_queries(&mut virtual_overseer, disabled_validators).await; - handle_approval_vote_request( - &mut virtual_overseer, - &another_candidate_hash, - HashMap::new(), - ) - .await; assert_eq!(confirmation_rx.await, Ok(ImportStatementsResult::ValidImport)); // we should not participate since due to offchain disabling assert!(virtual_overseer.recv().timeout(TEST_TIMEOUT).await.is_none()); + { + // make sure the new dispute is not active + let (tx, rx) = oneshot::channel(); + virtual_overseer + .send(FromOrchestra::Communication { + msg: DisputeCoordinatorMessage::ActiveDisputes(tx), + }) + .await; + + assert_eq!(rx.await.unwrap().len(), 1); + } + // now import enough votes for dispute confirmation // even though all of these votes are from (on chain) disabled validators let mut statements = Vec::new(); @@ -3007,6 +3026,12 @@ fn participation_with_offchain_disabling() { }) .await; + handle_approval_vote_request( + &mut virtual_overseer, + &another_candidate_hash, + HashMap::new(), + ) + .await; assert_eq!(confirmation_rx.await, Ok(ImportStatementsResult::ValidImport)); participation_with_distribution( diff --git a/prdoc/pr_3358.prdoc b/prdoc/pr_3358.prdoc new file mode 100644 index 00000000000..b6a03b3872f --- /dev/null +++ b/prdoc/pr_3358.prdoc @@ -0,0 +1,11 @@ +title: Do not stall finality on spam disputes + +doc: + - audience: Node Operator + description: | + This PR fixes the issue that periodically caused + finality stalls on Kusama due to disputes happening + there in combination with disputes spam protection mechanism. + See: https://github.com/paritytech/polkadot-sdk/issues/3345 + +crates: [ ] -- GitLab From de73dd9ac5224d51f45e93de31abae3498e95640 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Sat, 17 Feb 2024 11:18:15 +0100 Subject: [PATCH 078/133] rpc server: add rate limiting middleware (#3301) Add RPC server rate limiting which can be utilized by the CLI `--rpc-rate-limit ` Resolves first part of https://github.com/paritytech/polkadot-sdk/issues/3028 //cc @PierreBesson @kogeler you might be interested in this one --------- Co-authored-by: James Wilson Co-authored-by: Xiliang Chen --- Cargo.lock | 65 +++++++++++ cumulus/test/service/src/lib.rs | 1 + polkadot/node/test/service/src/lib.rs | 1 + prdoc/pr_3301.prdoc | 11 ++ .../bin/node/cli/benches/block_production.rs | 1 + .../bin/node/cli/benches/transaction_pool.rs | 1 + substrate/client/cli/src/commands/run_cmd.rs | 18 ++- substrate/client/cli/src/config.rs | 8 +- substrate/client/cli/src/runner.rs | 1 + substrate/client/rpc-servers/Cargo.toml | 1 + substrate/client/rpc-servers/src/lib.rs | 56 +++++---- .../client/rpc-servers/src/middleware/mod.rs | 4 + .../rpc-servers/src/middleware/rate_limit.rs | 107 ++++++++++++++++++ substrate/client/service/src/config.rs | 3 + substrate/client/service/src/lib.rs | 1 + substrate/client/service/test/src/lib.rs | 1 + 16 files changed, 257 insertions(+), 23 deletions(-) create mode 100644 prdoc/pr_3301.prdoc create mode 100644 substrate/client/rpc-servers/src/middleware/rate_limit.rs diff --git a/Cargo.lock b/Cargo.lock index 0d55487f8f1..e00cd343c50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6187,6 +6187,24 @@ dependencies = [ "testnet-parachains-constants", ] +[[package]] +name = "governor" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "821239e5672ff23e2a7060901fa622950bbd80b649cdaadd78d1c1767ed14eb4" +dependencies = [ + "cfg-if", + "dashmap", + "futures", + "futures-timer", + "no-std-compat", + "nonzero_ext", + "parking_lot 0.12.1", + "quanta", + "rand", + "smallvec", +] + [[package]] name = "group" version = "0.13.0" @@ -7865,6 +7883,15 @@ dependencies = [ "libc", ] +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + [[package]] name = "macro_magic" version = "0.5.0" @@ -8531,6 +8558,12 @@ dependencies = [ "libc", ] +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" + [[package]] name = "no-std-net" version = "0.6.0" @@ -8781,6 +8814,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nonzero_ext" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" + [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -14352,6 +14391,22 @@ dependencies = [ "thiserror", ] +[[package]] +name = "quanta" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17e662a7a8291a865152364c20c7abc5e60486ab2001e8ec10b24862de0b9ab" +dependencies = [ + "crossbeam-utils", + "libc", + "mach2", + "once_cell", + "raw-cpuid", + "wasi 0.11.0+wasi-snapshot-preview1", + "web-sys", + "winapi", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -14512,6 +14567,15 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "raw-cpuid" +version = "10.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "rawpointer" version = "0.2.1" @@ -16496,6 +16560,7 @@ name = "sc-rpc-server" version = "11.0.0" dependencies = [ "futures", + "governor", "http", "hyper", "jsonrpsee", diff --git a/cumulus/test/service/src/lib.rs b/cumulus/test/service/src/lib.rs index aa2c4af97df..229bde4d19c 100644 --- a/cumulus/test/service/src/lib.rs +++ b/cumulus/test/service/src/lib.rs @@ -801,6 +801,7 @@ pub fn node_config( rpc_max_subs_per_conn: Default::default(), rpc_port: 9945, rpc_message_buffer_capacity: Default::default(), + rpc_rate_limit: None, prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, diff --git a/polkadot/node/test/service/src/lib.rs b/polkadot/node/test/service/src/lib.rs index e4eec32baf2..a3af977ab3e 100644 --- a/polkadot/node/test/service/src/lib.rs +++ b/polkadot/node/test/service/src/lib.rs @@ -186,6 +186,7 @@ pub fn node_config( rpc_max_subs_per_conn: Default::default(), rpc_port: 9944, rpc_message_buffer_capacity: Default::default(), + rpc_rate_limit: None, prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, diff --git a/prdoc/pr_3301.prdoc b/prdoc/pr_3301.prdoc new file mode 100644 index 00000000000..19d1c5f1c18 --- /dev/null +++ b/prdoc/pr_3301.prdoc @@ -0,0 +1,11 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: rpc server add rate limiting. + +doc: + - audience: Node Operator + description: | + Add rate limiting for RPC server which can be utilized by the CLI `--rpc-rate-limit ` + The rate-limiting is disabled by default. +crates: [ ] diff --git a/substrate/bin/node/cli/benches/block_production.rs b/substrate/bin/node/cli/benches/block_production.rs index c17c12dfef1..d28cfbddfd2 100644 --- a/substrate/bin/node/cli/benches/block_production.rs +++ b/substrate/bin/node/cli/benches/block_production.rs @@ -84,6 +84,7 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { rpc_max_subs_per_conn: Default::default(), rpc_port: 9944, rpc_message_buffer_capacity: Default::default(), + rpc_rate_limit: None, prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, diff --git a/substrate/bin/node/cli/benches/transaction_pool.rs b/substrate/bin/node/cli/benches/transaction_pool.rs index 0d0d3a072d8..1e25b7ce6fd 100644 --- a/substrate/bin/node/cli/benches/transaction_pool.rs +++ b/substrate/bin/node/cli/benches/transaction_pool.rs @@ -80,6 +80,7 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { rpc_max_subs_per_conn: Default::default(), rpc_port: 9944, rpc_message_buffer_capacity: Default::default(), + rpc_rate_limit: None, prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, diff --git a/substrate/client/cli/src/commands/run_cmd.rs b/substrate/client/cli/src/commands/run_cmd.rs index f7b0fc51049..ecff7eead7b 100644 --- a/substrate/client/cli/src/commands/run_cmd.rs +++ b/substrate/client/cli/src/commands/run_cmd.rs @@ -34,7 +34,10 @@ use sc_service::{ ChainSpec, Role, }; use sc_telemetry::TelemetryEndpoints; -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::{ + net::{IpAddr, Ipv4Addr, SocketAddr}, + num::NonZeroU32, +}; /// The `run` command used to run a node. #[derive(Debug, Clone, Parser)] @@ -82,6 +85,15 @@ pub struct RunCmd { )] pub rpc_methods: RpcMethods, + /// RPC rate limiting (calls/minute) for each connection. + /// + /// This is disabled by default. + /// + /// For example `--rpc-rate-limit 10` will maximum allow + /// 10 calls per minute per connection. + #[arg(long)] + pub rpc_rate_limit: Option, + /// Set the maximum RPC request payload size for both HTTP and WS in megabytes. #[arg(long, default_value_t = RPC_DEFAULT_MAX_REQUEST_SIZE_MB)] pub rpc_max_request_size: u32, @@ -399,6 +411,10 @@ impl CliConfiguration for RunCmd { Ok(self.rpc_max_subscriptions_per_connection) } + fn rpc_rate_limit(&self) -> Result> { + Ok(self.rpc_rate_limit) + } + fn transaction_pool(&self, is_dev: bool) -> Result { Ok(self.pool_config.transaction_pool(is_dev)) } diff --git a/substrate/client/cli/src/config.rs b/substrate/client/cli/src/config.rs index defcc4a8a69..78015cf8373 100644 --- a/substrate/client/cli/src/config.rs +++ b/substrate/client/cli/src/config.rs @@ -33,7 +33,7 @@ use sc_service::{ BlocksPruning, ChainSpec, TracingReceiver, }; use sc_tracing::logging::LoggerBuilder; -use std::{net::SocketAddr, path::PathBuf}; +use std::{net::SocketAddr, num::NonZeroU32, path::PathBuf}; /// The maximum number of characters for a node name. pub(crate) const NODE_NAME_MAX_LENGTH: usize = 64; @@ -338,6 +338,11 @@ pub trait CliConfiguration: Sized { Ok(RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN) } + /// Rate limit calls per minute. + fn rpc_rate_limit(&self) -> Result> { + Ok(None) + } + /// Get the prometheus configuration (`None` if disabled) /// /// By default this is `None`. @@ -510,6 +515,7 @@ pub trait CliConfiguration: Sized { rpc_max_subs_per_conn: self.rpc_max_subscriptions_per_connection()?, rpc_port: DCV::rpc_listen_port(), rpc_message_buffer_capacity: self.rpc_buffer_capacity_per_connection()?, + rpc_rate_limit: self.rpc_rate_limit()?, prometheus_config: self .prometheus_config(DCV::prometheus_listen_port(), &chain_spec)?, telemetry_endpoints, diff --git a/substrate/client/cli/src/runner.rs b/substrate/client/cli/src/runner.rs index e37c8ab0e55..b4937db71e6 100644 --- a/substrate/client/cli/src/runner.rs +++ b/substrate/client/cli/src/runner.rs @@ -271,6 +271,7 @@ mod tests { rpc_max_subs_per_conn: Default::default(), rpc_message_buffer_capacity: Default::default(), rpc_port: 9944, + rpc_rate_limit: None, prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, diff --git a/substrate/client/rpc-servers/Cargo.toml b/substrate/client/rpc-servers/Cargo.toml index 7dd525ada65..3e04b927a22 100644 --- a/substrate/client/rpc-servers/Cargo.toml +++ b/substrate/client/rpc-servers/Cargo.toml @@ -27,3 +27,4 @@ http = "0.2.8" hyper = "0.14.27" futures = "0.3.29" pin-project = "1.1.3" +governor = "0.6.0" diff --git a/substrate/client/rpc-servers/src/lib.rs b/substrate/client/rpc-servers/src/lib.rs index 29b34b2945b..a22b7309ac1 100644 --- a/substrate/client/rpc-servers/src/lib.rs +++ b/substrate/client/rpc-servers/src/lib.rs @@ -22,7 +22,9 @@ pub mod middleware; -use std::{convert::Infallible, error::Error as StdError, net::SocketAddr, time::Duration}; +use std::{ + convert::Infallible, error::Error as StdError, net::SocketAddr, num::NonZeroU32, time::Duration, +}; use http::header::HeaderValue; use hyper::{ @@ -31,10 +33,7 @@ use hyper::{ }; use jsonrpsee::{ server::{ - middleware::{ - http::{HostFilterLayer, ProxyGetRequestLayer}, - rpc::RpcServiceBuilder, - }, + middleware::http::{HostFilterLayer, ProxyGetRequestLayer}, stop_channel, ws, PingConfig, StopHandle, TowerServiceBuilder, }, Methods, RpcModule, @@ -43,11 +42,14 @@ use tokio::net::TcpListener; use tower::Service; use tower_http::cors::{AllowOrigin, CorsLayer}; -pub use jsonrpsee::core::{ - id_providers::{RandomIntegerIdProvider, RandomStringIdProvider}, - traits::IdProvider, +pub use jsonrpsee::{ + core::{ + id_providers::{RandomIntegerIdProvider, RandomStringIdProvider}, + traits::IdProvider, + }, + server::middleware::rpc::RpcServiceBuilder, }; -pub use middleware::{MetricsLayer, RpcMetrics}; +pub use middleware::{MetricsLayer, RateLimitLayer, RpcMetrics}; const MEGABYTE: u32 = 1024 * 1024; @@ -79,12 +81,26 @@ pub struct Config<'a, M: Send + Sync + 'static> { pub id_provider: Option>, /// Tokio runtime handle. pub tokio_handle: tokio::runtime::Handle, + /// Rate limit calls per minute. + pub rate_limit: Option, +} + +#[derive(Debug, Clone)] +struct PerConnection { + methods: Methods, + stop_handle: StopHandle, + metrics: Option, + tokio_handle: tokio::runtime::Handle, + service_builder: TowerServiceBuilder, } /// Start RPC server listening on given address. -pub async fn start_server( +pub async fn start_server( config: Config<'_, M>, -) -> Result> { +) -> Result> +where + M: Send + Sync, +{ let Config { addrs, cors, @@ -97,6 +113,7 @@ pub async fn start_server( id_provider, tokio_handle, rpc_api, + rate_limit, } = config; let std_listener = TcpListener::bind(addrs.as_slice()).await?.into_std()?; @@ -153,7 +170,13 @@ pub async fn start_server( let transport_label = if is_websocket { "ws" } else { "http" }; let metrics = metrics.map(|m| MetricsLayer::new(m, transport_label)); - let rpc_middleware = RpcServiceBuilder::new().option_layer(metrics.clone()); + let rate_limit = rate_limit.map(|r| RateLimitLayer::per_minute(r)); + + // NOTE: The metrics needs to run first to include rate-limited calls in the + // metrics. + let rpc_middleware = + RpcServiceBuilder::new().option_layer(metrics.clone()).option_layer(rate_limit); + let mut svc = service_builder.set_rpc_middleware(rpc_middleware).build(methods, stop_handle); @@ -245,12 +268,3 @@ fn format_cors(maybe_cors: Option<&Vec>) -> String { format!("{:?}", ["*"]) } } - -#[derive(Clone)] -struct PerConnection { - methods: Methods, - stop_handle: StopHandle, - metrics: Option, - tokio_handle: tokio::runtime::Handle, - service_builder: TowerServiceBuilder, -} diff --git a/substrate/client/rpc-servers/src/middleware/mod.rs b/substrate/client/rpc-servers/src/middleware/mod.rs index 1c193058244..cac516913d3 100644 --- a/substrate/client/rpc-servers/src/middleware/mod.rs +++ b/substrate/client/rpc-servers/src/middleware/mod.rs @@ -18,6 +18,10 @@ //! JSON-RPC specific middleware. +/// Grafana metrics middleware. pub mod metrics; +/// Rate limit middleware. +pub mod rate_limit; pub use metrics::*; +pub use rate_limit::*; diff --git a/substrate/client/rpc-servers/src/middleware/rate_limit.rs b/substrate/client/rpc-servers/src/middleware/rate_limit.rs new file mode 100644 index 00000000000..cdcc3ebf66f --- /dev/null +++ b/substrate/client/rpc-servers/src/middleware/rate_limit.rs @@ -0,0 +1,107 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! RPC rate limiting middleware. + +use std::{num::NonZeroU32, sync::Arc, time::Duration}; + +use futures::future::{BoxFuture, FutureExt}; +use governor::{ + clock::{Clock, DefaultClock, QuantaClock}, + middleware::NoOpMiddleware, + state::{InMemoryState, NotKeyed}, + Jitter, +}; +use jsonrpsee::{ + server::middleware::rpc::RpcServiceT, + types::{ErrorObject, Id, Request}, + MethodResponse, +}; + +type RateLimitInner = governor::RateLimiter; + +const MAX_JITTER: Duration = Duration::from_millis(50); +const MAX_RETRIES: usize = 10; + +/// JSON-RPC rate limit middleware layer. +#[derive(Debug, Clone)] +pub struct RateLimitLayer(governor::Quota); + +impl RateLimitLayer { + /// Create new rate limit enforced per minute. + pub fn per_minute(n: NonZeroU32) -> Self { + Self(governor::Quota::per_minute(n)) + } +} + +/// JSON-RPC rate limit middleware +pub struct RateLimit { + service: S, + rate_limit: Arc, + clock: QuantaClock, +} + +impl tower::Layer for RateLimitLayer { + type Service = RateLimit; + + fn layer(&self, service: S) -> Self::Service { + let clock = QuantaClock::default(); + RateLimit { + service, + rate_limit: Arc::new(RateLimitInner::direct_with_clock(self.0, &clock)), + clock, + } + } +} + +impl<'a, S> RpcServiceT<'a> for RateLimit +where + S: Send + Sync + RpcServiceT<'a> + Clone + 'static, +{ + type Future = BoxFuture<'a, MethodResponse>; + + fn call(&self, req: Request<'a>) -> Self::Future { + let service = self.service.clone(); + let rate_limit = self.rate_limit.clone(); + let clock = self.clock.clone(); + + async move { + let mut attempts = 0; + let jitter = Jitter::up_to(MAX_JITTER); + + loop { + if attempts >= MAX_RETRIES { + break reject_too_many_calls(req.id); + } + + if let Err(rejected) = rate_limit.check() { + tokio::time::sleep(jitter + rejected.wait_time_from(clock.now())).await; + } else { + break service.call(req).await; + } + + attempts += 1; + } + } + .boxed() + } +} + +fn reject_too_many_calls(id: Id) -> MethodResponse { + MethodResponse::error(id, ErrorObject::owned(-32999, "RPC rate limit exceeded", None::<()>)) +} diff --git a/substrate/client/service/src/config.rs b/substrate/client/service/src/config.rs index 3e68f5b58de..74c5dd775b0 100644 --- a/substrate/client/service/src/config.rs +++ b/substrate/client/service/src/config.rs @@ -39,6 +39,7 @@ use sp_core::crypto::SecretString; use std::{ io, iter, net::SocketAddr, + num::NonZeroU32, path::{Path, PathBuf}, }; use tempfile::TempDir; @@ -102,6 +103,8 @@ pub struct Configuration { pub rpc_port: u16, /// The number of messages the JSON-RPC server is allowed to keep in memory. pub rpc_message_buffer_capacity: u32, + /// RPC rate limit per minute. + pub rpc_rate_limit: Option, /// Prometheus endpoint configuration. `None` if disabled. pub prometheus_config: Option, /// Telemetry service URL. `None` if disabled. diff --git a/substrate/client/service/src/lib.rs b/substrate/client/service/src/lib.rs index 1fbfd14c3be..d0adf4f7d5c 100644 --- a/substrate/client/service/src/lib.rs +++ b/substrate/client/service/src/lib.rs @@ -403,6 +403,7 @@ where id_provider: rpc_id_provider, cors: config.rpc_cors.as_ref(), tokio_handle: config.tokio_handle.clone(), + rate_limit: config.rpc_rate_limit, }; // TODO: https://github.com/paritytech/substrate/issues/13773 diff --git a/substrate/client/service/test/src/lib.rs b/substrate/client/service/test/src/lib.rs index 9b88300bf53..6148bb05fcf 100644 --- a/substrate/client/service/test/src/lib.rs +++ b/substrate/client/service/test/src/lib.rs @@ -254,6 +254,7 @@ fn node_config< rpc_max_subs_per_conn: Default::default(), rpc_port: 9944, rpc_message_buffer_capacity: Default::default(), + rpc_rate_limit: None, prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, -- GitLab From 8dc910c8a18ad91a18ff17c492f5cc3977a5133e Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Sat, 17 Feb 2024 12:34:34 +0100 Subject: [PATCH 079/133] Ensure referenda `TracksInfo` is sorted (#3325) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes: - Add `integrity_check` to trait `TracksInfo` to assert the sorting - Use the check in `integrity_test` - Rely upon sorted property in the `info` for speedup Note that the pallet already relies upon this (so far) untested assumption [here](https://github.com/paritytech/polkadot-sdk/blob/44c7a5eb8c375d77f685abf585961421e5f8b228/substrate/frame/referenda/src/lib.rs#L1280). --------- Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Bastian Köcher --- prdoc/pr_3308.prdoc | 2 +- prdoc/pr_3325.prdoc | 10 +++ substrate/frame/referenda/src/lib.rs | 5 ++ substrate/frame/referenda/src/types.rs | 91 +++++++++++++++++++++++++- 4 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 prdoc/pr_3325.prdoc diff --git a/prdoc/pr_3308.prdoc b/prdoc/pr_3308.prdoc index 611461e25ff..386cbd6230b 100644 --- a/prdoc/pr_3308.prdoc +++ b/prdoc/pr_3308.prdoc @@ -1,4 +1,4 @@ -title: Parachains-Aura: Only produce once per slot +title: "Parachains-Aura: Only produce once per slot" doc: - audience: Node Dev diff --git a/prdoc/pr_3325.prdoc b/prdoc/pr_3325.prdoc new file mode 100644 index 00000000000..eb8126dc912 --- /dev/null +++ b/prdoc/pr_3325.prdoc @@ -0,0 +1,10 @@ +title: "Ensure `TracksInfo` tracks are sorted by ID." + +doc: + - audience: Runtime Dev + description: | + Add a `integrity_check` function to trait `TracksInfo` and explicitly state that tracks must + always be sorted by ID. The referenda pallet now also uses this check in its `integrity_test`. + +crates: + - name: pallet-referenda diff --git a/substrate/frame/referenda/src/lib.rs b/substrate/frame/referenda/src/lib.rs index 8912f9ad217..c5bf2266e67 100644 --- a/substrate/frame/referenda/src/lib.rs +++ b/substrate/frame/referenda/src/lib.rs @@ -433,6 +433,11 @@ pub mod pallet { Self::do_try_state()?; Ok(()) } + + #[cfg(any(feature = "std", test))] + fn integrity_test() { + T::Tracks::check_integrity().expect("Static tracks configuration is valid."); + } } #[pallet::call] diff --git a/substrate/frame/referenda/src/types.rs b/substrate/frame/referenda/src/types.rs index 0a8fb4ff820..b3c583322cc 100644 --- a/substrate/frame/referenda/src/types.rs +++ b/substrate/frame/referenda/src/types.rs @@ -144,7 +144,10 @@ pub trait TracksInfo { /// The origin type from which a track is implied. type RuntimeOrigin; - /// Return the array of known tracks and their information. + /// Sorted array of known tracks and their information. + /// + /// The array MUST be sorted by `Id`. Consumers of this trait are advised to assert + /// [`Self::check_integrity`] prior to any use. fn tracks() -> &'static [(Self::Id, TrackInfo)]; /// Determine the voting track for the given `origin`. @@ -152,7 +155,23 @@ pub trait TracksInfo { /// Return the track info for track `id`, by default this just looks it up in `Self::tracks()`. fn info(id: Self::Id) -> Option<&'static TrackInfo> { - Self::tracks().iter().find(|x| x.0 == id).map(|x| &x.1) + let tracks = Self::tracks(); + let maybe_index = tracks.binary_search_by_key(&id, |t| t.0).ok()?; + + tracks.get(maybe_index).map(|(_, info)| info) + } + + /// Check assumptions about the static data that this trait provides. + fn check_integrity() -> Result<(), &'static str> + where + Balance: 'static, + Moment: 'static, + { + if Self::tracks().windows(2).all(|w| w[0].0 < w[1].0) { + Ok(()) + } else { + Err("The tracks that were returned by `tracks` were not sorted by `Id`") + } } } @@ -670,4 +689,72 @@ mod tests { assert_eq!(c.delay(pc(30).less_epsilon()), pc(100)); assert_eq!(c.delay(pc(0)), pc(100)); } + + #[test] + fn tracks_integrity_check_detects_unsorted() { + use crate::mock::RuntimeOrigin; + + pub struct BadTracksInfo; + impl TracksInfo for BadTracksInfo { + type Id = u8; + type RuntimeOrigin = ::PalletsOrigin; + fn tracks() -> &'static [(Self::Id, TrackInfo)] { + static DATA: [(u8, TrackInfo); 2] = [ + ( + 1u8, + TrackInfo { + name: "root", + max_deciding: 1, + decision_deposit: 10, + prepare_period: 4, + decision_period: 4, + confirm_period: 2, + min_enactment_period: 4, + min_approval: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(50), + ceil: Perbill::from_percent(100), + }, + min_support: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(0), + ceil: Perbill::from_percent(100), + }, + }, + ), + ( + 0u8, + TrackInfo { + name: "none", + max_deciding: 3, + decision_deposit: 1, + prepare_period: 2, + decision_period: 2, + confirm_period: 1, + min_enactment_period: 2, + min_approval: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(95), + ceil: Perbill::from_percent(100), + }, + min_support: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(90), + ceil: Perbill::from_percent(100), + }, + }, + ), + ]; + &DATA[..] + } + fn track_for(_: &Self::RuntimeOrigin) -> Result { + unimplemented!() + } + } + + assert_eq!( + BadTracksInfo::check_integrity(), + Err("The tracks that were returned by `tracks` were not sorted by `Id`") + ); + } } -- GitLab From 6185b002dfb54c73881257913a830416f2e17a15 Mon Sep 17 00:00:00 2001 From: s0me0ne-unkn0wn <48632512+s0me0ne-unkn0wn@users.noreply.github.com> Date: Sun, 18 Feb 2024 03:52:34 +0100 Subject: [PATCH 080/133] Remove unused `im-online` weights (#3373) A follow-up of #2265. Removes weights no longer needed. --- polkadot/runtime/rococo/src/weights/mod.rs | 1 - .../rococo/src/weights/pallet_im_online.rs | 76 ------------------ polkadot/runtime/westend/src/weights/mod.rs | 1 - .../westend/src/weights/pallet_im_online.rs | 77 ------------------- 4 files changed, 155 deletions(-) delete mode 100644 polkadot/runtime/rococo/src/weights/pallet_im_online.rs delete mode 100644 polkadot/runtime/westend/src/weights/pallet_im_online.rs diff --git a/polkadot/runtime/rococo/src/weights/mod.rs b/polkadot/runtime/rococo/src/weights/mod.rs index 0ba21ccf322..7328dca9393 100644 --- a/polkadot/runtime/rococo/src/weights/mod.rs +++ b/polkadot/runtime/rococo/src/weights/mod.rs @@ -23,7 +23,6 @@ pub mod pallet_bounties; pub mod pallet_child_bounties; pub mod pallet_conviction_voting; pub mod pallet_identity; -pub mod pallet_im_online; pub mod pallet_indices; pub mod pallet_message_queue; pub mod pallet_multisig; diff --git a/polkadot/runtime/rococo/src/weights/pallet_im_online.rs b/polkadot/runtime/rococo/src/weights/pallet_im_online.rs deleted file mode 100644 index b866426de52..00000000000 --- a/polkadot/runtime/rococo/src/weights/pallet_im_online.rs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . - -//! Autogenerated weights for `pallet_im_online` -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `build-host`, CPU: `AMD EPYC 7601 32-Core Processor` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("rococo-dev"), DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot -// benchmark -// pallet -// --chain=rococo-dev -// --steps=50 -// --repeat=20 -// --pallet=pallet_im_online -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --header=./file_header.txt -// --output=./runtime/rococo/src/weights/ - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(missing_docs)] - -use frame_support::{traits::Get, weights::Weight}; -use core::marker::PhantomData; - -/// Weight functions for `pallet_im_online`. -pub struct WeightInfo(PhantomData); -impl pallet_im_online::WeightInfo for WeightInfo { - /// Storage: Session Validators (r:1 w:0) - /// Proof Skipped: Session Validators (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: Session CurrentIndex (r:1 w:0) - /// Proof Skipped: Session CurrentIndex (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ImOnline Keys (r:1 w:0) - /// Proof: ImOnline Keys (max_values: Some(1), max_size: Some(320002), added: 320497, mode: MaxEncodedLen) - /// Storage: unknown `0x39e295d143ed41353167609a3d816584` (r:1 w:0) - /// Proof Skipped: unknown `0x39e295d143ed41353167609a3d816584` (r:1 w:0) - /// Storage: ImOnline ReceivedHeartbeats (r:1 w:1) - /// Proof: ImOnline ReceivedHeartbeats (max_values: None, max_size: Some(1028), added: 3503, mode: MaxEncodedLen) - /// Storage: ImOnline AuthoredBlocks (r:1 w:0) - /// Proof: ImOnline AuthoredBlocks (max_values: None, max_size: Some(56), added: 2531, mode: MaxEncodedLen) - /// The range of component `k` is `[1, 1000]`. - fn validate_unsigned_and_then_heartbeat(k: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `394 + k * (32 ±0)` - // Estimated: `321487 + k * (1761 ±0)` - // Minimum execution time: 132_910_000 picoseconds. - Weight::from_parts(149_854_501, 0) - .saturating_add(Weight::from_parts(0, 321487)) - // Standard Error: 3_317 - .saturating_add(Weight::from_parts(61_141, 0).saturating_mul(k.into())) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(Weight::from_parts(0, 1761).saturating_mul(k.into())) - } -} diff --git a/polkadot/runtime/westend/src/weights/mod.rs b/polkadot/runtime/westend/src/weights/mod.rs index d8a2ae5d2da..f6a9008d718 100644 --- a/polkadot/runtime/westend/src/weights/mod.rs +++ b/polkadot/runtime/westend/src/weights/mod.rs @@ -24,7 +24,6 @@ pub mod pallet_conviction_voting; pub mod pallet_election_provider_multi_phase; pub mod pallet_fast_unstake; pub mod pallet_identity; -pub mod pallet_im_online; pub mod pallet_indices; pub mod pallet_message_queue; pub mod pallet_multisig; diff --git a/polkadot/runtime/westend/src/weights/pallet_im_online.rs b/polkadot/runtime/westend/src/weights/pallet_im_online.rs deleted file mode 100644 index a83cd43804d..00000000000 --- a/polkadot/runtime/westend/src/weights/pallet_im_online.rs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . - -//! Autogenerated weights for `pallet_im_online` -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `build-host`, CPU: `AMD EPYC 7601 32-Core Processor` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("westend-dev"), DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot -// benchmark -// pallet -// --chain=westend-dev -// --steps=50 -// --repeat=20 -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --pallet=pallet_im_online -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --header=./file_header.txt -// --output=./runtime/westend/src/weights/ - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(missing_docs)] - -use frame_support::{traits::Get, weights::Weight}; -use core::marker::PhantomData; - -/// Weight functions for `pallet_im_online`. -pub struct WeightInfo(PhantomData); -impl pallet_im_online::WeightInfo for WeightInfo { - /// Storage: Session Validators (r:1 w:0) - /// Proof Skipped: Session Validators (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: Session CurrentIndex (r:1 w:0) - /// Proof Skipped: Session CurrentIndex (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ImOnline Keys (r:1 w:0) - /// Proof: ImOnline Keys (max_values: Some(1), max_size: Some(320002), added: 320497, mode: MaxEncodedLen) - /// Storage: ImOnline ReceivedHeartbeats (r:1 w:1) - /// Proof: ImOnline ReceivedHeartbeats (max_values: None, max_size: Some(1028), added: 3503, mode: MaxEncodedLen) - /// Storage: ImOnline AuthoredBlocks (r:1 w:0) - /// Proof: ImOnline AuthoredBlocks (max_values: None, max_size: Some(56), added: 2531, mode: MaxEncodedLen) - /// The range of component `k` is `[1, 1000]`. - fn validate_unsigned_and_then_heartbeat(k: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `361 + k * (32 ±0)` - // Estimated: `321487 + k * (1761 ±0)` - // Minimum execution time: 122_571_000 picoseconds. - Weight::from_parts(162_954_849, 0) - .saturating_add(Weight::from_parts(0, 321487)) - // Standard Error: 8_676 - .saturating_add(Weight::from_parts(11_122, 0).saturating_mul(k.into())) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(Weight::from_parts(0, 1761).saturating_mul(k.into())) - } -} -- GitLab From b179b83264b6766a1d588b467460bd18c4af1fbc Mon Sep 17 00:00:00 2001 From: Koute Date: Mon, 19 Feb 2024 00:32:01 +0900 Subject: [PATCH 081/133] Make `sp-keystore` `no_std`-compatible and fix the `build-runtimes-polkavm` CI job (#3363) Fixes https://github.com/paritytech/polkadot-sdk/issues/3352 --- .gitlab/pipeline/build.yml | 1 - Cargo.lock | 1 - .../externalities/src/extensions.rs | 16 ++++++------ substrate/primitives/keystore/Cargo.toml | 5 ++-- substrate/primitives/keystore/src/lib.rs | 26 ++++++++++++++----- 5 files changed, 30 insertions(+), 19 deletions(-) diff --git a/.gitlab/pipeline/build.yml b/.gitlab/pipeline/build.yml index c6682f7974b..15b4869997b 100644 --- a/.gitlab/pipeline/build.yml +++ b/.gitlab/pipeline/build.yml @@ -341,7 +341,6 @@ build-runtimes-polkavm: - SUBSTRATE_RUNTIME_TARGET=riscv cargo check -p westend-runtime - SUBSTRATE_RUNTIME_TARGET=riscv cargo check -p rococo-runtime - SUBSTRATE_RUNTIME_TARGET=riscv cargo check -p polkadot-test-runtime - allow_failure: true .build-subkey: stage: build diff --git a/Cargo.lock b/Cargo.lock index e00cd343c50..70600b1a35c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18720,7 +18720,6 @@ dependencies = [ "rand_chacha 0.2.2", "sp-core", "sp-externalities 0.25.0", - "thiserror", ] [[package]] diff --git a/substrate/primitives/externalities/src/extensions.rs b/substrate/primitives/externalities/src/extensions.rs index 282e6ea914a..d99dfe6cf53 100644 --- a/substrate/primitives/externalities/src/extensions.rs +++ b/substrate/primitives/externalities/src/extensions.rs @@ -78,16 +78,16 @@ macro_rules! decl_extension { $vis struct $ext_name (pub $inner); impl $crate::Extension for $ext_name { - fn as_mut_any(&mut self) -> &mut dyn std::any::Any { + fn as_mut_any(&mut self) -> &mut dyn core::any::Any { self } - fn type_id(&self) -> std::any::TypeId { - std::any::Any::type_id(self) + fn type_id(&self) -> core::any::TypeId { + core::any::Any::type_id(self) } } - impl std::ops::Deref for $ext_name { + impl core::ops::Deref for $ext_name { type Target = $inner; fn deref(&self) -> &Self::Target { @@ -95,7 +95,7 @@ macro_rules! decl_extension { } } - impl std::ops::DerefMut for $ext_name { + impl core::ops::DerefMut for $ext_name { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } @@ -115,12 +115,12 @@ macro_rules! decl_extension { $vis struct $ext_name; impl $crate::Extension for $ext_name { - fn as_mut_any(&mut self) -> &mut dyn std::any::Any { + fn as_mut_any(&mut self) -> &mut dyn core::any::Any { self } - fn type_id(&self) -> std::any::TypeId { - std::any::Any::type_id(self) + fn type_id(&self) -> core::any::TypeId { + core::any::Any::type_id(self) } } } diff --git a/substrate/primitives/keystore/Cargo.toml b/substrate/primitives/keystore/Cargo.toml index 139f04beb20..a34839358e1 100644 --- a/substrate/primitives/keystore/Cargo.toml +++ b/substrate/primitives/keystore/Cargo.toml @@ -17,8 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -parking_lot = { version = "0.12.1", default-features = false } -thiserror = "1.0" +parking_lot = { version = "0.12.1", default-features = false, optional = true } sp-core = { path = "../core", default-features = false } sp-externalities = { path = "../externalities", default-features = false } @@ -28,7 +27,7 @@ rand_chacha = "0.2.2" [features] default = ["std"] -std = ["codec/std", "sp-core/std", "sp-externalities/std"] +std = ["codec/std", "dep:parking_lot", "sp-core/std", "sp-externalities/std"] # This feature adds BLS crypto primitives. # It should not be used in production since the implementation and interface may still diff --git a/substrate/primitives/keystore/src/lib.rs b/substrate/primitives/keystore/src/lib.rs index 5fae27bc328..64f0e3ea49e 100644 --- a/substrate/primitives/keystore/src/lib.rs +++ b/substrate/primitives/keystore/src/lib.rs @@ -17,6 +17,10 @@ //! Keystore traits +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + #[cfg(feature = "std")] pub mod testing; @@ -29,25 +33,35 @@ use sp_core::{ ecdsa, ed25519, sr25519, }; -use std::sync::Arc; +use alloc::{string::String, sync::Arc, vec::Vec}; /// Keystore error -#[derive(Debug, thiserror::Error)] +#[derive(Debug)] pub enum Error { /// Public key type is not supported - #[error("Key not supported: {0:?}")] KeyNotSupported(KeyTypeId), /// Validation error - #[error("Validation error: {0}")] ValidationError(String), /// Keystore unavailable - #[error("Keystore unavailable")] Unavailable, /// Programming errors - #[error("An unknown keystore error occurred: {0}")] Other(String), } +impl core::fmt::Display for Error { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { + match self { + Error::KeyNotSupported(key_type) => write!(fmt, "Key not supported: {key_type:?}"), + Error::ValidationError(error) => write!(fmt, "Validation error: {error}"), + Error::Unavailable => fmt.write_str("Keystore unavailable"), + Error::Other(error) => write!(fmt, "An unknown keystore error occurred: {error}"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error {} + /// Something that generates, stores and provides access to secret keys. pub trait Keystore: Send + Sync { /// Returns all the sr25519 public keys for the given key type. -- GitLab From d7862aa8c9b4f8be1d4330bc11c742bf48d407f6 Mon Sep 17 00:00:00 2001 From: s0me0ne-unkn0wn <48632512+s0me0ne-unkn0wn@users.noreply.github.com> Date: Mon, 19 Feb 2024 00:51:40 +0100 Subject: [PATCH 082/133] Fix documentation dead link (#3372) --- substrate/client/cli/src/commands/run_cmd.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/client/cli/src/commands/run_cmd.rs b/substrate/client/cli/src/commands/run_cmd.rs index ecff7eead7b..c50198cf3cb 100644 --- a/substrate/client/cli/src/commands/run_cmd.rs +++ b/substrate/client/cli/src/commands/run_cmd.rs @@ -62,7 +62,7 @@ pub struct RunCmd { /// Not all RPC methods are safe to be exposed publicly. /// /// Use an RPC proxy server to filter out dangerous methods. More details: - /// . + /// . /// /// Use `--unsafe-rpc-external` to suppress the warning if you understand the risks. #[arg(long)] -- GitLab From 3168ed4d6a305f22aef096e4fbf5a3622286ee61 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 09:29:57 +0000 Subject: [PATCH 083/133] Bump the known_good_semver group with 1 update (#3379) Bumps the known_good_semver group with 1 update: [clap](https://github.com/clap-rs/clap). Updates `clap` from 4.5.0 to 4.5.1
Release notes

Sourced from clap's releases.

v4.5.1

[4.5.1] - 2024-02-16

Fixes

  • (error) Include suggestion to add -- even if there is a "did you mean" so long as last or trailing_var_arg is used
Changelog

Sourced from clap's changelog.

[4.5.1] - 2024-02-16

Fixes

  • (error) Include suggestion to add -- even if there is a "did you mean" so long as last or trailing_var_arg is used
Commits
  • 0c01b55 chore: Release
  • 08e0b5b docs: Update changelog
  • f2c4e6e Merge pull request #5359 from poliorcetics/ab/push-szymvyzpmnqx
  • e782775 fix(complete): Handle newlines in command/arg descriptions
  • fba7c85 test(complete): Show newline issue
  • 8a7a13a chore: Release
  • 7b3a3e1 docs: Update changelog
  • 7b624ca Merge pull request #5356 from epage/escape
  • 446328a fix(error): Include -- in more cases
  • 7de6df8 test(error): Show existing last behavior
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=clap&package-manager=cargo&previous-version=4.5.0&new-version=4.5.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 66 +++++++++---------- cumulus/client/cli/Cargo.toml | 2 +- cumulus/parachain-template/node/Cargo.toml | 2 +- cumulus/polkadot-parachain/Cargo.toml | 2 +- cumulus/test/service/Cargo.toml | 2 +- polkadot/cli/Cargo.toml | 2 +- polkadot/node/malus/Cargo.toml | 2 +- polkadot/node/subsystem-bench/Cargo.toml | 2 +- .../test-parachains/adder/collator/Cargo.toml | 2 +- .../undying/collator/Cargo.toml | 2 +- polkadot/utils/generate-bags/Cargo.toml | 2 +- .../remote-ext-tests/bags-list/Cargo.toml | 2 +- substrate/bin/minimal/node/Cargo.toml | 2 +- substrate/bin/node-template/node/Cargo.toml | 2 +- substrate/bin/node/bench/Cargo.toml | 2 +- substrate/bin/node/cli/Cargo.toml | 4 +- substrate/bin/node/inspect/Cargo.toml | 2 +- .../bin/utils/chain-spec-builder/Cargo.toml | 2 +- substrate/bin/utils/subkey/Cargo.toml | 2 +- substrate/client/cli/Cargo.toml | 2 +- substrate/client/storage-monitor/Cargo.toml | 2 +- .../solution-type/fuzzer/Cargo.toml | 2 +- .../npos-elections/fuzzer/Cargo.toml | 2 +- .../ci/node-template-release/Cargo.toml | 2 +- .../utils/frame/benchmarking-cli/Cargo.toml | 2 +- .../frame/frame-utilities-cli/Cargo.toml | 2 +- .../generate-bags/node-runtime/Cargo.toml | 2 +- .../utils/frame/try-runtime/cli/Cargo.toml | 2 +- 28 files changed, 61 insertions(+), 61 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 70600b1a35c..aea00abb8f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2609,9 +2609,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.0" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f" +checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" dependencies = [ "clap_builder", "clap_derive 4.5.0", @@ -2628,9 +2628,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.0" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99" +checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" dependencies = [ "anstream", "anstyle", @@ -2645,7 +2645,7 @@ version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "586a385f7ef2f8b4d86bddaa0c094794e7ccbfe5ffef1f434fe928143fc783a5" dependencies = [ - "clap 4.5.0", + "clap 4.5.1", ] [[package]] @@ -3380,7 +3380,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.5.0", + "clap 4.5.1", "criterion-plot", "futures", "is-terminal", @@ -3543,7 +3543,7 @@ dependencies = [ name = "cumulus-client-cli" version = "0.7.0" dependencies = [ - "clap 4.5.0", + "clap 4.5.1", "parity-scale-codec", "sc-chain-spec", "sc-cli", @@ -4284,7 +4284,7 @@ name = "cumulus-test-service" version = "0.1.0" dependencies = [ "async-trait", - "clap 4.5.0", + "clap 4.5.1", "criterion 0.5.1", "cumulus-client-cli", "cumulus-client-consensus-common", @@ -5486,7 +5486,7 @@ dependencies = [ "Inflector", "array-bytes 6.1.0", "chrono", - "clap 4.5.0", + "clap 4.5.1", "comfy-table", "frame-benchmarking", "frame-support", @@ -5578,7 +5578,7 @@ dependencies = [ name = "frame-election-solution-type-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.5.0", + "clap 4.5.1", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-support", @@ -8106,7 +8106,7 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" name = "minimal-node" version = "4.0.0-dev" dependencies = [ - "clap 4.5.0", + "clap 4.5.1", "frame", "futures", "futures-timer", @@ -8575,7 +8575,7 @@ name = "node-bench" version = "0.9.0-dev" dependencies = [ "array-bytes 6.1.0", - "clap 4.5.0", + "clap 4.5.1", "derive_more", "fs_extra", "futures", @@ -8652,7 +8652,7 @@ dependencies = [ name = "node-runtime-generate-bags" version = "3.0.0" dependencies = [ - "clap 4.5.0", + "clap 4.5.1", "generate-bags", "kitchensink-runtime", ] @@ -8661,7 +8661,7 @@ dependencies = [ name = "node-template" version = "4.0.0-dev" dependencies = [ - "clap 4.5.0", + "clap 4.5.1", "frame-benchmarking", "frame-benchmarking-cli", "frame-system", @@ -8705,7 +8705,7 @@ dependencies = [ name = "node-template-release" version = "3.0.0" dependencies = [ - "clap 4.5.0", + "clap 4.5.1", "flate2", "fs_extra", "glob", @@ -11275,7 +11275,7 @@ dependencies = [ name = "parachain-template-node" version = "0.1.0" dependencies = [ - "clap 4.5.0", + "clap 4.5.1", "color-print", "cumulus-client-cli", "cumulus-client-collator", @@ -12203,7 +12203,7 @@ name = "polkadot-cli" version = "7.0.0" dependencies = [ "cfg-if", - "clap 4.5.0", + "clap 4.5.1", "frame-benchmarking-cli", "futures", "log", @@ -13047,7 +13047,7 @@ dependencies = [ "async-trait", "bridge-hub-rococo-runtime", "bridge-hub-westend-runtime", - "clap 4.5.0", + "clap 4.5.1", "collectives-westend-runtime", "color-print", "contracts-rococo-runtime", @@ -13563,7 +13563,7 @@ dependencies = [ "async-trait", "bincode", "bitvec", - "clap 4.5.0", + "clap 4.5.1", "clap-num", "color-eyre", "colored", @@ -13657,7 +13657,7 @@ version = "1.0.0" dependencies = [ "assert_matches", "async-trait", - "clap 4.5.0", + "clap 4.5.1", "color-eyre", "futures", "futures-timer", @@ -13804,7 +13804,7 @@ dependencies = [ name = "polkadot-voter-bags" version = "7.0.0" dependencies = [ - "clap 4.5.0", + "clap 4.5.1", "generate-bags", "sp-io", "westend-runtime", @@ -14765,7 +14765,7 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" name = "remote-ext-tests-bags-list" version = "1.0.0" dependencies = [ - "clap 4.5.0", + "clap 4.5.1", "frame-system", "log", "pallet-bags-list-remote-tests", @@ -15603,7 +15603,7 @@ dependencies = [ "array-bytes 6.1.0", "bip39", "chrono", - "clap 4.5.0", + "clap 4.5.1", "fdlimit", "futures", "futures-timer", @@ -16761,7 +16761,7 @@ dependencies = [ name = "sc-storage-monitor" version = "0.16.0" dependencies = [ - "clap 4.5.0", + "clap 4.5.1", "fs4", "log", "sp-core", @@ -18788,7 +18788,7 @@ dependencies = [ name = "sp-npos-elections-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.5.0", + "clap 4.5.1", "honggfuzz", "rand", "sp-npos-elections", @@ -19311,7 +19311,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" name = "staging-chain-spec-builder" version = "2.0.0" dependencies = [ - "clap 4.5.0", + "clap 4.5.1", "log", "sc-chain-spec", "serde_json", @@ -19324,7 +19324,7 @@ version = "3.0.0-dev" dependencies = [ "array-bytes 6.1.0", "assert_cmd", - "clap 4.5.0", + "clap 4.5.1", "clap_complete", "criterion 0.4.0", "frame-benchmarking", @@ -19434,7 +19434,7 @@ dependencies = [ name = "staging-node-inspect" version = "0.12.0" dependencies = [ - "clap 4.5.0", + "clap 4.5.1", "parity-scale-codec", "sc-cli", "sc-client-api", @@ -19639,7 +19639,7 @@ dependencies = [ name = "subkey" version = "9.0.0" dependencies = [ - "clap 4.5.0", + "clap 4.5.1", "sc-cli", ] @@ -19681,7 +19681,7 @@ dependencies = [ name = "substrate-frame-cli" version = "32.0.0" dependencies = [ - "clap 4.5.0", + "clap 4.5.1", "frame-support", "frame-system", "sc-cli", @@ -20162,7 +20162,7 @@ dependencies = [ name = "test-parachain-adder-collator" version = "1.0.0" dependencies = [ - "clap 4.5.0", + "clap 4.5.1", "futures", "futures-timer", "log", @@ -20210,7 +20210,7 @@ dependencies = [ name = "test-parachain-undying-collator" version = "1.0.0" dependencies = [ - "clap 4.5.0", + "clap 4.5.1", "futures", "futures-timer", "log", @@ -20905,7 +20905,7 @@ version = "0.38.0" dependencies = [ "assert_cmd", "async-trait", - "clap 4.5.0", + "clap 4.5.1", "frame-remote-externalities", "frame-try-runtime", "hex", diff --git a/cumulus/client/cli/Cargo.toml b/cumulus/client/cli/Cargo.toml index b6d3e7a7442..eaf0d5d5d7f 100644 --- a/cumulus/client/cli/Cargo.toml +++ b/cumulus/client/cli/Cargo.toml @@ -10,7 +10,7 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0" workspace = true [dependencies] -clap = { version = "4.5.0", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0" } url = "2.4.0" diff --git a/cumulus/parachain-template/node/Cargo.toml b/cumulus/parachain-template/node/Cargo.toml index 7ee0b4e8161..77b484913b8 100644 --- a/cumulus/parachain-template/node/Cargo.toml +++ b/cumulus/parachain-template/node/Cargo.toml @@ -14,7 +14,7 @@ publish = false workspace = true [dependencies] -clap = { version = "4.5.0", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } log = { workspace = true, default-features = true } codec = { package = "parity-scale-codec", version = "3.0.0" } serde = { version = "1.0.196", features = ["derive"] } diff --git a/cumulus/polkadot-parachain/Cargo.toml b/cumulus/polkadot-parachain/Cargo.toml index bbd8f9d409e..d5624941c03 100644 --- a/cumulus/polkadot-parachain/Cargo.toml +++ b/cumulus/polkadot-parachain/Cargo.toml @@ -16,7 +16,7 @@ path = "src/main.rs" [dependencies] async-trait = "0.1.74" -clap = { version = "4.5.0", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.28" hex-literal = "0.4.1" diff --git a/cumulus/test/service/Cargo.toml b/cumulus/test/service/Cargo.toml index 04119c4c9ee..66bc9c66697 100644 --- a/cumulus/test/service/Cargo.toml +++ b/cumulus/test/service/Cargo.toml @@ -14,7 +14,7 @@ path = "src/main.rs" [dependencies] async-trait = "0.1.74" -clap = { version = "4.5.0", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0" } criterion = { version = "0.5.1", features = ["async_tokio"] } jsonrpsee = { version = "0.22", features = ["server"] } diff --git a/polkadot/cli/Cargo.toml b/polkadot/cli/Cargo.toml index 3f085e18765..ee2673ac20f 100644 --- a/polkadot/cli/Cargo.toml +++ b/polkadot/cli/Cargo.toml @@ -19,7 +19,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] cfg-if = "1.0" -clap = { version = "4.5.0", features = ["derive"], optional = true } +clap = { version = "4.5.1", features = ["derive"], optional = true } log = { workspace = true, default-features = true } thiserror = "1.0.48" futures = "0.3.21" diff --git a/polkadot/node/malus/Cargo.toml b/polkadot/node/malus/Cargo.toml index 69a1ffac111..ea25b9077f3 100644 --- a/polkadot/node/malus/Cargo.toml +++ b/polkadot/node/malus/Cargo.toml @@ -43,7 +43,7 @@ assert_matches = "1.5" async-trait = "0.1.74" sp-keystore = { path = "../../../substrate/primitives/keystore" } sp-core = { path = "../../../substrate/primitives/core" } -clap = { version = "4.5.0", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } futures = "0.3.21" futures-timer = "3.0.2" gum = { package = "tracing-gum", path = "../gum" } diff --git a/polkadot/node/subsystem-bench/Cargo.toml b/polkadot/node/subsystem-bench/Cargo.toml index fca88616ac4..203cfe05db1 100644 --- a/polkadot/node/subsystem-bench/Cargo.toml +++ b/polkadot/node/subsystem-bench/Cargo.toml @@ -35,7 +35,7 @@ async-trait = "0.1.57" sp-keystore = { path = "../../../substrate/primitives/keystore" } sc-keystore = { path = "../../../substrate/client/keystore" } sp-core = { path = "../../../substrate/primitives/core" } -clap = { version = "4.5.0", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } futures = "0.3.21" futures-timer = "3.0.2" bincode = "1.3.3" diff --git a/polkadot/parachain/test-parachains/adder/collator/Cargo.toml b/polkadot/parachain/test-parachains/adder/collator/Cargo.toml index 268b96aa153..8ce4ceb47c2 100644 --- a/polkadot/parachain/test-parachains/adder/collator/Cargo.toml +++ b/polkadot/parachain/test-parachains/adder/collator/Cargo.toml @@ -16,7 +16,7 @@ path = "src/main.rs" [dependencies] parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } -clap = { version = "4.5.0", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } futures = "0.3.21" futures-timer = "3.0.2" log = { workspace = true, default-features = true } diff --git a/polkadot/parachain/test-parachains/undying/collator/Cargo.toml b/polkadot/parachain/test-parachains/undying/collator/Cargo.toml index 805262fe025..25fdbfa74ea 100644 --- a/polkadot/parachain/test-parachains/undying/collator/Cargo.toml +++ b/polkadot/parachain/test-parachains/undying/collator/Cargo.toml @@ -16,7 +16,7 @@ path = "src/main.rs" [dependencies] parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } -clap = { version = "4.5.0", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } futures = "0.3.21" futures-timer = "3.0.2" log = { workspace = true, default-features = true } diff --git a/polkadot/utils/generate-bags/Cargo.toml b/polkadot/utils/generate-bags/Cargo.toml index 38f1ac561c5..c0e9bd332df 100644 --- a/polkadot/utils/generate-bags/Cargo.toml +++ b/polkadot/utils/generate-bags/Cargo.toml @@ -10,7 +10,7 @@ description = "CLI to generate voter bags for Polkadot runtimes" workspace = true [dependencies] -clap = { version = "4.5.0", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } generate-bags = { path = "../../../substrate/utils/frame/generate-bags" } sp-io = { path = "../../../substrate/primitives/io" } diff --git a/polkadot/utils/remote-ext-tests/bags-list/Cargo.toml b/polkadot/utils/remote-ext-tests/bags-list/Cargo.toml index 79adfe36852..ddc5af97a16 100644 --- a/polkadot/utils/remote-ext-tests/bags-list/Cargo.toml +++ b/polkadot/utils/remote-ext-tests/bags-list/Cargo.toml @@ -18,6 +18,6 @@ sp-tracing = { path = "../../../../substrate/primitives/tracing" } frame-system = { path = "../../../../substrate/frame/system" } sp-core = { path = "../../../../substrate/primitives/core" } -clap = { version = "4.5.0", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } log = { workspace = true, default-features = true } tokio = { version = "1.24.2", features = ["macros"] } diff --git a/substrate/bin/minimal/node/Cargo.toml b/substrate/bin/minimal/node/Cargo.toml index fd10a3f37af..02b3b0a735b 100644 --- a/substrate/bin/minimal/node/Cargo.toml +++ b/substrate/bin/minimal/node/Cargo.toml @@ -20,7 +20,7 @@ targets = ["x86_64-unknown-linux-gnu"] name = "minimal-node" [dependencies] -clap = { version = "4.5.0", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } futures = { version = "0.3.21", features = ["thread-pool"] } futures-timer = "3.0.1" jsonrpsee = { version = "0.22", features = ["server"] } diff --git a/substrate/bin/node-template/node/Cargo.toml b/substrate/bin/node-template/node/Cargo.toml index dbdb270b09c..7a6955d2cec 100644 --- a/substrate/bin/node-template/node/Cargo.toml +++ b/substrate/bin/node-template/node/Cargo.toml @@ -20,7 +20,7 @@ targets = ["x86_64-unknown-linux-gnu"] name = "node-template" [dependencies] -clap = { version = "4.5.0", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } futures = { version = "0.3.21", features = ["thread-pool"] } serde_json = "1.0.113" diff --git a/substrate/bin/node/bench/Cargo.toml b/substrate/bin/node/bench/Cargo.toml index 76b9c7a0ca6..ada58c79c77 100644 --- a/substrate/bin/node/bench/Cargo.toml +++ b/substrate/bin/node/bench/Cargo.toml @@ -16,7 +16,7 @@ workspace = true [dependencies] array-bytes = "6.1" -clap = { version = "4.5.0", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } log = { workspace = true, default-features = true } node-primitives = { path = "../primitives" } node-testing = { path = "../testing" } diff --git a/substrate/bin/node/cli/Cargo.toml b/substrate/bin/node/cli/Cargo.toml index 42e977e74ec..aed2101a3f5 100644 --- a/substrate/bin/node/cli/Cargo.toml +++ b/substrate/bin/node/cli/Cargo.toml @@ -41,7 +41,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] # third-party dependencies array-bytes = "6.1" -clap = { version = "4.5.0", features = ["derive"], optional = true } +clap = { version = "4.5.1", features = ["derive"], optional = true } codec = { package = "parity-scale-codec", version = "3.6.1" } serde = { version = "1.0.196", features = ["derive"] } jsonrpsee = { version = "0.22", features = ["server"] } @@ -165,7 +165,7 @@ sp-trie = { path = "../../../primitives/trie" } sp-state-machine = { path = "../../../primitives/state-machine" } [build-dependencies] -clap = { version = "4.5.0", optional = true } +clap = { version = "4.5.1", optional = true } clap_complete = { version = "4.0.2", optional = true } node-inspect = { package = "staging-node-inspect", path = "../inspect", optional = true } frame-benchmarking-cli = { path = "../../../utils/frame/benchmarking-cli", optional = true } diff --git a/substrate/bin/node/inspect/Cargo.toml b/substrate/bin/node/inspect/Cargo.toml index 27bf190ce15..6c9be0fe1ac 100644 --- a/substrate/bin/node/inspect/Cargo.toml +++ b/substrate/bin/node/inspect/Cargo.toml @@ -15,7 +15,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "4.5.0", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.6.1" } thiserror = "1.0" sc-cli = { path = "../../../client/cli" } diff --git a/substrate/bin/utils/chain-spec-builder/Cargo.toml b/substrate/bin/utils/chain-spec-builder/Cargo.toml index 2f4edeff49a..8b3180fbbcd 100644 --- a/substrate/bin/utils/chain-spec-builder/Cargo.toml +++ b/substrate/bin/utils/chain-spec-builder/Cargo.toml @@ -23,7 +23,7 @@ name = "chain-spec-builder" crate-type = ["rlib"] [dependencies] -clap = { version = "4.5.0", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } log = { workspace = true, default-features = true } sc-chain-spec = { path = "../../../client/chain-spec" } serde_json = "1.0.113" diff --git a/substrate/bin/utils/subkey/Cargo.toml b/substrate/bin/utils/subkey/Cargo.toml index a79015238d6..93b1368ca75 100644 --- a/substrate/bin/utils/subkey/Cargo.toml +++ b/substrate/bin/utils/subkey/Cargo.toml @@ -20,5 +20,5 @@ path = "src/main.rs" name = "subkey" [dependencies] -clap = { version = "4.5.0", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } sc-cli = { path = "../../../client/cli" } diff --git a/substrate/client/cli/Cargo.toml b/substrate/client/cli/Cargo.toml index f5e2be23193..1b4d3733baa 100644 --- a/substrate/client/cli/Cargo.toml +++ b/substrate/client/cli/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] array-bytes = "6.1" chrono = "0.4.31" -clap = { version = "4.5.0", features = ["derive", "string", "wrap_help"] } +clap = { version = "4.5.1", features = ["derive", "string", "wrap_help"] } fdlimit = "0.3.0" futures = "0.3.21" itertools = "0.10.3" diff --git a/substrate/client/storage-monitor/Cargo.toml b/substrate/client/storage-monitor/Cargo.toml index 3916f560909..b0f04644142 100644 --- a/substrate/client/storage-monitor/Cargo.toml +++ b/substrate/client/storage-monitor/Cargo.toml @@ -12,7 +12,7 @@ homepage = "https://substrate.io" workspace = true [dependencies] -clap = { version = "4.5.0", features = ["derive", "string"] } +clap = { version = "4.5.1", features = ["derive", "string"] } log = { workspace = true, default-features = true } fs4 = "0.7.0" sp-core = { path = "../../primitives/core" } diff --git a/substrate/frame/election-provider-support/solution-type/fuzzer/Cargo.toml b/substrate/frame/election-provider-support/solution-type/fuzzer/Cargo.toml index 5ed37033e81..8a73dd38fa2 100644 --- a/substrate/frame/election-provider-support/solution-type/fuzzer/Cargo.toml +++ b/substrate/frame/election-provider-support/solution-type/fuzzer/Cargo.toml @@ -16,7 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "4.5.0", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } honggfuzz = "0.5" rand = { version = "0.8", features = ["small_rng", "std"] } diff --git a/substrate/primitives/npos-elections/fuzzer/Cargo.toml b/substrate/primitives/npos-elections/fuzzer/Cargo.toml index fc231a535f2..bcd908b9705 100644 --- a/substrate/primitives/npos-elections/fuzzer/Cargo.toml +++ b/substrate/primitives/npos-elections/fuzzer/Cargo.toml @@ -17,7 +17,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "4.5.0", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } honggfuzz = "0.5" rand = { version = "0.8", features = ["small_rng", "std"] } sp-npos-elections = { path = ".." } diff --git a/substrate/scripts/ci/node-template-release/Cargo.toml b/substrate/scripts/ci/node-template-release/Cargo.toml index c96da2ed426..cc60d0a4510 100644 --- a/substrate/scripts/ci/node-template-release/Cargo.toml +++ b/substrate/scripts/ci/node-template-release/Cargo.toml @@ -14,7 +14,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "4.5.0", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } flate2 = "1.0" fs_extra = "1.3" glob = "0.3" diff --git a/substrate/utils/frame/benchmarking-cli/Cargo.toml b/substrate/utils/frame/benchmarking-cli/Cargo.toml index 68ecb0612ce..4f6cbc1d5d6 100644 --- a/substrate/utils/frame/benchmarking-cli/Cargo.toml +++ b/substrate/utils/frame/benchmarking-cli/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] array-bytes = "6.1" chrono = "0.4" -clap = { version = "4.5.0", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.6.1" } comfy-table = { version = "7.1.0", default-features = false } handlebars = "4.2.2" diff --git a/substrate/utils/frame/frame-utilities-cli/Cargo.toml b/substrate/utils/frame/frame-utilities-cli/Cargo.toml index 3f5e8e48e0f..919016b2d8f 100644 --- a/substrate/utils/frame/frame-utilities-cli/Cargo.toml +++ b/substrate/utils/frame/frame-utilities-cli/Cargo.toml @@ -14,7 +14,7 @@ readme = "README.md" workspace = true [dependencies] -clap = { version = "4.5.0", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } frame-support = { path = "../../../frame/support" } frame-system = { path = "../../../frame/system" } sc-cli = { path = "../../../client/cli" } diff --git a/substrate/utils/frame/generate-bags/node-runtime/Cargo.toml b/substrate/utils/frame/generate-bags/node-runtime/Cargo.toml index 8604bab1ce5..68d4733614c 100644 --- a/substrate/utils/frame/generate-bags/node-runtime/Cargo.toml +++ b/substrate/utils/frame/generate-bags/node-runtime/Cargo.toml @@ -17,4 +17,4 @@ kitchensink-runtime = { path = "../../../../bin/node/runtime" } generate-bags = { path = ".." } # third-party -clap = { version = "4.5.0", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } diff --git a/substrate/utils/frame/try-runtime/cli/Cargo.toml b/substrate/utils/frame/try-runtime/cli/Cargo.toml index 998d0790490..63165f07e5c 100644 --- a/substrate/utils/frame/try-runtime/cli/Cargo.toml +++ b/substrate/utils/frame/try-runtime/cli/Cargo.toml @@ -38,7 +38,7 @@ frame-try-runtime = { path = "../../../../frame/try-runtime", optional = true } substrate-rpc-client = { path = "../../rpc/client" } async-trait = "0.1.74" -clap = { version = "4.5.0", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } hex = { version = "0.4.3", default-features = false } log = { workspace = true, default-features = true } parity-scale-codec = "3.6.1" -- GitLab From a5492dc6ba273f1bca8b5001bc10edd402ead382 Mon Sep 17 00:00:00 2001 From: s0me0ne-unkn0wn <48632512+s0me0ne-unkn0wn@users.noreply.github.com> Date: Mon, 19 Feb 2024 10:39:34 +0100 Subject: [PATCH 084/133] `im-online` removal cleanup: remove off-chain storage (#2290) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a follow-up for `im-online` pallet removal that is cleaning up its off-chain storage. Must be merged no earlier than #2265 is enacted. Related: #1964 --------- Co-authored-by: Bastian Köcher --- polkadot/runtime/rococo/src/lib.rs | 6 ++++++ polkadot/runtime/westend/src/lib.rs | 6 ++++++ prdoc/pr_2290.prdoc | 10 ++++++++++ substrate/frame/im-online/src/migration.rs | 15 +++++++++++++++ 4 files changed, 37 insertions(+) create mode 100644 prdoc/pr_2290.prdoc diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 45f27312697..03e96ab388b 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -1824,6 +1824,12 @@ sp_api::impl_runtime_apis! { impl offchain_primitives::OffchainWorkerApi for Runtime { fn offchain_worker(header: &::Header) { + use sp_runtime::{traits::Header, DigestItem}; + + if header.digest().logs().iter().any(|di| di == &DigestItem::RuntimeEnvironmentUpdated) { + pallet_im_online::migration::clear_offchain_storage(Session::validators().len() as u32); + } + Executive::offchain_worker(header) } } diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 166c2d0d96e..03bd5b18b5b 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -1794,6 +1794,12 @@ sp_api::impl_runtime_apis! { impl offchain_primitives::OffchainWorkerApi for Runtime { fn offchain_worker(header: &::Header) { + use sp_runtime::{traits::Header, DigestItem}; + + if header.digest().logs().iter().any(|di| di == &DigestItem::RuntimeEnvironmentUpdated) { + pallet_im_online::migration::clear_offchain_storage(Session::validators().len() as u32); + } + Executive::offchain_worker(header) } } diff --git a/prdoc/pr_2290.prdoc b/prdoc/pr_2290.prdoc new file mode 100644 index 00000000000..9f0476e9152 --- /dev/null +++ b/prdoc/pr_2290.prdoc @@ -0,0 +1,10 @@ +title: im-online pallet offcain storage cleanup + +doc: + - audience: Runtime Dev + description: | + Adds a function `clear_offchain_storage` to `pallet-im-online`. This function can be used + after the pallet was removed to clear its offchain storage. + +crates: + - name: pallet-im-online diff --git a/substrate/frame/im-online/src/migration.rs b/substrate/frame/im-online/src/migration.rs index 3860a7ca532..0d2c0a055b6 100644 --- a/substrate/frame/im-online/src/migration.rs +++ b/substrate/frame/im-online/src/migration.rs @@ -116,6 +116,21 @@ pub mod v1 { } } +/// Clears the pallet's offchain storage. +/// +/// Must be put in `OffchainWorkerApi::offchain_worker` after +/// the pallet was removed. +pub fn clear_offchain_storage(validator_set_size: u32) { + (0..validator_set_size).for_each(|idx| { + let key = { + let mut key = DB_PREFIX.to_vec(); + key.extend(idx.encode()); + key + }; + sp_runtime::offchain::storage::StorageValueRef::persistent(&key).clear(); + }); +} + #[cfg(all(feature = "try-runtime", test))] mod test { use super::*; -- GitLab From 2a32b5c9379e6daefd2061a8fb5b4d972d3460a1 Mon Sep 17 00:00:00 2001 From: Bulat Saifullin Date: Mon, 19 Feb 2024 14:11:17 +0400 Subject: [PATCH 085/133] Update coretime-westend bootnodes (#3380) Update coretime-westend bootnodes --- cumulus/parachains/chain-specs/coretime-westend.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cumulus/parachains/chain-specs/coretime-westend.json b/cumulus/parachains/chain-specs/coretime-westend.json index 92e119b935f..c79fd582348 100644 --- a/cumulus/parachains/chain-specs/coretime-westend.json +++ b/cumulus/parachains/chain-specs/coretime-westend.json @@ -3,8 +3,8 @@ "id": "coretime-westend", "chainType": "Live", "bootNodes": [ - "/dns/westend-coretime-collator-0.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWP93Dzk8T7GWxyWw9jhLcz8Pksokk3R9vL2eEH337bNkT", - "/dns/westend-coretime-collator-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWMh2imeAzsZKGQgm2cv6Uoep3GBYtwGfujt1bs5YfVzkH" + "/dns/westend-coretime-collator-node-0.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWP93Dzk8T7GWxyWw9jhLcz8Pksokk3R9vL2eEH337bNkT", + "/dns/westend-coretime-collator-node-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWMh2imeAzsZKGQgm2cv6Uoep3GBYtwGfujt1bs5YfVzkH" ], "telemetryEndpoints": null, "protocolId": null, @@ -68,4 +68,4 @@ "childrenDefault": {} } } -} \ No newline at end of file +} -- GitLab From b78c72cf9c0e5d6772cd4a4a25239602ff2c5ad1 Mon Sep 17 00:00:00 2001 From: Gilt0 <82427864+Gilt0@users.noreply.github.com> Date: Mon, 19 Feb 2024 21:07:10 +0900 Subject: [PATCH 086/133] [FRAME Core] remove unnecessary overrides while using derive_impl for frame_system (#3317) # Description This PR removes redundant type definition from test definition config implementations like ``` #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { type A = A; ... } ``` This changes avoid redundancies in the code as the macro `derive_impl` defines the relevant types. To implement the changes, it was a simple fact of running tests and making sure that the tests would still run while the definition would be removed. Closes #3237 As a note, here is a brief account of things done from the Issue's description statement ``` alliance migrate alliance, fast-unstake and bags list to use derive-impl #1636 asset-conversion DONE asset-rate DONE assets DONE atomic-swap DONE aura DONE authority-discovery DONE authorship migrate babe and authorship to use derive-impl #1790 babe migrate babe and authorship to use derive-impl #1790 bags-list migrate alliance, fast-unstake and bags list to use derive-impl #1636 balances DONE beefy NOTHING TO DO --- also noted this error without failing tests Feb 13 13:49:08.941 ERROR runtime::timestamp: `pallet_timestamp::UnixTime::now` is called at genesis, invalid value returned: 0 beefy-mmr NOTHING TO DO bounties DONE child-bounties DONE collective DONE contracts DONE conviction-voting DONE core-fellowship NOTHING TO DO democracy DONE election-provider-multi-phase NOTHING TO DO elections-phragmen DONE executive NOTHING TO DO fast-unstake migrate alliance, fast-unstake and bags list to use derive-impl #1636 glutton DONE grandpa DONE identity DONE im-online NOTHING TO DO indices Refactor indices pallet #1789 insecure-randomness-collective-flip DONE lottery DONE membership DONE merkle-mountain-range NOTHING TO DO message-queue DONE multisig add frame_system::DefaultConfig to individual pallet DefaultConfigs substrate#14453 nft-fractionalization DONE nfts DONE nicks Refactor pallet-state-trie-migration to fungible::* traits #1801 NOT IN REPO nis DONE node-authorization DONE nomination-pools NOTHING TO DO -- ONLY impl for Runtime offences DELETED EVERYTHING -- IS THAT CORRECT?? preimage DONE proxy add frame_system::DefaultConfig to individual pallet DefaultConfigs substrate#14453 ranked-collective NOTHING TO DO recovery DONE referenda DONE remark DONE root-offences DONE root-testing NOTHING TO DO salary NOTHING TO DO scheduler DONE scored-pool DONE session DONE -- substrate/frame/session/benchmarking/src/mock.rs untouched society NOTHING TO DO staking DONE staking-bags-benchmarks NOT IN REPO state-trie-migration NOTHING TO DO statement DONE sudo DONE system DONE timestamp DONE tips DONE transaction-payment NOTHING TO DO transaction-storage NOTHING TO DO treasury DONE try-runtime NOTHING TO DO -- no specific mention of 'for Test' uniques DONE utility DONE vesting DONE whitelist DONE ``` --------- Co-authored-by: command-bot <> Co-authored-by: gupnik --- substrate/frame/asset-conversion/src/mock.rs | 24 ++----------- substrate/frame/asset-rate/src/mock.rs | 32 ++--------------- substrate/frame/assets/src/mock.rs | 26 +------------- substrate/frame/atomic-swap/src/tests.rs | 27 +------------- substrate/frame/aura/src/mock.rs | 25 +------------ .../frame/authority-discovery/src/lib.rs | 27 ++------------ substrate/frame/balances/src/tests/mod.rs | 29 +++------------ substrate/frame/bounties/src/tests.rs | 22 +----------- substrate/frame/child-bounties/src/tests.rs | 22 +----------- substrate/frame/collective/src/tests.rs | 28 +-------------- substrate/frame/contracts/src/tests.rs | 19 ---------- .../frame/conviction-voting/src/tests.rs | 27 +------------- substrate/frame/democracy/src/tests.rs | 23 +----------- substrate/frame/elections-phragmen/src/lib.rs | 28 +-------------- substrate/frame/glutton/src/mock.rs | 33 ++--------------- substrate/frame/grandpa/src/mock.rs | 23 +----------- substrate/frame/identity/src/tests.rs | 19 ---------- .../src/lib.rs | 29 ++------------- substrate/frame/lottery/src/mock.rs | 27 +------------- substrate/frame/membership/src/lib.rs | 31 ++-------------- .../message-queue/src/integration_test.rs | 29 +-------------- substrate/frame/message-queue/src/mock.rs | 33 ++--------------- .../frame/nft-fractionalization/src/mock.rs | 22 +----------- substrate/frame/nfts/src/mock.rs | 22 +----------- substrate/frame/nis/src/mock.rs | 33 ++--------------- .../frame/node-authorization/src/mock.rs | 33 ++--------------- substrate/frame/preimage/src/mock.rs | 26 ++------------ substrate/frame/recovery/src/mock.rs | 29 ++------------- substrate/frame/referenda/src/mock.rs | 23 +----------- substrate/frame/remark/src/mock.rs | 33 ++--------------- substrate/frame/root-offences/src/mock.rs | 29 +-------------- substrate/frame/scheduler/src/mock.rs | 32 ++--------------- substrate/frame/scored-pool/src/mock.rs | 28 +-------------- substrate/frame/session/src/mock.rs | 36 ++----------------- substrate/frame/staking/src/mock.rs | 29 +-------------- substrate/frame/statement/src/mock.rs | 29 ++------------- substrate/frame/sudo/src/mock.rs | 33 ++--------------- substrate/frame/system/src/mock.rs | 28 ++------------- substrate/frame/timestamp/src/mock.rs | 33 ++--------------- substrate/frame/tips/src/tests.rs | 19 ---------- substrate/frame/treasury/src/tests.rs | 22 +----------- substrate/frame/uniques/src/mock.rs | 27 +------------- substrate/frame/utility/src/tests.rs | 24 ++----------- substrate/frame/vesting/src/mock.rs | 29 ++------------- substrate/frame/whitelist/src/mock.rs | 32 ++--------------- 45 files changed, 69 insertions(+), 1165 deletions(-) diff --git a/substrate/frame/asset-conversion/src/mock.rs b/substrate/frame/asset-conversion/src/mock.rs index 6c19f834cb8..870538a68cc 100644 --- a/substrate/frame/asset-conversion/src/mock.rs +++ b/substrate/frame/asset-conversion/src/mock.rs @@ -29,15 +29,14 @@ use frame_support::{ fungible::{NativeFromLeft, NativeOrWithId, UnionOf}, imbalance::ResolveAssetTo, }, - AsEnsureOriginWithArg, ConstU128, ConstU32, ConstU64, + AsEnsureOriginWithArg, ConstU128, ConstU32, }, PalletId, }; use frame_system::{EnsureSigned, EnsureSignedBy}; use sp_arithmetic::Permill; -use sp_core::H256; use sp_runtime::{ - traits::{AccountIdConversion, BlakeTwo256, IdentityLookup}, + traits::{AccountIdConversion, IdentityLookup}, BuildStorage, }; @@ -56,29 +55,10 @@ construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; type AccountId = u128; type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type DbWeight = (); - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/asset-rate/src/mock.rs b/substrate/frame/asset-rate/src/mock.rs index d6044e09ccd..5981b056764 100644 --- a/substrate/frame/asset-rate/src/mock.rs +++ b/substrate/frame/asset-rate/src/mock.rs @@ -18,15 +18,8 @@ //! The crate's mock. use crate as pallet_asset_rate; -use frame_support::{ - derive_impl, - traits::{ConstU16, ConstU64}, -}; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use frame_support::{derive_impl, traits::ConstU64}; +use sp_runtime::BuildStorage; type Block = frame_system::mocking::MockBlock; @@ -41,29 +34,8 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = ConstU16<42>; - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/assets/src/mock.rs b/substrate/frame/assets/src/mock.rs index 6dda08eaff8..e1722200c35 100644 --- a/substrate/frame/assets/src/mock.rs +++ b/substrate/frame/assets/src/mock.rs @@ -25,12 +25,8 @@ use frame_support::{ construct_runtime, derive_impl, parameter_types, traits::{AsEnsureOriginWithArg, ConstU32, ConstU64}, }; -use sp_core::H256; use sp_io::storage; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use sp_runtime::BuildStorage; type Block = frame_system::mocking::MockBlock; @@ -48,28 +44,8 @@ type AssetId = u32; #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type DbWeight = (); - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); type MaxConsumers = ConstU32<3>; } diff --git a/substrate/frame/atomic-swap/src/tests.rs b/substrate/frame/atomic-swap/src/tests.rs index 4a4b96f7aae..4b444d888ed 100644 --- a/substrate/frame/atomic-swap/src/tests.rs +++ b/substrate/frame/atomic-swap/src/tests.rs @@ -24,11 +24,7 @@ use frame_support::{ derive_impl, traits::{ConstU32, ConstU64}, }; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use sp_runtime::BuildStorage; type Block = frame_system::mocking::MockBlock; @@ -43,29 +39,8 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/aura/src/mock.rs b/substrate/frame/aura/src/mock.rs index d87fea8fc81..8bc3e407158 100644 --- a/substrate/frame/aura/src/mock.rs +++ b/substrate/frame/aura/src/mock.rs @@ -25,8 +25,7 @@ use frame_support::{ traits::{ConstU32, ConstU64, DisabledValidators}, }; use sp_consensus_aura::{ed25519::AuthorityId, AuthorityIndex}; -use sp_core::H256; -use sp_runtime::{testing::UintAuthorityId, traits::IdentityLookup, BuildStorage}; +use sp_runtime::{testing::UintAuthorityId, BuildStorage}; type Block = frame_system::mocking::MockBlock; @@ -43,29 +42,7 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } impl pallet_timestamp::Config for Test { diff --git a/substrate/frame/authority-discovery/src/lib.rs b/substrate/frame/authority-discovery/src/lib.rs index 640c0c8759e..2b4dfaf1aea 100644 --- a/substrate/frame/authority-discovery/src/lib.rs +++ b/substrate/frame/authority-discovery/src/lib.rs @@ -168,13 +168,10 @@ impl OneSessionHandler for Pallet { mod tests { use super::*; use crate as pallet_authority_discovery; - use frame_support::{ - derive_impl, parameter_types, - traits::{ConstU32, ConstU64}, - }; + use frame_support::{derive_impl, parameter_types, traits::ConstU32}; use sp_application_crypto::Pair; use sp_authority_discovery::AuthorityPair; - use sp_core::{crypto::key_types, H256}; + use sp_core::crypto::key_types; use sp_io::TestExternalities; use sp_runtime::{ testing::UintAuthorityId, @@ -227,29 +224,9 @@ mod tests { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = AuthorityId; type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } pub struct TestSessionHandler; diff --git a/substrate/frame/balances/src/tests/mod.rs b/substrate/frame/balances/src/tests/mod.rs index 91452b292b5..599909fa943 100644 --- a/substrate/frame/balances/src/tests/mod.rs +++ b/substrate/frame/balances/src/tests/mod.rs @@ -26,18 +26,18 @@ use frame_support::{ dispatch::{DispatchInfo, GetDispatchInfo}, parameter_types, traits::{ - fungible, ConstU32, ConstU64, ConstU8, Imbalance as ImbalanceT, OnUnbalanced, - StorageMapShim, StoredMap, VariantCount, WhitelistedStorageKeys, + fungible, ConstU32, ConstU8, Imbalance as ImbalanceT, OnUnbalanced, StorageMapShim, + StoredMap, VariantCount, WhitelistedStorageKeys, }, weights::{IdentityFee, Weight}, }; use frame_system::{self as system, RawOrigin}; use pallet_transaction_payment::{ChargeTransactionPayment, CurrencyAdapter, Multiplier}; use scale_info::TypeInfo; -use sp_core::{hexdisplay::HexDisplay, H256}; +use sp_core::hexdisplay::HexDisplay; use sp_io; use sp_runtime::{ - traits::{BadOrigin, IdentityLookup, SignedExtension, Zero}, + traits::{BadOrigin, SignedExtension, Zero}, ArithmeticError, BuildStorage, DispatchError, DispatchResult, FixedPointNumber, RuntimeDebug, TokenError, }; @@ -92,29 +92,8 @@ parameter_types! { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = BlockWeights; - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = super::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } impl pallet_transaction_payment::Config for Test { diff --git a/substrate/frame/bounties/src/tests.rs b/substrate/frame/bounties/src/tests.rs index b0e3c085e65..da6596617da 100644 --- a/substrate/frame/bounties/src/tests.rs +++ b/substrate/frame/bounties/src/tests.rs @@ -31,9 +31,8 @@ use frame_support::{ PalletId, }; -use sp_core::H256; use sp_runtime::{ - traits::{BadOrigin, BlakeTwo256, IdentityLookup}, + traits::{BadOrigin, IdentityLookup}, BuildStorage, Perbill, Storage, }; @@ -61,29 +60,10 @@ type Balance = u64; #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; type AccountId = u128; // u64 is not enough to hold bytes used to generate bounty account type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/child-bounties/src/tests.rs b/substrate/frame/child-bounties/src/tests.rs index d663b8d9961..276a90a3e29 100644 --- a/substrate/frame/child-bounties/src/tests.rs +++ b/substrate/frame/child-bounties/src/tests.rs @@ -32,9 +32,8 @@ use frame_support::{ PalletId, }; -use sp_core::H256; use sp_runtime::{ - traits::{BadOrigin, BlakeTwo256, IdentityLookup}, + traits::{BadOrigin, IdentityLookup}, BuildStorage, Perbill, Permill, TokenError, }; @@ -64,29 +63,10 @@ type Balance = u64; #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; type AccountId = u128; type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/collective/src/tests.rs b/substrate/frame/collective/src/tests.rs index dbc3fbe69b3..aae17b7ffc2 100644 --- a/substrate/frame/collective/src/tests.rs +++ b/substrate/frame/collective/src/tests.rs @@ -26,11 +26,7 @@ use frame_support::{ }; use frame_system::{EnsureRoot, EventRecord, Phase}; use sp_core::H256; -use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use sp_runtime::{testing::Header, traits::BlakeTwo256, BuildStorage}; pub type Block = sp_runtime::generic::Block; pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; @@ -93,29 +89,7 @@ parameter_types! { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = BlockWeights; - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl Config for Test { type RuntimeOrigin = RuntimeOrigin; diff --git a/substrate/frame/contracts/src/tests.rs b/substrate/frame/contracts/src/tests.rs index eba959e8d46..a06b65b86b5 100644 --- a/substrate/frame/contracts/src/tests.rs +++ b/substrate/frame/contracts/src/tests.rs @@ -332,29 +332,10 @@ parameter_types! { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = BlockWeights; - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; type AccountId = AccountId32; type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } impl pallet_insecure_randomness_collective_flip::Config for Test {} impl pallet_balances::Config for Test { diff --git a/substrate/frame/conviction-voting/src/tests.rs b/substrate/frame/conviction-voting/src/tests.rs index b67290e7fec..dbcd643b60f 100644 --- a/substrate/frame/conviction-voting/src/tests.rs +++ b/substrate/frame/conviction-voting/src/tests.rs @@ -23,11 +23,7 @@ use frame_support::{ assert_noop, assert_ok, derive_impl, parameter_types, traits::{ConstU32, ConstU64, Contains, Polling, VoteTally}, }; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use sp_runtime::BuildStorage; use super::*; use crate as pallet_conviction_voting; @@ -53,29 +49,8 @@ impl Contains for BaseFilter { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = BaseFilter; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/democracy/src/tests.rs b/substrate/frame/democracy/src/tests.rs index 8136fa5c4c9..973e0c28eb2 100644 --- a/substrate/frame/democracy/src/tests.rs +++ b/substrate/frame/democracy/src/tests.rs @@ -29,9 +29,8 @@ use frame_support::{ }; use frame_system::{EnsureRoot, EnsureSigned, EnsureSignedBy}; use pallet_balances::{BalanceLock, Error as BalancesError}; -use sp_core::H256; use sp_runtime::{ - traits::{BadOrigin, BlakeTwo256, Hash, IdentityLookup}, + traits::{BadOrigin, BlakeTwo256, Hash}, BuildStorage, Perbill, }; mod cancellation; @@ -81,28 +80,8 @@ parameter_types! { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { type BaseCallFilter = BaseFilter; - type BlockWeights = BlockWeights; - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } parameter_types! { pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block; diff --git a/substrate/frame/elections-phragmen/src/lib.rs b/substrate/frame/elections-phragmen/src/lib.rs index 5d20a3cfcee..a078361a5f7 100644 --- a/substrate/frame/elections-phragmen/src/lib.rs +++ b/substrate/frame/elections-phragmen/src/lib.rs @@ -1313,39 +1313,13 @@ mod tests { traits::{ConstU32, ConstU64, OnInitialize}, }; use frame_system::ensure_signed; - use sp_core::H256; - use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, - }; + use sp_runtime::{testing::Header, BuildStorage}; use substrate_test_utils::assert_eq_uvec; #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/glutton/src/mock.rs b/substrate/frame/glutton/src/mock.rs index 26863811e29..0049800d952 100644 --- a/substrate/frame/glutton/src/mock.rs +++ b/substrate/frame/glutton/src/mock.rs @@ -18,15 +18,8 @@ use super::*; use crate as pallet_glutton; -use frame_support::{ - assert_ok, derive_impl, - traits::{ConstU32, ConstU64}, -}; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use frame_support::{assert_ok, derive_impl}; +use sp_runtime::BuildStorage; type Block = frame_system::mocking::MockBlock; @@ -40,29 +33,7 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl Config for Test { diff --git a/substrate/frame/grandpa/src/mock.rs b/substrate/frame/grandpa/src/mock.rs index 990a820d3a9..5d48f974c31 100644 --- a/substrate/frame/grandpa/src/mock.rs +++ b/substrate/frame/grandpa/src/mock.rs @@ -38,7 +38,7 @@ use sp_runtime::{ curve::PiecewiseLinear, impl_opaque_keys, testing::{TestXt, UintAuthorityId}, - traits::{IdentityLookup, OpaqueKeys}, + traits::OpaqueKeys, BuildStorage, DigestItem, Perbill, }; use sp_staking::{EraIndex, SessionIndex}; @@ -68,29 +68,8 @@ impl_opaque_keys! { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = sp_runtime::traits::BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } impl frame_system::offchain::SendTransactionTypes for Test diff --git a/substrate/frame/identity/src/tests.rs b/substrate/frame/identity/src/tests.rs index 5c9304ca8c1..60866f12baa 100644 --- a/substrate/frame/identity/src/tests.rs +++ b/substrate/frame/identity/src/tests.rs @@ -55,29 +55,10 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type DbWeight = (); - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/insecure-randomness-collective-flip/src/lib.rs b/substrate/frame/insecure-randomness-collective-flip/src/lib.rs index 00f1055f6f2..04f8cda6541 100644 --- a/substrate/frame/insecure-randomness-collective-flip/src/lib.rs +++ b/substrate/frame/insecure-randomness-collective-flip/src/lib.rs @@ -163,14 +163,11 @@ mod tests { use crate as pallet_insecure_randomness_collective_flip; use sp_core::H256; - use sp_runtime::{ - traits::{BlakeTwo256, Header as _, IdentityLookup}, - BuildStorage, - }; + use sp_runtime::{traits::Header as _, BuildStorage}; use frame_support::{ derive_impl, parameter_types, - traits::{ConstU32, ConstU64, OnInitialize, Randomness}, + traits::{OnInitialize, Randomness}, }; use frame_system::limits; @@ -191,29 +188,7 @@ mod tests { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = BlockLength; - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_insecure_randomness_collective_flip::Config for Test {} diff --git a/substrate/frame/lottery/src/mock.rs b/substrate/frame/lottery/src/mock.rs index 0b73bba6135..563ce7202ec 100644 --- a/substrate/frame/lottery/src/mock.rs +++ b/substrate/frame/lottery/src/mock.rs @@ -26,11 +26,7 @@ use frame_support::{ }; use frame_support_test::TestRandomness; use frame_system::EnsureRoot; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, Perbill, -}; +use sp_runtime::{BuildStorage, Perbill}; type Block = frame_system::mocking::MockBlock; @@ -49,29 +45,8 @@ parameter_types! { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/membership/src/lib.rs b/substrate/frame/membership/src/lib.rs index 0e03c097d12..19b5e54d308 100644 --- a/substrate/frame/membership/src/lib.rs +++ b/substrate/frame/membership/src/lib.rs @@ -523,16 +523,11 @@ mod tests { use super::*; use crate as pallet_membership; - use sp_core::H256; - use sp_runtime::{ - bounded_vec, - traits::{BadOrigin, BlakeTwo256, IdentityLookup}, - BuildStorage, - }; + use sp_runtime::{bounded_vec, traits::BadOrigin, BuildStorage}; use frame_support::{ assert_noop, assert_ok, derive_impl, ord_parameter_types, parameter_types, - traits::{ConstU32, ConstU64, StorageVersion}, + traits::{ConstU32, StorageVersion}, }; use frame_system::EnsureSignedBy; @@ -553,29 +548,7 @@ mod tests { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } ord_parameter_types! { pub const One: u64 = 1; diff --git a/substrate/frame/message-queue/src/integration_test.rs b/substrate/frame/message-queue/src/integration_test.rs index aa6f019d650..cc3da6ebdc6 100644 --- a/substrate/frame/message-queue/src/integration_test.rs +++ b/substrate/frame/message-queue/src/integration_test.rs @@ -37,14 +37,9 @@ use crate::{ }; use crate as pallet_message_queue; -use frame_support::{ - derive_impl, parameter_types, - traits::{ConstU32, ConstU64}, -}; +use frame_support::{derive_impl, parameter_types}; use rand::{rngs::StdRng, Rng, SeedableRng}; use rand_distr::Pareto; -use sp_core::H256; -use sp_runtime::traits::{BlakeTwo256, IdentityLookup}; use std::collections::{BTreeMap, BTreeSet}; type Block = frame_system::mocking::MockBlock; @@ -59,29 +54,7 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } parameter_types! { diff --git a/substrate/frame/message-queue/src/mock.rs b/substrate/frame/message-queue/src/mock.rs index f1067ed961e..a46fa31df3e 100644 --- a/substrate/frame/message-queue/src/mock.rs +++ b/substrate/frame/message-queue/src/mock.rs @@ -23,15 +23,8 @@ pub use super::mock_helpers::*; use super::*; use crate as pallet_message_queue; -use frame_support::{ - derive_impl, parameter_types, - traits::{ConstU32, ConstU64}, -}; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use frame_support::{derive_impl, parameter_types}; +use sp_runtime::BuildStorage; use sp_std::collections::btree_map::BTreeMap; type Block = frame_system::mocking::MockBlock; @@ -46,29 +39,7 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } parameter_types! { pub const HeapSize: u32 = 24; diff --git a/substrate/frame/nft-fractionalization/src/mock.rs b/substrate/frame/nft-fractionalization/src/mock.rs index 40cce96d550..a4138615009 100644 --- a/substrate/frame/nft-fractionalization/src/mock.rs +++ b/substrate/frame/nft-fractionalization/src/mock.rs @@ -27,9 +27,8 @@ use frame_support::{ }; use frame_system::EnsureSigned; use pallet_nfts::PalletFeatures; -use sp_core::H256; use sp_runtime::{ - traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, + traits::{IdentifyAccount, IdentityLookup, Verify}, BuildStorage, MultiSignature, }; @@ -52,29 +51,10 @@ construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type DbWeight = (); - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/nfts/src/mock.rs b/substrate/frame/nfts/src/mock.rs index ac6d0d75732..e86fafd07e9 100644 --- a/substrate/frame/nfts/src/mock.rs +++ b/substrate/frame/nfts/src/mock.rs @@ -24,10 +24,9 @@ use frame_support::{ construct_runtime, derive_impl, parameter_types, traits::{AsEnsureOriginWithArg, ConstU32, ConstU64}, }; -use sp_core::H256; use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; use sp_runtime::{ - traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, + traits::{IdentifyAccount, IdentityLookup, Verify}, BuildStorage, MultiSignature, }; @@ -48,29 +47,10 @@ pub type AccountId = ::AccountId; #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type DbWeight = (); - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/nis/src/mock.rs b/substrate/frame/nis/src/mock.rs index 98b653635ca..03976bc66c4 100644 --- a/substrate/frame/nis/src/mock.rs +++ b/substrate/frame/nis/src/mock.rs @@ -21,19 +21,13 @@ use crate::{self as pallet_nis, Perquintill, WithMaximumOf}; use frame_support::{ derive_impl, ord_parameter_types, parameter_types, - traits::{ - fungible::Inspect, ConstU16, ConstU32, ConstU64, Everything, OnFinalize, OnInitialize, - StorageMapShim, - }, + traits::{fungible::Inspect, ConstU32, ConstU64, OnFinalize, OnInitialize, StorageMapShim}, weights::Weight, PalletId, }; use pallet_balances::{Instance1, Instance2}; -use sp_core::{ConstU128, H256}; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use sp_core::ConstU128; +use sp_runtime::BuildStorage; type Block = frame_system::mocking::MockBlock; @@ -52,29 +46,8 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = Everything; - type BlockWeights = (); - type BlockLength = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type DbWeight = (); - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = ConstU16<42>; - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/node-authorization/src/mock.rs b/substrate/frame/node-authorization/src/mock.rs index bd648de2b24..84ca4d7eff7 100644 --- a/substrate/frame/node-authorization/src/mock.rs +++ b/substrate/frame/node-authorization/src/mock.rs @@ -20,16 +20,9 @@ use super::*; use crate as pallet_node_authorization; -use frame_support::{ - derive_impl, ord_parameter_types, - traits::{ConstU32, ConstU64}, -}; +use frame_support::{derive_impl, ord_parameter_types, traits::ConstU32}; use frame_system::EnsureSignedBy; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use sp_runtime::BuildStorage; type Block = frame_system::mocking::MockBlock; @@ -43,29 +36,7 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type DbWeight = (); - type BlockWeights = (); - type BlockLength = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } ord_parameter_types! { diff --git a/substrate/frame/preimage/src/mock.rs b/substrate/frame/preimage/src/mock.rs index 60ffecbb448..a43e8347d76 100644 --- a/substrate/frame/preimage/src/mock.rs +++ b/substrate/frame/preimage/src/mock.rs @@ -22,13 +22,12 @@ use super::*; use crate as pallet_preimage; use frame_support::{ derive_impl, ord_parameter_types, parameter_types, - traits::{fungible::HoldConsideration, ConstU32, ConstU64, Everything}, - weights::constants::RocksDbWeight, + traits::{fungible::HoldConsideration, ConstU32, ConstU64}, }; use frame_system::EnsureSignedBy; use sp_core::H256; use sp_runtime::{ - traits::{BlakeTwo256, Convert, IdentityLookup}, + traits::{BlakeTwo256, Convert}, BuildStorage, }; @@ -45,29 +44,8 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = RocksDbWeight; - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/recovery/src/mock.rs b/substrate/frame/recovery/src/mock.rs index ba7958f7dd4..89374527e06 100644 --- a/substrate/frame/recovery/src/mock.rs +++ b/substrate/frame/recovery/src/mock.rs @@ -22,13 +22,9 @@ use super::*; use crate as recovery; use frame_support::{ derive_impl, parameter_types, - traits::{ConstU32, ConstU64, OnFinalize, OnInitialize}, -}; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, + traits::{OnFinalize, OnInitialize}, }; +use sp_runtime::BuildStorage; type Block = frame_system::mocking::MockBlock; @@ -43,29 +39,8 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } parameter_types! { diff --git a/substrate/frame/referenda/src/mock.rs b/substrate/frame/referenda/src/mock.rs index d9f9042fefc..bfafc107c28 100644 --- a/substrate/frame/referenda/src/mock.rs +++ b/substrate/frame/referenda/src/mock.rs @@ -29,9 +29,8 @@ use frame_support::{ weights::Weight, }; use frame_system::{EnsureRoot, EnsureSignedBy}; -use sp_core::H256; use sp_runtime::{ - traits::{BlakeTwo256, Hash, IdentityLookup}, + traits::{BlakeTwo256, Hash}, BuildStorage, DispatchResult, Perbill, }; @@ -62,28 +61,8 @@ parameter_types! { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { type BaseCallFilter = BaseFilter; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_preimage::Config for Test { type RuntimeEvent = RuntimeEvent; diff --git a/substrate/frame/remark/src/mock.rs b/substrate/frame/remark/src/mock.rs index 5a5e6a3ccdf..d89583513b6 100644 --- a/substrate/frame/remark/src/mock.rs +++ b/substrate/frame/remark/src/mock.rs @@ -18,15 +18,8 @@ //! Test environment for remarks pallet. use crate as pallet_remark; -use frame_support::{ - derive_impl, - traits::{ConstU16, ConstU32, ConstU64}, -}; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use frame_support::derive_impl; +use sp_runtime::BuildStorage; pub type Block = frame_system::mocking::MockBlock; @@ -41,29 +34,7 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type DbWeight = (); - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = ConstU16<42>; - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_remark::Config for Test { diff --git a/substrate/frame/root-offences/src/mock.rs b/substrate/frame/root-offences/src/mock.rs index b0f346032b9..1f7cce27769 100644 --- a/substrate/frame/root-offences/src/mock.rs +++ b/substrate/frame/root-offences/src/mock.rs @@ -27,13 +27,7 @@ use frame_support::{ traits::{ConstU32, ConstU64, Hooks, OneSessionHandler}, }; use pallet_staking::StakerStatus; -use sp_core::H256; -use sp_runtime::{ - curve::PiecewiseLinear, - testing::UintAuthorityId, - traits::{BlakeTwo256, IdentityLookup, Zero}, - BuildStorage, -}; +use sp_runtime::{curve::PiecewiseLinear, testing::UintAuthorityId, traits::Zero, BuildStorage}; use sp_staking::{EraIndex, SessionIndex}; use sp_std::collections::btree_map::BTreeMap; @@ -86,29 +80,8 @@ impl sp_runtime::BoundToRuntimeAppPublic for OtherSessionHandler { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/scheduler/src/mock.rs b/substrate/frame/scheduler/src/mock.rs index dbdc430a833..bf7dac0d53a 100644 --- a/substrate/frame/scheduler/src/mock.rs +++ b/substrate/frame/scheduler/src/mock.rs @@ -22,17 +22,10 @@ use super::*; use crate as scheduler; use frame_support::{ derive_impl, ord_parameter_types, parameter_types, - traits::{ - ConstU32, ConstU64, Contains, EitherOfDiverse, EqualPrivilegeOnly, OnFinalize, OnInitialize, - }, - weights::constants::RocksDbWeight, + traits::{ConstU32, Contains, EitherOfDiverse, EqualPrivilegeOnly, OnFinalize, OnInitialize}, }; use frame_system::{EnsureRoot, EnsureSignedBy}; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, Perbill, -}; +use sp_runtime::{BuildStorage, Perbill}; // Logger module to track execution. #[frame_support::pallet] @@ -147,28 +140,7 @@ parameter_types! { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl system::Config for Test { type BaseCallFilter = BaseFilter; - type BlockWeights = BlockWeights; - type BlockLength = (); - type DbWeight = RocksDbWeight; - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl logger::Config for Test { type RuntimeEvent = RuntimeEvent; diff --git a/substrate/frame/scored-pool/src/mock.rs b/substrate/frame/scored-pool/src/mock.rs index e767b49b983..6fba1bb3d53 100644 --- a/substrate/frame/scored-pool/src/mock.rs +++ b/substrate/frame/scored-pool/src/mock.rs @@ -25,12 +25,7 @@ use frame_support::{ traits::{ConstU32, ConstU64}, }; use frame_system::EnsureSignedBy; -use sp_core::H256; -use sp_runtime::{ - bounded_vec, - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use sp_runtime::{bounded_vec, BuildStorage}; type Block = frame_system::mocking::MockBlock; @@ -53,29 +48,8 @@ ord_parameter_types! { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/session/src/mock.rs b/substrate/frame/session/src/mock.rs index eb9eddfd847..89804f72cd6 100644 --- a/substrate/frame/session/src/mock.rs +++ b/substrate/frame/session/src/mock.rs @@ -24,20 +24,12 @@ use crate::historical as pallet_session_historical; use std::collections::BTreeMap; -use sp_core::{crypto::key_types::DUMMY, H256}; -use sp_runtime::{ - impl_opaque_keys, - testing::UintAuthorityId, - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use sp_core::crypto::key_types::DUMMY; +use sp_runtime::{impl_opaque_keys, testing::UintAuthorityId, BuildStorage}; use sp_staking::SessionIndex; use sp_state_machine::BasicExternalities; -use frame_support::{ - derive_impl, parameter_types, - traits::{ConstU32, ConstU64}, -}; +use frame_support::{derive_impl, parameter_types, traits::ConstU64}; impl_opaque_keys! { pub struct MockSessionKeys { @@ -234,29 +226,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_timestamp::Config for Test { diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index 40a5be51ded..24311cb9e78 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -31,14 +31,8 @@ use frame_support::{ weights::constants::RocksDbWeight, }; use frame_system::{EnsureRoot, EnsureSignedBy}; -use sp_core::H256; use sp_io; -use sp_runtime::{ - curve::PiecewiseLinear, - testing::UintAuthorityId, - traits::{IdentityLookup, Zero}, - BuildStorage, -}; +use sp_runtime::{curve::PiecewiseLinear, testing::UintAuthorityId, traits::Zero, BuildStorage}; use sp_staking::{ offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, OnStakingUpdate, @@ -49,7 +43,6 @@ pub const BLOCK_TIME: u64 = 1000; /// The AccountId alias in this test module. pub(crate) type AccountId = u64; -pub(crate) type Nonce = u64; pub(crate) type BlockNumber = u64; pub(crate) type Balance = u128; @@ -127,29 +120,9 @@ parameter_types! { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); type DbWeight = RocksDbWeight; - type RuntimeOrigin = RuntimeOrigin; - type Nonce = Nonce; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = frame_support::traits::ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } impl pallet_balances::Config for Test { type MaxLocks = frame_support::traits::ConstU32<1024>; diff --git a/substrate/frame/statement/src/mock.rs b/substrate/frame/statement/src/mock.rs index c5bee2639dc..4ab9cf9e0f9 100644 --- a/substrate/frame/statement/src/mock.rs +++ b/substrate/frame/statement/src/mock.rs @@ -22,14 +22,10 @@ use super::*; use crate as pallet_statement; use frame_support::{ derive_impl, ord_parameter_types, - traits::{ConstU32, ConstU64, Everything}, - weights::constants::RocksDbWeight, -}; -use sp_core::{Pair, H256}; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - AccountId32, BuildStorage, + traits::{ConstU32, ConstU64}, }; +use sp_core::Pair; +use sp_runtime::{traits::IdentityLookup, AccountId32, BuildStorage}; type Block = frame_system::mocking::MockBlock; @@ -49,29 +45,10 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = RocksDbWeight; - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; type AccountId = AccountId32; type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/sudo/src/mock.rs b/substrate/frame/sudo/src/mock.rs index 9ad14804524..3b907a27168 100644 --- a/substrate/frame/sudo/src/mock.rs +++ b/substrate/frame/sudo/src/mock.rs @@ -19,16 +19,9 @@ use super::*; use crate as sudo; -use frame_support::{ - derive_impl, - traits::{ConstU32, Contains}, -}; -use sp_core::{ConstU64, H256}; +use frame_support::{derive_impl, traits::Contains}; use sp_io; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use sp_runtime::BuildStorage; // Logger module to track execution. #[frame_support::pallet] @@ -113,29 +106,7 @@ impl Contains for BlockEverything { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = BlockEverything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } // Implement the logger module's `Config` on the Test runtime. diff --git a/substrate/frame/system/src/mock.rs b/substrate/frame/system/src/mock.rs index 06527a9bb29..c4108099e39 100644 --- a/substrate/frame/system/src/mock.rs +++ b/substrate/frame/system/src/mock.rs @@ -16,15 +16,8 @@ // limitations under the License. use crate::{self as frame_system, *}; -use frame_support::{ - derive_impl, parameter_types, - traits::{ConstU32, ConstU64}, -}; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, Perbill, -}; +use frame_support::{derive_impl, parameter_types}; +use sp_runtime::{BuildStorage, Perbill}; type Block = mocking::MockBlock; @@ -87,29 +80,12 @@ impl OnKilledAccount for RecordKilled { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl Config for Test { - type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = RuntimeBlockWeights; type BlockLength = RuntimeBlockLength; - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<10>; - type DbWeight = DbWeight; type Version = Version; - type PalletInfo = PalletInfo; type AccountData = u32; - type OnNewAccount = (); type OnKilledAccount = RecordKilled; - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } pub type SysEvent = frame_system::Event; diff --git a/substrate/frame/timestamp/src/mock.rs b/substrate/frame/timestamp/src/mock.rs index 5cbc5211368..244b66a4bb2 100644 --- a/substrate/frame/timestamp/src/mock.rs +++ b/substrate/frame/timestamp/src/mock.rs @@ -20,16 +20,9 @@ use super::*; use crate as pallet_timestamp; -use frame_support::{ - derive_impl, parameter_types, - traits::{ConstU32, ConstU64}, -}; -use sp_core::H256; +use frame_support::{derive_impl, parameter_types, traits::ConstU64}; use sp_io::TestExternalities; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use sp_runtime::BuildStorage; type Block = frame_system::mocking::MockBlock; type Moment = u64; @@ -44,29 +37,7 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } parameter_types! { diff --git a/substrate/frame/tips/src/tests.rs b/substrate/frame/tips/src/tests.rs index 9d4047cd80e..0e7ea1f4781 100644 --- a/substrate/frame/tips/src/tests.rs +++ b/substrate/frame/tips/src/tests.rs @@ -59,29 +59,10 @@ parameter_types! { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; type AccountId = u128; // u64 is not enough to hold bytes used to generate bounty account type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/treasury/src/tests.rs b/substrate/frame/treasury/src/tests.rs index e35d50e23a3..b488300de99 100644 --- a/substrate/frame/treasury/src/tests.rs +++ b/substrate/frame/treasury/src/tests.rs @@ -20,9 +20,8 @@ #![cfg(test)] use core::{cell::RefCell, marker::PhantomData}; -use sp_core::H256; use sp_runtime::{ - traits::{BadOrigin, BlakeTwo256, Dispatchable, IdentityLookup}, + traits::{BadOrigin, Dispatchable, IdentityLookup}, BuildStorage, }; @@ -56,29 +55,10 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; type AccountId = u128; // u64 is not enough to hold bytes used to generate bounty account type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { type MaxLocks = (); diff --git a/substrate/frame/uniques/src/mock.rs b/substrate/frame/uniques/src/mock.rs index 16da2b2a2e2..eae12597163 100644 --- a/substrate/frame/uniques/src/mock.rs +++ b/substrate/frame/uniques/src/mock.rs @@ -24,11 +24,7 @@ use frame_support::{ construct_runtime, derive_impl, traits::{AsEnsureOriginWithArg, ConstU32, ConstU64}, }; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use sp_runtime::BuildStorage; type Block = frame_system::mocking::MockBlock; @@ -43,29 +39,8 @@ construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type DbWeight = (); - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/utility/src/tests.rs b/substrate/frame/utility/src/tests.rs index 1a1196cb4c0..8742513be95 100644 --- a/substrate/frame/utility/src/tests.rs +++ b/substrate/frame/utility/src/tests.rs @@ -27,13 +27,12 @@ use frame_support::{ dispatch::{DispatchErrorWithPostInfo, Pays}, error::BadOrigin, parameter_types, storage, - traits::{ConstU32, ConstU64, Contains}, + traits::{ConstU64, Contains}, weights::Weight, }; use pallet_collective::{EnsureProportionAtLeast, Instance1}; -use sp_core::H256; use sp_runtime::{ - traits::{BlakeTwo256, Dispatchable, Hash, IdentityLookup}, + traits::{BlakeTwo256, Dispatchable, Hash}, BuildStorage, DispatchError, TokenError, }; @@ -148,27 +147,8 @@ parameter_types! { impl frame_system::Config for Test { type BaseCallFilter = TestBaseCallFilter; type BlockWeights = BlockWeights; - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = frame_system::weights::SubstrateWeight; - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/vesting/src/mock.rs b/substrate/frame/vesting/src/mock.rs index befe8cd3b76..8a0cd135125 100644 --- a/substrate/frame/vesting/src/mock.rs +++ b/substrate/frame/vesting/src/mock.rs @@ -17,13 +17,9 @@ use frame_support::{ derive_impl, parameter_types, - traits::{ConstU32, ConstU64, WithdrawReasons}, -}; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, Identity, IdentityLookup}, - BuildStorage, + traits::{ConstU32, WithdrawReasons}, }; +use sp_runtime::{traits::Identity, BuildStorage}; use super::*; use crate as pallet_vesting; @@ -42,28 +38,7 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { type AccountData = pallet_balances::AccountData; - type AccountId = u64; - type BaseCallFilter = frame_support::traits::Everything; - type BlockHashCount = ConstU64<250>; - type BlockLength = (); - type BlockWeights = (); - type RuntimeCall = RuntimeCall; - type DbWeight = (); - type RuntimeEvent = RuntimeEvent; - type Hash = H256; - type Hashing = BlakeTwo256; type Block = Block; - type Nonce = u64; - type Lookup = IdentityLookup; - type OnKilledAccount = (); - type OnNewAccount = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; - type RuntimeOrigin = RuntimeOrigin; - type PalletInfo = PalletInfo; - type SS58Prefix = (); - type SystemWeightInfo = (); - type Version = (); } impl pallet_balances::Config for Test { diff --git a/substrate/frame/whitelist/src/mock.rs b/substrate/frame/whitelist/src/mock.rs index c0c38075f29..e323e806b81 100644 --- a/substrate/frame/whitelist/src/mock.rs +++ b/substrate/frame/whitelist/src/mock.rs @@ -21,16 +21,9 @@ use crate as pallet_whitelist; -use frame_support::{ - construct_runtime, derive_impl, - traits::{ConstU32, ConstU64, Nothing}, -}; +use frame_support::{construct_runtime, derive_impl, traits::ConstU64}; use frame_system::EnsureRoot; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use sp_runtime::BuildStorage; type Block = frame_system::mocking::MockBlock; @@ -46,29 +39,8 @@ construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = Nothing; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { -- GitLab From 197c6cf9e09afaeab26b31e266c1f45cff6b1197 Mon Sep 17 00:00:00 2001 From: Alexandru Gheorghe <49718502+alexggh@users.noreply.github.com> Date: Mon, 19 Feb 2024 14:22:49 +0200 Subject: [PATCH 087/133] gossip-support: add unittests for update authorities (#3258) ~The previous fix was actually incomplete because we update the authorties only on the situation where we decided to reconnect because we had a low connectivity issue. Now the problem is that update_authority_ids use the list of connected peers, so on restart that does contain anything, so calling immediately after issue_connection_request won't detect all authorities, so we need to also check every block as the comment said, but that did not match the code.~ Actually the fix was correct the flow is follow if more than 1/3 of the authorities can not be resolved we set last_failure and call `ConnectToResolvedValidators`. We will call UpdateAuthorities for all the authorities already connected and for which we already know the address and for the ones that will connect later on `PeerConnected` will have the AuthorityId field set, because it is already known, so approval-distribution will update its cache topology. --------- Signed-off-by: Alexandru Gheorghe Co-authored-by: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> --- Cargo.lock | 1 + .../node/network/gossip-support/Cargo.toml | 1 + .../node/network/gossip-support/src/lib.rs | 8 +- .../node/network/gossip-support/src/tests.rs | 675 +++++++++++++----- 4 files changed, 506 insertions(+), 179 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aea00abb8f0..c26c4081bbd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12325,6 +12325,7 @@ dependencies = [ "futures", "futures-timer", "lazy_static", + "parking_lot 0.12.1", "polkadot-node-network-protocol", "polkadot-node-subsystem", "polkadot-node-subsystem-test-helpers", diff --git a/polkadot/node/network/gossip-support/Cargo.toml b/polkadot/node/network/gossip-support/Cargo.toml index c17f39b019d..8d0edc206d7 100644 --- a/polkadot/node/network/gossip-support/Cargo.toml +++ b/polkadot/node/network/gossip-support/Cargo.toml @@ -38,5 +38,6 @@ polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" } assert_matches = "1.4.0" async-trait = "0.1.74" +parking_lot = "0.12.1" lazy_static = "1.4.0" quickcheck = "1.0.3" diff --git a/polkadot/node/network/gossip-support/src/lib.rs b/polkadot/node/network/gossip-support/src/lib.rs index e9cb8a4de1c..4dfdd1f7208 100644 --- a/polkadot/node/network/gossip-support/src/lib.rs +++ b/polkadot/node/network/gossip-support/src/lib.rs @@ -63,8 +63,12 @@ use metrics::Metrics; const LOG_TARGET: &str = "parachain::gossip-support"; // How much time should we wait to reissue a connection request // since the last authority discovery resolution failure. +#[cfg(not(test))] const BACKOFF_DURATION: Duration = Duration::from_secs(5); +#[cfg(test)] +const BACKOFF_DURATION: Duration = Duration::from_millis(500); + /// Duration after which we consider low connectivity a problem. /// /// Especially at startup low connectivity is expected (authority discovery cache needs to be @@ -271,8 +275,8 @@ where ) .await?; } - // authority_discovery is just a cache so let's try every leaf to detect if there - // are new authorities there. + // authority_discovery is just a cache so let's try every time we try to re-connect + // if new authorities are present. self.update_authority_ids(sender, session_info.discovery_keys).await; } } diff --git a/polkadot/node/network/gossip-support/src/tests.rs b/polkadot/node/network/gossip-support/src/tests.rs index e5ee101c31d..6817c85f98d 100644 --- a/polkadot/node/network/gossip-support/src/tests.rs +++ b/polkadot/node/network/gossip-support/src/tests.rs @@ -25,13 +25,19 @@ use lazy_static::lazy_static; use quickcheck::quickcheck; use rand::seq::SliceRandom as _; +use parking_lot::Mutex; use sc_network::multiaddr::Protocol; use sp_authority_discovery::AuthorityPair as AuthorityDiscoveryPair; use sp_consensus_babe::{AllowedSlots, BabeEpochConfiguration, Epoch as BabeEpoch}; use sp_core::crypto::Pair as PairT; use sp_keyring::Sr25519Keyring; +use std::sync::Arc; -use polkadot_node_network_protocol::grid_topology::{SessionGridTopology, TopologyPeerInfo}; +use polkadot_node_network_protocol::{ + grid_topology::{SessionGridTopology, TopologyPeerInfo}, + peer_set::ValidationVersion, + ObservedRole, +}; use polkadot_node_subsystem::messages::{AllMessages, RuntimeApiMessage, RuntimeApiRequest}; use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_node_subsystem_util::TimeoutExt as _; @@ -51,7 +57,6 @@ const AUTHORITY_KEYRINGS: &[Sr25519Keyring] = &[ ]; lazy_static! { - static ref MOCK_AUTHORITY_DISCOVERY: MockAuthorityDiscovery = MockAuthorityDiscovery::new(); static ref AUTHORITIES: Vec = AUTHORITY_KEYRINGS.iter().map(|k| k.public().into()).collect(); @@ -89,17 +94,14 @@ type VirtualOverseer = test_helpers::TestSubsystemContextHandle>, - authorities: HashMap>, + addrs: Arc>>>, + authorities: Arc>>>, } impl MockAuthorityDiscovery { - fn new() -> Self { - let authorities: HashMap<_, _> = PAST_PRESENT_FUTURE_AUTHORITIES - .clone() - .into_iter() - .map(|a| (PeerId::random(), a)) - .collect(); + fn new(authorities: Vec) -> Self { + let authorities: HashMap<_, _> = + authorities.clone().into_iter().map(|a| (PeerId::random(), a)).collect(); let addrs = authorities .clone() .into_iter() @@ -109,10 +111,37 @@ impl MockAuthorityDiscovery { }) .collect(); Self { - addrs, - authorities: authorities.into_iter().map(|(p, a)| (p, HashSet::from([a]))).collect(), + addrs: Arc::new(Mutex::new(addrs)), + authorities: Arc::new(Mutex::new( + authorities.into_iter().map(|(p, a)| (p, HashSet::from([a]))).collect(), + )), } } + + fn authorities(&self) -> HashMap> { + self.authorities.lock().clone() + } + + fn add_more_authorties( + &self, + new_known: Vec, + ) -> HashMap> { + let authorities: HashMap<_, _> = + new_known.clone().into_iter().map(|a| (PeerId::random(), a)).collect(); + let addrs: HashMap> = authorities + .clone() + .into_iter() + .map(|(p, a)| { + let multiaddr = Multiaddr::empty().with(Protocol::P2p(p.into())); + (a, HashSet::from([multiaddr])) + }) + .collect(); + let authorities: HashMap> = + authorities.into_iter().map(|(p, a)| (p, HashSet::from([a]))).collect(); + self.addrs.as_ref().lock().extend(addrs); + self.authorities.as_ref().lock().extend(authorities.clone()); + authorities + } } #[async_trait] @@ -121,19 +150,23 @@ impl AuthorityDiscovery for MockAuthorityDiscovery { &mut self, authority: polkadot_primitives::AuthorityDiscoveryId, ) -> Option> { - self.addrs.get(&authority).cloned() + self.addrs.lock().get(&authority).cloned() } + async fn get_authority_ids_by_peer_id( &mut self, peer_id: polkadot_node_network_protocol::PeerId, ) -> Option> { - self.authorities.get(&peer_id).cloned() + self.authorities.as_ref().lock().get(&peer_id).cloned() } } -async fn get_multiaddrs(authorities: Vec) -> Vec> { +async fn get_multiaddrs( + authorities: Vec, + mock_authority_discovery: MockAuthorityDiscovery, +) -> Vec> { let mut addrs = Vec::with_capacity(authorities.len()); - let mut discovery = MOCK_AUTHORITY_DISCOVERY.clone(); + let mut discovery = mock_authority_discovery.clone(); for authority in authorities.into_iter() { if let Some(addr) = discovery.get_addresses_by_authority_id(authority).await { addrs.push(addr); @@ -144,9 +177,10 @@ async fn get_multiaddrs(authorities: Vec) -> Vec, + mock_authority_discovery: MockAuthorityDiscovery, ) -> HashMap> { let mut addrs = HashMap::with_capacity(authorities.len()); - let mut discovery = MOCK_AUTHORITY_DISCOVERY.clone(); + let mut discovery = mock_authority_discovery.clone(); for authority in authorities.into_iter() { if let Some(addr) = discovery.get_addresses_by_authority_id(authority.clone()).await { addrs.insert(authority, addr); @@ -155,12 +189,10 @@ async fn get_address_map( addrs } -fn make_subsystem() -> GossipSupport { - GossipSupport::new( - make_ferdie_keystore(), - MOCK_AUTHORITY_DISCOVERY.clone(), - Metrics::new_dummy(), - ) +fn make_subsystem_with_authority_discovery( + mock: MockAuthorityDiscovery, +) -> GossipSupport { + GossipSupport::new(make_ferdie_keystore(), mock, Metrics::new_dummy()) } fn test_harness, AD: AuthorityDiscovery>( @@ -291,59 +323,65 @@ async fn test_neighbors(overseer: &mut VirtualOverseer, expected_session: Sessio #[test] fn issues_a_connection_request_on_new_session() { + let mock_authority_discovery = + MockAuthorityDiscovery::new(PAST_PRESENT_FUTURE_AUTHORITIES.clone()); + let mock_authority_discovery_clone = mock_authority_discovery.clone(); let hash = Hash::repeat_byte(0xAA); - let state = test_harness(make_subsystem(), |mut virtual_overseer| async move { - let overseer = &mut virtual_overseer; - overseer_signal_active_leaves(overseer, hash).await; - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::SessionIndexForChild(tx), - )) => { - assert_eq!(relay_parent, hash); - tx.send(Ok(1)).unwrap(); - } - ); + let state = test_harness( + make_subsystem_with_authority_discovery(mock_authority_discovery.clone()), + |mut virtual_overseer| async move { + let overseer = &mut virtual_overseer; + overseer_signal_active_leaves(overseer, hash).await; + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionIndexForChild(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(1)).unwrap(); + } + ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::SessionInfo(s, tx), - )) => { - assert_eq!(relay_parent, hash); - assert_eq!(s, 1); - tx.send(Ok(Some(make_session_info()))).unwrap(); - } - ); + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionInfo(s, tx), + )) => { + assert_eq!(relay_parent, hash); + assert_eq!(s, 1); + tx.send(Ok(Some(make_session_info()))).unwrap(); + } + ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::Authorities(tx), - )) => { - assert_eq!(relay_parent, hash); - tx.send(Ok(AUTHORITIES.clone())).unwrap(); - } - ); + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::Authorities(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(AUTHORITIES.clone())).unwrap(); + } + ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { - validator_addrs, - peer_set, - }) => { - assert_eq!(validator_addrs, get_multiaddrs(AUTHORITIES_WITHOUT_US.clone()).await); - assert_eq!(peer_set, PeerSet::Validation); - } - ); + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { + validator_addrs, + peer_set, + }) => { + assert_eq!(validator_addrs, get_multiaddrs(AUTHORITIES_WITHOUT_US.clone(), mock_authority_discovery_clone).await); + assert_eq!(peer_set, PeerSet::Validation); + } + ); - test_neighbors(overseer, 1).await; + test_neighbors(overseer, 1).await; - virtual_overseer - }); + virtual_overseer + }, + ); assert_eq!(state.last_session_index, Some(1)); assert!(state.last_failure.is_none()); @@ -363,6 +401,7 @@ fn issues_a_connection_request_on_new_session() { tx.send(Ok(1)).unwrap(); } ); + virtual_overseer }); @@ -414,7 +453,7 @@ fn issues_a_connection_request_on_new_session() { validator_addrs, peer_set, }) => { - assert_eq!(validator_addrs, get_multiaddrs(AUTHORITIES_WITHOUT_US.clone()).await); + assert_eq!(validator_addrs, get_multiaddrs(AUTHORITIES_WITHOUT_US.clone(), mock_authority_discovery.clone()).await); assert_eq!(peer_set, PeerSet::Validation); } ); @@ -430,125 +469,405 @@ fn issues_a_connection_request_on_new_session() { #[test] fn issues_connection_request_to_past_present_future() { let hash = Hash::repeat_byte(0xAA); - test_harness(make_subsystem(), |mut virtual_overseer| async move { - let overseer = &mut virtual_overseer; - overseer_signal_active_leaves(overseer, hash).await; - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::SessionIndexForChild(tx), - )) => { - assert_eq!(relay_parent, hash); - tx.send(Ok(1)).unwrap(); + let mock_authority_discovery = + MockAuthorityDiscovery::new(PAST_PRESENT_FUTURE_AUTHORITIES.clone()); + test_harness( + make_subsystem_with_authority_discovery(mock_authority_discovery.clone()), + |mut virtual_overseer| async move { + let overseer = &mut virtual_overseer; + overseer_signal_active_leaves(overseer, hash).await; + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionIndexForChild(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(1)).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionInfo(s, tx), + )) => { + assert_eq!(relay_parent, hash); + assert_eq!(s, 1); + tx.send(Ok(Some(make_session_info()))).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::Authorities(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(PAST_PRESENT_FUTURE_AUTHORITIES.clone())).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { + validator_addrs, + peer_set, + }) => { + let all_without_ferdie: Vec<_> = PAST_PRESENT_FUTURE_AUTHORITIES + .iter() + .cloned() + .filter(|p| p != &Sr25519Keyring::Ferdie.public().into()) + .collect(); + + let addrs = get_multiaddrs(all_without_ferdie, mock_authority_discovery.clone()).await; + + assert_eq!(validator_addrs, addrs); + assert_eq!(peer_set, PeerSet::Validation); + } + ); + + // Ensure neighbors are unaffected + test_neighbors(overseer, 1).await; + + virtual_overseer + }, + ); +} + +// Test we notify peer about learning of the authority ID after session boundary, when we couldn't +// connect to more than 1/3 of the authorities. +#[test] +fn issues_update_authorities_after_session() { + let hash = Hash::repeat_byte(0xAA); + + let mut authorities = PAST_PRESENT_FUTURE_AUTHORITIES.clone(); + let unknown_at_session = authorities.split_off(authorities.len() / 3 - 1); + let mut authority_discovery_mock = MockAuthorityDiscovery::new(authorities); + + test_harness( + make_subsystem_with_authority_discovery(authority_discovery_mock.clone()), + |mut virtual_overseer| async move { + let overseer = &mut virtual_overseer; + // 1. Initialize with the first leaf in the session. + overseer_signal_active_leaves(overseer, hash).await; + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionIndexForChild(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(1)).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionInfo(s, tx), + )) => { + assert_eq!(relay_parent, hash); + assert_eq!(s, 1); + let mut session_info = make_session_info(); + session_info.discovery_keys = PAST_PRESENT_FUTURE_AUTHORITIES.clone(); + tx.send(Ok(Some(session_info))).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::Authorities(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(PAST_PRESENT_FUTURE_AUTHORITIES.clone())).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { + validator_addrs, + peer_set, + }) => { + let all_without_ferdie: Vec<_> = PAST_PRESENT_FUTURE_AUTHORITIES + .iter() + .cloned() + .filter(|p| p != &Sr25519Keyring::Ferdie.public().into()) + .collect(); + + let addrs = get_multiaddrs(all_without_ferdie, authority_discovery_mock.clone()).await; + + assert_eq!(validator_addrs, addrs); + assert_eq!(peer_set, PeerSet::Validation); + } + ); + + // Ensure neighbors are unaffected + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _, + RuntimeApiRequest::CurrentBabeEpoch(tx), + )) => { + let _ = tx.send(Ok(BabeEpoch { + epoch_index: 2 as _, + start_slot: 0.into(), + duration: 200, + authorities: vec![(Sr25519Keyring::Alice.public().into(), 1)], + randomness: [0u8; 32], + config: BabeEpochConfiguration { + c: (1, 4), + allowed_slots: AllowedSlots::PrimarySlots, + }, + })).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeRx(NetworkBridgeRxMessage::NewGossipTopology { + session: _, + local_index: _, + canonical_shuffling: _, + shuffled_indices: _, + }) => { + + } + ); + + // 2. Connect all authorities that are known so far. + let known_authorities = authority_discovery_mock.authorities(); + for (peer_id, _id) in known_authorities.iter() { + let msg = + GossipSupportMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerConnected( + *peer_id, + ObservedRole::Authority, + ValidationVersion::V3.into(), + None, + )); + overseer.send(FromOrchestra::Communication { msg }).await } - ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::SessionInfo(s, tx), - )) => { - assert_eq!(relay_parent, hash); - assert_eq!(s, 1); - tx.send(Ok(Some(make_session_info()))).unwrap(); + Delay::new(BACKOFF_DURATION).await; + // 3. Send a new leaf after BACKOFF_DURATION and check UpdateAuthority is emitted for + // all known connected peers. + let hash = Hash::repeat_byte(0xBB); + overseer_signal_active_leaves(overseer, hash).await; + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionIndexForChild(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(1)).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionInfo(s, tx), + )) => { + assert_eq!(relay_parent, hash); + assert_eq!(s, 1); + let mut session_info = make_session_info(); + session_info.discovery_keys = PAST_PRESENT_FUTURE_AUTHORITIES.clone(); + tx.send(Ok(Some(session_info))).unwrap(); + + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::Authorities(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(PAST_PRESENT_FUTURE_AUTHORITIES.clone())).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { + validator_addrs: _, + peer_set: _, + }) => { + } + ); + + for _ in 0..known_authorities.len() { + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeRx(NetworkBridgeRxMessage::UpdatedAuthorityIds { + peer_id, + authority_ids, + }) => { + assert_eq!(authority_discovery_mock.get_authority_ids_by_peer_id(peer_id).await.unwrap_or_default(), authority_ids); + } + ); } - ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::Authorities(tx), - )) => { - assert_eq!(relay_parent, hash); - tx.send(Ok(PAST_PRESENT_FUTURE_AUTHORITIES.clone())).unwrap(); + assert!(overseer.recv().timeout(TIMEOUT).await.is_none()); + // 4. Connect more authorities except one + let newly_added = authority_discovery_mock.add_more_authorties(unknown_at_session); + let mut newly_added_iter = newly_added.iter(); + let unconnected_at_last_retry = newly_added_iter + .next() + .map(|(peer_id, authority_id)| (*peer_id, authority_id.clone())) + .unwrap(); + for (peer_id, _) in newly_added_iter { + let msg = + GossipSupportMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerConnected( + *peer_id, + ObservedRole::Authority, + ValidationVersion::V3.into(), + None, + )); + overseer.send(FromOrchestra::Communication { msg }).await } - ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { - validator_addrs, - peer_set, - }) => { - let all_without_ferdie: Vec<_> = PAST_PRESENT_FUTURE_AUTHORITIES - .iter() - .cloned() - .filter(|p| p != &Sr25519Keyring::Ferdie.public().into()) - .collect(); + // 5. Send a new leaf and check UpdateAuthority is emitted only for the newly connected + // peers. + let hash = Hash::repeat_byte(0xCC); + Delay::new(BACKOFF_DURATION).await; + overseer_signal_active_leaves(overseer, hash).await; - let addrs = get_multiaddrs(all_without_ferdie).await; + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionIndexForChild(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(1)).unwrap(); + } + ); - assert_eq!(validator_addrs, addrs); - assert_eq!(peer_set, PeerSet::Validation); - } - ); + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionInfo(s, tx), + )) => { + assert_eq!(relay_parent, hash); + assert_eq!(s, 1); + let mut session_info = make_session_info(); + session_info.discovery_keys = PAST_PRESENT_FUTURE_AUTHORITIES.clone(); + tx.send(Ok(Some(session_info))).unwrap(); + } + ); - // Ensure neighbors are unaffected - test_neighbors(overseer, 1).await; + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::Authorities(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(PAST_PRESENT_FUTURE_AUTHORITIES.clone())).unwrap(); + } + ); - virtual_overseer - }); + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { + validator_addrs: _, + peer_set: _, + }) => { + } + ); + + for _ in 1..newly_added.len() { + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeRx(NetworkBridgeRxMessage::UpdatedAuthorityIds { + peer_id, + authority_ids, + }) => { + assert_ne!(peer_id, unconnected_at_last_retry.0); + assert_eq!(newly_added.get(&peer_id).cloned().unwrap_or_default(), authority_ids); + } + ); + } + + assert!(overseer.recv().timeout(TIMEOUT).await.is_none()); + virtual_overseer + }, + ); } #[test] fn disconnect_when_not_in_past_present_future() { sp_tracing::try_init_simple(); + let mock_authority_discovery = + MockAuthorityDiscovery::new(PAST_PRESENT_FUTURE_AUTHORITIES.clone()); let hash = Hash::repeat_byte(0xAA); - test_harness(make_subsystem(), |mut virtual_overseer| async move { - let overseer = &mut virtual_overseer; - overseer_signal_active_leaves(overseer, hash).await; - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::SessionIndexForChild(tx), - )) => { - assert_eq!(relay_parent, hash); - tx.send(Ok(1)).unwrap(); - } - ); + test_harness( + make_subsystem_with_authority_discovery(mock_authority_discovery.clone()), + |mut virtual_overseer| async move { + let overseer = &mut virtual_overseer; + overseer_signal_active_leaves(overseer, hash).await; + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionIndexForChild(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(1)).unwrap(); + } + ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::SessionInfo(s, tx), - )) => { - assert_eq!(relay_parent, hash); - assert_eq!(s, 1); - let mut heute_leider_nicht = make_session_info(); - heute_leider_nicht.discovery_keys = AUTHORITIES_WITHOUT_US.clone(); - tx.send(Ok(Some(heute_leider_nicht))).unwrap(); - } - ); + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionInfo(s, tx), + )) => { + assert_eq!(relay_parent, hash); + assert_eq!(s, 1); + let mut heute_leider_nicht = make_session_info(); + heute_leider_nicht.discovery_keys = AUTHORITIES_WITHOUT_US.clone(); + tx.send(Ok(Some(heute_leider_nicht))).unwrap(); + } + ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::Authorities(tx), - )) => { - assert_eq!(relay_parent, hash); - tx.send(Ok(AUTHORITIES_WITHOUT_US.clone())).unwrap(); - } - ); + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::Authorities(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(AUTHORITIES_WITHOUT_US.clone())).unwrap(); + } + ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { - validator_addrs, - peer_set, - }) => { - assert!(validator_addrs.is_empty()); - assert_eq!(peer_set, PeerSet::Validation); - } - ); + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { + validator_addrs, + peer_set, + }) => { + assert!(validator_addrs.is_empty()); + assert_eq!(peer_set, PeerSet::Validation); + } + ); - virtual_overseer - }); + virtual_overseer + }, + ); } #[test] @@ -579,13 +898,15 @@ fn test_log_output() { #[test] fn issues_a_connection_request_when_last_request_was_mostly_unresolved() { let hash = Hash::repeat_byte(0xAA); - let mut state = make_subsystem(); + let mock_authority_discovery = + MockAuthorityDiscovery::new(PAST_PRESENT_FUTURE_AUTHORITIES.clone()); + let state = make_subsystem_with_authority_discovery(mock_authority_discovery.clone()); // There will be two lookup failures: let alice = Sr25519Keyring::Alice.public().into(); let bob = Sr25519Keyring::Bob.public().into(); - let alice_addr = state.authority_discovery.addrs.remove(&alice); - state.authority_discovery.addrs.remove(&bob); - + let alice_addr = state.authority_discovery.addrs.lock().remove(&alice); + state.authority_discovery.addrs.lock().remove(&bob); + let mock_authority_discovery_clone = mock_authority_discovery.clone(); let mut state = { let alice = alice.clone(); let bob = bob.clone(); @@ -633,7 +954,7 @@ fn issues_a_connection_request_when_last_request_was_mostly_unresolved() { validator_addrs, peer_set, }) => { - let mut expected = get_address_map(AUTHORITIES_WITHOUT_US.clone()).await; + let mut expected = get_address_map(AUTHORITIES_WITHOUT_US.clone(), mock_authority_discovery_clone.clone()).await; expected.remove(&alice); expected.remove(&bob); let expected: HashSet = expected.into_values().flat_map(|v| v.into_iter()).collect(); @@ -652,7 +973,7 @@ fn issues_a_connection_request_when_last_request_was_mostly_unresolved() { assert!(state.last_failure.is_some()); state.last_failure = state.last_failure.and_then(|i| i.checked_sub(BACKOFF_DURATION)); // One error less: - state.authority_discovery.addrs.insert(alice, alice_addr.unwrap()); + state.authority_discovery.addrs.lock().insert(alice, alice_addr.unwrap()); let hash = Hash::repeat_byte(0xBB); let state = test_harness(state, |mut virtual_overseer| async move { @@ -698,7 +1019,7 @@ fn issues_a_connection_request_when_last_request_was_mostly_unresolved() { validator_addrs, peer_set, }) => { - let mut expected = get_address_map(AUTHORITIES_WITHOUT_US.clone()).await; + let mut expected = get_address_map(AUTHORITIES_WITHOUT_US.clone(), mock_authority_discovery.clone()).await; expected.remove(&bob); let expected: HashSet = expected.into_values().flat_map(|v| v.into_iter()).collect(); assert_eq!(validator_addrs.into_iter().flat_map(|v| v.into_iter()).collect::>(), expected); -- GitLab From 435e33926177c3c384d9ba4867952ebdb6a003b0 Mon Sep 17 00:00:00 2001 From: Matteo Muraca <56828990+muraca@users.noreply.github.com> Date: Mon, 19 Feb 2024 14:19:25 +0100 Subject: [PATCH 088/133] removed `pallet::getter` from `pallet-sudo` (#3370) part of #3326 @ggwpez @kianenigma @shawntabrizi --------- Signed-off-by: Matteo Muraca Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- substrate/frame/sudo/src/extension.rs | 4 ++-- substrate/frame/sudo/src/lib.rs | 3 +-- substrate/frame/sudo/src/tests.rs | 10 +++++----- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/substrate/frame/sudo/src/extension.rs b/substrate/frame/sudo/src/extension.rs index c717ff35672..e90286e5a7c 100644 --- a/substrate/frame/sudo/src/extension.rs +++ b/substrate/frame/sudo/src/extension.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{Config, Pallet}; +use crate::{Config, Key}; use codec::{Decode, Encode}; use frame_support::{dispatch::DispatchInfo, ensure}; use scale_info::TypeInfo; @@ -86,7 +86,7 @@ where info: &DispatchInfoOf, _len: usize, ) -> TransactionValidity { - let sudo_key: T::AccountId = >::key().ok_or(UnknownTransaction::CannotLookup)?; + let sudo_key: T::AccountId = Key::::get().ok_or(UnknownTransaction::CannotLookup)?; ensure!(*who == sudo_key, InvalidTransaction::BadSigner); Ok(ValidTransaction { diff --git a/substrate/frame/sudo/src/lib.rs b/substrate/frame/sudo/src/lib.rs index 4f14c32ff76..2ebe4cb0157 100644 --- a/substrate/frame/sudo/src/lib.rs +++ b/substrate/frame/sudo/src/lib.rs @@ -329,7 +329,6 @@ pub mod pallet { /// The `AccountId` of the sudo key. #[pallet::storage] - #[pallet::getter(fn key)] pub(super) type Key = StorageValue<_, T::AccountId, OptionQuery>; #[pallet::genesis_config] @@ -352,7 +351,7 @@ pub mod pallet { let sender = ensure_signed_or_root(origin)?; if let Some(sender) = sender { - if Self::key().map_or(false, |k| k == sender) { + if Key::::get().map_or(false, |k| k == sender) { Ok(()) } else { Err(Error::::RequireSudo.into()) diff --git a/substrate/frame/sudo/src/tests.rs b/substrate/frame/sudo/src/tests.rs index 73689415a73..00bb86cc268 100644 --- a/substrate/frame/sudo/src/tests.rs +++ b/substrate/frame/sudo/src/tests.rs @@ -28,7 +28,7 @@ use mock::{ fn test_setup_works() { // Environment setup, logger storage, and sudo `key` retrieval should work as expected. new_test_ext(1).execute_with(|| { - assert_eq!(Sudo::key(), Some(1u64)); + assert_eq!(Key::::get(), Some(1u64)); assert!(Logger::i32_log().is_empty()); assert!(Logger::account_log().is_empty()); }); @@ -135,7 +135,7 @@ fn set_key_basics() { new_test_ext(1).execute_with(|| { // A root `key` can change the root `key` assert_ok!(Sudo::set_key(RuntimeOrigin::signed(1), 2)); - assert_eq!(Sudo::key(), Some(2u64)); + assert_eq!(Key::::get(), Some(2u64)); }); new_test_ext(1).execute_with(|| { @@ -161,7 +161,7 @@ fn set_key_emits_events_correctly() { fn remove_key_works() { new_test_ext(1).execute_with(|| { assert_ok!(Sudo::remove_key(RuntimeOrigin::signed(1))); - assert!(Sudo::key().is_none()); + assert!(Key::::get().is_none()); System::assert_has_event(TestEvent::Sudo(Event::KeyRemoved {})); assert_noop!(Sudo::remove_key(RuntimeOrigin::signed(1)), Error::::RequireSudo); @@ -173,11 +173,11 @@ fn remove_key_works() { fn using_root_origin_works() { new_test_ext(1).execute_with(|| { assert_ok!(Sudo::remove_key(RuntimeOrigin::root())); - assert!(Sudo::key().is_none()); + assert!(Key::::get().is_none()); System::assert_has_event(TestEvent::Sudo(Event::KeyRemoved {})); assert_ok!(Sudo::set_key(RuntimeOrigin::root(), 1)); - assert_eq!(Some(1), Sudo::key()); + assert_eq!(Some(1), Key::::get()); }); } -- GitLab From 320863a8471ee25dc5b5ebc2395a0d139163092e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20Vilhelm=20=C3=81sgeirsson?= Date: Mon, 19 Feb 2024 13:25:19 +0000 Subject: [PATCH 089/133] Add Coretime to Westend (#3319) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the coretime and on demand pallets to enable Coretime on Westend. In order for the migration to run successfully, we need the Broker/Coretime parachain to be live. TODO: - [ ] Broker parachain is live https://github.com/paritytech/polkadot-sdk/pull/3272 --------- Co-authored-by: command-bot <> Co-authored-by: Bastian Köcher --- polkadot/runtime/westend/src/lib.rs | 69 +++++++++++++++++-- .../weights/runtime_parachains_coretime.rs | 57 ++++++++------- polkadot/runtime/westend/src/xcm_config.rs | 3 + polkadot/xcm/src/v2/mod.rs | 5 +- prdoc/pr_3319.prdoc | 11 +++ 5 files changed, 114 insertions(+), 31 deletions(-) create mode 100644 prdoc/pr_3319.prdoc diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 03bd5b18b5b..bbb010f60bf 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -61,12 +61,16 @@ use runtime_common::{ impls::{ LocatableAssetConverter, ToAuthor, VersionedLocatableAsset, VersionedLocationConverter, }, - paras_registrar, paras_sudo_wrapper, prod_or_fast, slots, BalanceToU256, BlockHashCount, - BlockLength, CurrencyToVote, SlowAdjustingFeeUpdate, U256ToBalance, + paras_registrar, paras_sudo_wrapper, prod_or_fast, slots, + traits::Leaser, + BalanceToU256, BlockHashCount, BlockLength, CurrencyToVote, SlowAdjustingFeeUpdate, + U256ToBalance, }; use runtime_parachains::{ + assigner_coretime as parachains_assigner_coretime, + assigner_on_demand as parachains_assigner_on_demand, assigner_parachains as parachains_assigner_parachains, - configuration as parachains_configuration, disputes as parachains_disputes, + configuration as parachains_configuration, coretime, disputes as parachains_disputes, disputes::slashing as parachains_slashing, dmp as parachains_dmp, hrmp as parachains_hrmp, inclusion as parachains_inclusion, inclusion::{AggregateMessageOrigin, UmpQueueId}, @@ -147,7 +151,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("westend"), impl_name: create_runtime_str!("parity-westend"), authoring_version: 2, - spec_version: 1_007_000, + spec_version: 1_007_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 24, @@ -1143,7 +1147,7 @@ impl parachains_paras::Config for Runtime { type QueueFootprinter = ParaInclusion; type NextSessionRotation = Babe; type OnNewHead = (); - type AssignCoretime = (); + type AssignCoretime = CoretimeAssignmentProvider; } parameter_types! { @@ -1212,20 +1216,42 @@ impl parachains_paras_inherent::Config for Runtime { impl parachains_scheduler::Config for Runtime { // If you change this, make sure the `Assignment` type of the new provider is binary compatible, // otherwise provide a migration. - type AssignmentProvider = ParachainsAssignmentProvider; + type AssignmentProvider = CoretimeAssignmentProvider; } parameter_types! { pub const BrokerId: u32 = BROKER_ID; } +impl coretime::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type BrokerId = BrokerId; + type WeightInfo = weights::runtime_parachains_coretime::WeightInfo; + type SendXcm = crate::xcm_config::XcmRouter; +} + +parameter_types! { + pub const OnDemandTrafficDefaultValue: FixedU128 = FixedU128::from_u32(1); +} + +impl parachains_assigner_on_demand::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type TrafficDefaultValue = OnDemandTrafficDefaultValue; + type WeightInfo = weights::runtime_parachains_assigner_on_demand::WeightInfo; +} + impl parachains_assigner_parachains::Config for Runtime {} +impl parachains_assigner_coretime::Config for Runtime {} + impl parachains_initializer::Config for Runtime { type Randomness = pallet_babe::RandomnessFromOneEpochAgo; type ForceOrigin = EnsureRoot; type WeightInfo = weights::runtime_parachains_initializer::WeightInfo; - type CoretimeOnNewSession = (); + type CoretimeOnNewSession = Coretime; } impl paras_sudo_wrapper::Config for Runtime {} @@ -1480,6 +1506,8 @@ construct_runtime! { ParasDisputes: parachains_disputes = 53, ParasSlashing: parachains_slashing = 54, ParachainsAssignmentProvider: parachains_assigner_parachains = 55, + OnDemandAssignmentProvider: parachains_assigner_on_demand = 56, + CoretimeAssignmentProvider: parachains_assigner_coretime = 57, // Parachain Onboarding Pallets. Start indices at 60 to leave room. Registrar: paras_registrar = 60, @@ -1488,6 +1516,7 @@ construct_runtime! { Auctions: auctions = 63, Crowdloan: crowdloan = 64, AssignedSlots: assigned_slots = 65, + Coretime: coretime = 66, // Pallet for sending XCM. XcmPallet: pallet_xcm = 99, @@ -1555,6 +1584,24 @@ pub mod migrations { #[cfg(feature = "try-runtime")] use sp_core::crypto::ByteArray; + pub struct GetLegacyLeaseImpl; + impl coretime::migration::GetLegacyLease for GetLegacyLeaseImpl { + fn get_parachain_lease_in_blocks(para: ParaId) -> Option { + let now = frame_system::Pallet::::block_number(); + let lease = slots::Pallet::::lease(para); + if lease.is_empty() { + return None + } + // Lease not yet started, ignore: + if lease.iter().any(Option::is_none) { + return None + } + let (index, _) = + as Leaser>::lease_period_index(now)?; + Some(index.saturating_add(lease.len() as u32).saturating_mul(LeasePeriod::get())) + } + } + parameter_types! { pub const ImOnlinePalletName: &'static str = "ImOnline"; } @@ -1658,6 +1705,12 @@ pub mod migrations { parachains_configuration::migration::v11::MigrateToV11, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, + // Migrate from legacy lease to coretime. Needs to run after configuration v11 + coretime::migration::MigrateToCoretime< + Runtime, + crate::xcm_config::XcmRouter, + GetLegacyLeaseImpl, + >, ); } @@ -1696,6 +1749,8 @@ mod benches { [runtime_parachains::initializer, Initializer] [runtime_parachains::paras, Paras] [runtime_parachains::paras_inherent, ParaInherent] + [runtime_parachains::assigner_on_demand, OnDemandAssignmentProvider] + [runtime_parachains::coretime, Coretime] // Substrate [pallet_bags_list, VoterList] [pallet_balances, Balances] diff --git a/polkadot/runtime/westend/src/weights/runtime_parachains_coretime.rs b/polkadot/runtime/westend/src/weights/runtime_parachains_coretime.rs index d9f2d45207b..aa65a2e9034 100644 --- a/polkadot/runtime/westend/src/weights/runtime_parachains_coretime.rs +++ b/polkadot/runtime/westend/src/weights/runtime_parachains_coretime.rs @@ -16,11 +16,11 @@ //! Autogenerated weights for `runtime_parachains::coretime` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-12-01, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-15, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-r43aesjn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot @@ -32,10 +32,10 @@ // --wasm-execution=compiled // --heap-pages=4096 // --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=runtime_common::coretime -// --chain=rococo-dev +// --pallet=runtime_parachains::coretime +// --chain=westend-dev // --header=./polkadot/file_header.txt -// --output=./polkadot/runtime/rococo/src/weights/ +// --output=./polkadot/runtime/westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -45,28 +45,39 @@ use frame_support::{traits::Get, weights::Weight}; use core::marker::PhantomData; -use runtime_parachains::configuration::{self, WeightInfo as ConfigWeightInfo}; - -/// Weight functions for `runtime_common::coretime`. +/// Weight functions for `runtime_parachains::coretime`. pub struct WeightInfo(PhantomData); -impl runtime_parachains::coretime::WeightInfo for WeightInfo { +impl runtime_parachains::coretime::WeightInfo for WeightInfo { + /// Storage: `Configuration::PendingConfigs` (r:1 w:1) + /// Proof: `Configuration::PendingConfigs` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Configuration::BypassConsistencyCheck` (r:1 w:0) + /// Proof: `Configuration::BypassConsistencyCheck` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParasShared::CurrentSessionIndex` (r:1 w:0) + /// Proof: `ParasShared::CurrentSessionIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn request_core_count() -> Weight { - ::WeightInfo::set_config_with_u32() + // Proof Size summary in bytes: + // Measured: `151` + // Estimated: `1636` + // Minimum execution time: 7_486_000 picoseconds. + Weight::from_parts(7_889_000, 0) + .saturating_add(Weight::from_parts(0, 1636)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `CoreTimeAssignmentProvider::CoreDescriptors` (r:1 w:1) - /// Proof: `CoreTimeAssignmentProvider::CoreDescriptors` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `CoreTimeAssignmentProvider::CoreSchedules` (r:0 w:1) - /// Proof: `CoreTimeAssignmentProvider::CoreSchedules` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `CoretimeAssignmentProvider::CoreDescriptors` (r:1 w:1) + /// Proof: `CoretimeAssignmentProvider::CoreDescriptors` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `CoretimeAssignmentProvider::CoreSchedules` (r:0 w:1) + /// Proof: `CoretimeAssignmentProvider::CoreSchedules` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `s` is `[1, 100]`. fn assign_core(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `76` - // Estimated: `3541` - // Minimum execution time: 6_275_000 picoseconds. - Weight::from_parts(6_883_543, 0) - .saturating_add(Weight::from_parts(0, 3541)) - // Standard Error: 202 - .saturating_add(Weight::from_parts(15_028, 0).saturating_mul(s.into())) + // Measured: `147` + // Estimated: `3612` + // Minimum execution time: 9_409_000 picoseconds. + Weight::from_parts(10_177_115, 0) + .saturating_add(Weight::from_parts(0, 3612)) + // Standard Error: 259 + .saturating_add(Weight::from_parts(13_932, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } diff --git a/polkadot/runtime/westend/src/xcm_config.rs b/polkadot/runtime/westend/src/xcm_config.rs index a11c52156c8..06124d896fb 100644 --- a/polkadot/runtime/westend/src/xcm_config.rs +++ b/polkadot/runtime/westend/src/xcm_config.rs @@ -115,11 +115,13 @@ parameter_types! { pub Collectives: Location = Parachain(COLLECTIVES_ID).into_location(); pub BridgeHub: Location = Parachain(BRIDGE_HUB_ID).into_location(); pub People: Location = Parachain(PEOPLE_ID).into_location(); + pub Broker: Location = Parachain(BROKER_ID).into_location(); pub Wnd: AssetFilter = Wild(AllOf { fun: WildFungible, id: AssetId(TokenLocation::get()) }); pub WndForAssetHub: (AssetFilter, Location) = (Wnd::get(), AssetHub::get()); pub WndForCollectives: (AssetFilter, Location) = (Wnd::get(), Collectives::get()); pub WndForBridgeHub: (AssetFilter, Location) = (Wnd::get(), BridgeHub::get()); pub WndForPeople: (AssetFilter, Location) = (Wnd::get(), People::get()); + pub WndForBroker: (AssetFilter, Location) = (Wnd::get(), Broker::get()); pub MaxInstructions: u32 = 100; pub MaxAssetsIntoHolding: u32 = 64; } @@ -129,6 +131,7 @@ pub type TrustedTeleporters = ( xcm_builder::Case, xcm_builder::Case, xcm_builder::Case, + xcm_builder::Case, ); pub struct OnlyParachains; diff --git a/polkadot/xcm/src/v2/mod.rs b/polkadot/xcm/src/v2/mod.rs index 188b7f0b5c9..347f3f2c292 100644 --- a/polkadot/xcm/src/v2/mod.rs +++ b/polkadot/xcm/src/v2/mod.rs @@ -1134,7 +1134,10 @@ impl TryFrom> for Instruction Self::UnsubscribeVersion, - _ => return Err(()), + i => { + log::debug!(target: "xcm::v3tov2", "`{i:?}` not supported by v2"); + return Err(()); + }, }) } } diff --git a/prdoc/pr_3319.prdoc b/prdoc/pr_3319.prdoc new file mode 100644 index 00000000000..b84ec25a22e --- /dev/null +++ b/prdoc/pr_3319.prdoc @@ -0,0 +1,11 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Add Coretime to Westend + +doc: + - audience: Runtime User + description: | + Add the on demand and coretime assigners and migrate from legacy parachain auctions to coretime. + +crates: [ ] -- GitLab From ca382f32033d8350d49934b30680d8c30dd9bdfc Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Mon, 19 Feb 2024 16:29:47 +0100 Subject: [PATCH 090/133] Contracts: xcm host fn fixes (#3086) ## Xcm changes: - Fix `pallet_xcm::execute`, move the logic into The `ExecuteController` so it can be shared with anything that implement that trait. - Make `ExecuteController::execute` retursn `DispatchErrorWithPostInfo` instead of `DispatchError`, so that we don't charge the full `max_weight` provided if the execution is incomplete (useful for force_batch or contracts calls) - Fix docstring for `pallet_xcm::execute`, to reflect the changes from #2405 - Update the signature for `ExecuteController::execute`, we don't need to return the `Outcome` anymore since we only care about `Outcome::Complete` ## Contracts changes: - Update host fn `xcm_exexute`, we don't need to write the `Outcome` to the sandbox memory anymore. This was also not charged as well before so it if fixes this too. - One of the issue was that the dry_run of a contract that call `xcm_execute` would exhaust the `gas_limit`. This is because `XcmExecuteController::execute` takes a `max_weight` argument, and since we don't want the user to specify it manually we were passing everything left by pre-charghing `ctx.ext.gas_meter().gas_left()` - To fix it I added a `fn influence_lowest_limit` on the `Token` trait and make it return false for `RuntimeCost::XcmExecute`. - Got rid of the `RuntimeToken` indirection, we can just use `RuntimeCost` directly. --------- Co-authored-by: command-bot <> --- polkadot/xcm/pallet-xcm/src/lib.rs | 60 +++++++----- polkadot/xcm/pallet-xcm/src/tests/mod.rs | 38 ++++---- polkadot/xcm/xcm-builder/src/controller.rs | 15 ++- .../fixtures/contracts/xcm_execute.rs | 11 ++- .../frame/contracts/mock-network/src/lib.rs | 3 +- .../frame/contracts/mock-network/src/tests.rs | 71 ++++++++++---- substrate/frame/contracts/src/exec.rs | 7 ++ substrate/frame/contracts/src/gas.rs | 9 +- substrate/frame/contracts/src/wasm/mod.rs | 4 + substrate/frame/contracts/src/wasm/runtime.rs | 95 ++++++++----------- substrate/frame/contracts/uapi/src/host.rs | 3 +- .../frame/contracts/uapi/src/host/riscv32.rs | 2 +- .../frame/contracts/uapi/src/host/wasm32.rs | 7 +- 13 files changed, 192 insertions(+), 133 deletions(-) diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs index 586c275ae98..5e1a3e55f9b 100644 --- a/polkadot/xcm/pallet-xcm/src/lib.rs +++ b/polkadot/xcm/pallet-xcm/src/lib.rs @@ -29,7 +29,7 @@ pub mod migration; use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; use frame_support::{ - dispatch::GetDispatchInfo, + dispatch::{DispatchErrorWithPostInfo, GetDispatchInfo, WithPostDispatchInfo}, pallet_prelude::*, traits::{ Contains, ContainsPair, Currency, Defensive, EnsureOrigin, Get, LockableCurrency, @@ -291,22 +291,38 @@ pub mod pallet { origin: OriginFor, message: Box::RuntimeCall>>, max_weight: Weight, - ) -> Result { - let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; - let mut hash = message.using_encoded(sp_io::hashing::blake2_256); - let message = (*message).try_into().map_err(|()| Error::::BadVersion)?; - let value = (origin_location, message); - ensure!(T::XcmExecuteFilter::contains(&value), Error::::Filtered); - let (origin_location, message) = value; - let outcome = T::XcmExecutor::prepare_and_execute( - origin_location, - message, - &mut hash, - max_weight, - max_weight, - ); + ) -> Result { + log::trace!(target: "xcm::pallet_xcm::execute", "message {:?}, max_weight {:?}", message, max_weight); + let outcome = (|| { + let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; + let mut hash = message.using_encoded(sp_io::hashing::blake2_256); + let message = (*message).try_into().map_err(|()| Error::::BadVersion)?; + let value = (origin_location, message); + ensure!(T::XcmExecuteFilter::contains(&value), Error::::Filtered); + let (origin_location, message) = value; + Ok(T::XcmExecutor::prepare_and_execute( + origin_location, + message, + &mut hash, + max_weight, + max_weight, + )) + })() + .map_err(|e: DispatchError| { + e.with_weight(::execute()) + })?; + Self::deposit_event(Event::Attempted { outcome: outcome.clone() }); - Ok(outcome) + let weight_used = outcome.weight_used(); + outcome.ensure_complete().map_err(|error| { + log::error!(target: "xcm::pallet_xcm::execute", "XCM execution failed with error {:?}", error); + Error::::LocalExecutionIncomplete.with_weight( + weight_used.saturating_add( + ::execute(), + ), + ) + })?; + Ok(weight_used) } } @@ -1009,9 +1025,6 @@ pub mod pallet { /// No more than `max_weight` will be used in its attempted execution. If this is less than /// the maximum amount of weight that the message could take to be executed, then no /// execution attempt will be made. - /// - /// NOTE: A successful return to this does *not* imply that the `msg` was executed - /// successfully to completion; only that it was attempted. #[pallet::call_index(3)] #[pallet::weight(max_weight.saturating_add(T::WeightInfo::execute()))] pub fn execute( @@ -1019,13 +1032,8 @@ pub mod pallet { message: Box::RuntimeCall>>, max_weight: Weight, ) -> DispatchResultWithPostInfo { - log::trace!(target: "xcm::pallet_xcm::execute", "message {:?}, max_weight {:?}", message, max_weight); - let outcome = >::execute(origin, message, max_weight)?; - let weight_used = outcome.weight_used(); - outcome.ensure_complete().map_err(|error| { - log::error!(target: "xcm::pallet_xcm::execute", "XCM execution failed with error {:?}", error); - Error::::LocalExecutionIncomplete - })?; + let weight_used = + >::execute(origin, message, max_weight)?; Ok(Some(weight_used.saturating_add(T::WeightInfo::execute())).into()) } diff --git a/polkadot/xcm/pallet-xcm/src/tests/mod.rs b/polkadot/xcm/pallet-xcm/src/tests/mod.rs index e5ac80c3a14..28c7d197443 100644 --- a/polkadot/xcm/pallet-xcm/src/tests/mod.rs +++ b/polkadot/xcm/pallet-xcm/src/tests/mod.rs @@ -20,11 +20,12 @@ pub(crate) mod assets_transfer; use crate::{ mock::*, pallet::SupportedVersion, AssetTraps, Config, CurrentMigration, Error, - LatestVersionedLocation, Pallet, Queries, QueryStatus, VersionDiscoveryQueue, - VersionMigrationStage, VersionNotifiers, VersionNotifyTargets, WeightInfo, + ExecuteControllerWeightInfo, LatestVersionedLocation, Pallet, Queries, QueryStatus, + VersionDiscoveryQueue, VersionMigrationStage, VersionNotifiers, VersionNotifyTargets, + WeightInfo, }; use frame_support::{ - assert_noop, assert_ok, + assert_err_ignore_postinfo, assert_noop, assert_ok, traits::{Currency, Hooks}, weights::Weight, }; @@ -450,19 +451,19 @@ fn trapped_assets_can_be_claimed() { assert_eq!(Balances::total_balance(&BOB), INITIAL_BALANCE + SEND_AMOUNT); assert_eq!(AssetTraps::::iter().collect::>(), vec![]); - let weight = BaseXcmWeight::get() * 3; - assert_ok!(>::execute( - RuntimeOrigin::signed(ALICE), - Box::new(VersionedXcm::from(Xcm(vec![ - ClaimAsset { assets: (Here, SEND_AMOUNT).into(), ticket: Here.into() }, - buy_execution((Here, SEND_AMOUNT)), - DepositAsset { assets: AllCounted(1).into(), beneficiary: dest }, - ]))), - weight - )); - let outcome = - Outcome::Incomplete { used: BaseXcmWeight::get(), error: XcmError::UnknownClaim }; - assert_eq!(last_event(), RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome })); + // Can't claim twice. + assert_err_ignore_postinfo!( + XcmPallet::execute( + RuntimeOrigin::signed(ALICE), + Box::new(VersionedXcm::from(Xcm(vec![ + ClaimAsset { assets: (Here, SEND_AMOUNT).into(), ticket: Here.into() }, + buy_execution((Here, SEND_AMOUNT)), + DepositAsset { assets: AllCounted(1).into(), beneficiary: dest }, + ]))), + weight + ), + Error::::LocalExecutionIncomplete + ); }); } @@ -495,11 +496,14 @@ fn incomplete_execute_reverts_side_effects() { // all effects are reverted and balances unchanged for either sender or receiver assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); assert_eq!(Balances::total_balance(&BOB), INITIAL_BALANCE); + assert_eq!( result, Err(sp_runtime::DispatchErrorWithPostInfo { post_info: frame_support::dispatch::PostDispatchInfo { - actual_weight: None, + actual_weight: Some( + as ExecuteControllerWeightInfo>::execute() + weight + ), pays_fee: frame_support::dispatch::Pays::Yes, }, error: sp_runtime::DispatchError::Module(sp_runtime::ModuleError { diff --git a/polkadot/xcm/xcm-builder/src/controller.rs b/polkadot/xcm/xcm-builder/src/controller.rs index 8ead18b5bd7..ba2b1fb44b8 100644 --- a/polkadot/xcm/xcm-builder/src/controller.rs +++ b/polkadot/xcm/xcm-builder/src/controller.rs @@ -18,7 +18,10 @@ //! Controller traits defined in this module are high-level traits that will rely on other traits //! from `xcm-executor` to perform their tasks. -use frame_support::pallet_prelude::DispatchError; +use frame_support::{ + dispatch::{DispatchErrorWithPostInfo, WithPostDispatchInfo}, + pallet_prelude::DispatchError, +}; use sp_std::boxed::Box; use xcm::prelude::*; pub use xcm_executor::traits::QueryHandler; @@ -52,7 +55,8 @@ pub trait ExecuteController { /// Weight information for ExecuteController functions. type WeightInfo: ExecuteControllerWeightInfo; - /// Attempt to execute an XCM locally, and return the outcome. + /// Attempt to execute an XCM locally, returns Ok with the weight consumed if the execution + /// complete successfully, Err otherwise. /// /// # Parameters /// @@ -63,7 +67,7 @@ pub trait ExecuteController { origin: Origin, message: Box>, max_weight: Weight, - ) -> Result; + ) -> Result; } /// Weight functions needed for [`SendController`]. @@ -137,8 +141,9 @@ impl ExecuteController for () { _origin: Origin, _message: Box>, _max_weight: Weight, - ) -> Result { - Ok(Outcome::Error { error: XcmError::Unimplemented }) + ) -> Result { + Err(DispatchError::Other("ExecuteController::execute not implemented") + .with_weight(Weight::zero())) } } diff --git a/substrate/frame/contracts/fixtures/contracts/xcm_execute.rs b/substrate/frame/contracts/fixtures/contracts/xcm_execute.rs index 09d0b6cf972..1d570ffead7 100644 --- a/substrate/frame/contracts/fixtures/contracts/xcm_execute.rs +++ b/substrate/frame/contracts/fixtures/contracts/xcm_execute.rs @@ -30,10 +30,11 @@ pub extern "C" fn deploy() {} pub extern "C" fn call() { input!(512, msg: [u8],); - let mut outcome = [0u8; 512]; - let outcome = &mut &mut outcome[..]; - #[allow(deprecated)] - api::xcm_execute(msg, outcome).unwrap(); - api::return_value(uapi::ReturnFlags::empty(), outcome); + let err_code = match api::xcm_execute(msg) { + Ok(_) => 0u32, + Err(code) => code as u32, + }; + + api::return_value(uapi::ReturnFlags::empty(), &err_code.to_le_bytes()); } diff --git a/substrate/frame/contracts/mock-network/src/lib.rs b/substrate/frame/contracts/mock-network/src/lib.rs index eea9dde062c..8a17a3f2fa7 100644 --- a/substrate/frame/contracts/mock-network/src/lib.rs +++ b/substrate/frame/contracts/mock-network/src/lib.rs @@ -26,7 +26,8 @@ use crate::primitives::{AccountId, UNITS}; use sp_runtime::BuildStorage; use xcm::latest::prelude::*; use xcm_executor::traits::ConvertLocation; -use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain, TestExt}; +pub use xcm_simulator::TestExt; +use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain}; // Accounts pub const ADMIN: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([0u8; 32]); diff --git a/substrate/frame/contracts/mock-network/src/tests.rs b/substrate/frame/contracts/mock-network/src/tests.rs index c25373bcbe3..d22221fe8ee 100644 --- a/substrate/frame/contracts/mock-network/src/tests.rs +++ b/substrate/frame/contracts/mock-network/src/tests.rs @@ -21,7 +21,6 @@ use crate::{ primitives::{AccountId, CENTS}, relay_chain, MockNet, ParaA, ParachainBalances, Relay, ALICE, BOB, INITIAL_BALANCE, }; -use assert_matches::assert_matches; use codec::{Decode, Encode}; use frame_support::{ assert_err, @@ -31,11 +30,18 @@ use frame_support::{ use pallet_balances::{BalanceLock, Reasons}; use pallet_contracts::{Code, CollectEvents, DebugInfo, Determinism}; use pallet_contracts_fixtures::compile_module; +use pallet_contracts_uapi::ReturnErrorCode; use xcm::{v4::prelude::*, VersionedLocation, VersionedXcm}; use xcm_simulator::TestExt; type ParachainContracts = pallet_contracts::Pallet; +macro_rules! assert_return_code { + ( $x:expr , $y:expr $(,)? ) => {{ + assert_eq!(u32::from_le_bytes($x.data[..].try_into().unwrap()), $y as u32); + }}; +} + /// Instantiate the tests contract, and fund it with some balance and assets. fn instantiate_test_contract(name: &str) -> AccountId { let (wasm, _) = compile_module::(name).unwrap(); @@ -100,19 +106,57 @@ fn test_xcm_execute() { DebugInfo::UnsafeDebug, CollectEvents::UnsafeCollect, Determinism::Enforced, - ) - .result - .unwrap(); + ); - let mut data = &result.data[..]; - let outcome = Outcome::decode(&mut data).expect("Failed to decode xcm_execute Outcome"); - assert_matches!(outcome, Outcome::Complete { .. }); + assert_eq!(result.gas_consumed, result.gas_required); + assert_return_code!(&result.result.unwrap(), ReturnErrorCode::Success); // Check if the funds are subtracted from the account of Alice and added to the account of // Bob. let initial = INITIAL_BALANCE; - assert_eq!(parachain::Assets::balance(0, contract_addr), initial); assert_eq!(ParachainBalances::free_balance(BOB), initial + amount); + assert_eq!(ParachainBalances::free_balance(&contract_addr), initial - amount); + }); +} + +#[test] +fn test_xcm_execute_incomplete() { + MockNet::reset(); + + let contract_addr = instantiate_test_contract("xcm_execute"); + let amount = 10 * CENTS; + + // Execute XCM instructions through the contract. + ParaA::execute_with(|| { + // The XCM used to transfer funds to Bob. + let message: Xcm<()> = Xcm(vec![ + WithdrawAsset(vec![(Here, amount).into()].into()), + // This will fail as the contract does not have enough balance to complete both + // withdrawals. + WithdrawAsset(vec![(Here, INITIAL_BALANCE).into()].into()), + DepositAsset { + assets: All.into(), + beneficiary: AccountId32 { network: None, id: BOB.clone().into() }.into(), + }, + ]); + + let result = ParachainContracts::bare_call( + ALICE, + contract_addr.clone(), + 0, + Weight::MAX, + None, + VersionedXcm::V4(message).encode(), + DebugInfo::UnsafeDebug, + CollectEvents::UnsafeCollect, + Determinism::Enforced, + ); + + assert_eq!(result.gas_consumed, result.gas_required); + assert_return_code!(&result.result.unwrap(), ReturnErrorCode::XcmExecutionFailed); + + assert_eq!(ParachainBalances::free_balance(BOB), INITIAL_BALANCE); + assert_eq!(ParachainBalances::free_balance(&contract_addr), INITIAL_BALANCE - amount); }); } @@ -182,17 +226,10 @@ fn test_xcm_execute_reentrant_call() { DebugInfo::UnsafeDebug, CollectEvents::UnsafeCollect, Determinism::Enforced, - ) - .result - .unwrap(); - - let mut data = &result.data[..]; - let outcome = Outcome::decode(&mut data).expect("Failed to decode xcm_execute Outcome"); - assert_matches!( - outcome, - Outcome::Incomplete { used: _, error: XcmError::ExpectationFalse } ); + assert_return_code!(&result.result.unwrap(), ReturnErrorCode::XcmExecutionFailed); + // Funds should not change hands as the XCM transact failed. assert_eq!(ParachainBalances::free_balance(BOB), INITIAL_BALANCE); }); diff --git a/substrate/frame/contracts/src/exec.rs b/substrate/frame/contracts/src/exec.rs index 6f7131f3ea4..79220021efe 100644 --- a/substrate/frame/contracts/src/exec.rs +++ b/substrate/frame/contracts/src/exec.rs @@ -287,6 +287,9 @@ pub trait Ext: sealing::Sealed { /// Returns `true` if debug message recording is enabled. Otherwise `false` is returned. fn append_debug_buffer(&mut self, msg: &str) -> bool; + /// Returns `true` if debug message recording is enabled. Otherwise `false` is returned. + fn debug_buffer_enabled(&self) -> bool; + /// Call some dispatchable and return the result. fn call_runtime(&self, call: ::RuntimeCall) -> DispatchResultWithPostInfo; @@ -1429,6 +1432,10 @@ where self.top_frame_mut().nested_storage.charge(diff) } + fn debug_buffer_enabled(&self) -> bool { + self.debug_message.is_some() + } + fn append_debug_buffer(&mut self, msg: &str) -> bool { if let Some(buffer) = &mut self.debug_message { buffer diff --git a/substrate/frame/contracts/src/gas.rs b/substrate/frame/contracts/src/gas.rs index 11292dc03f0..9271b615d00 100644 --- a/substrate/frame/contracts/src/gas.rs +++ b/substrate/frame/contracts/src/gas.rs @@ -63,6 +63,11 @@ pub trait Token: Copy + Clone + TestAuxiliaries { /// while calculating the amount. In this case it is ok to use saturating operations /// since on overflow they will return `max_value` which should consume all gas. fn weight(&self) -> Weight; + + /// Returns true if this token is expected to influence the lowest gas limit. + fn influence_lowest_gas_limit(&self) -> bool { + true + } } /// A wrapper around a type-erased trait object of what used to be a `Token`. @@ -160,7 +165,9 @@ impl GasMeter { /// This is when a maximum a priori amount was charged and then should be partially /// refunded to match the actual amount. pub fn adjust_gas>(&mut self, charged_amount: ChargedAmount, token: Tok) { - self.gas_left_lowest = self.gas_left_lowest(); + if token.influence_lowest_gas_limit() { + self.gas_left_lowest = self.gas_left_lowest(); + } let adjustment = charged_amount.0.saturating_sub(token.weight()); self.gas_left = self.gas_left.saturating_add(adjustment).min(self.gas_limit); } diff --git a/substrate/frame/contracts/src/wasm/mod.rs b/substrate/frame/contracts/src/wasm/mod.rs index d00ec2e4752..1c7395ec3bf 100644 --- a/substrate/frame/contracts/src/wasm/mod.rs +++ b/substrate/frame/contracts/src/wasm/mod.rs @@ -687,6 +687,10 @@ mod tests { &mut self.gas_meter } fn charge_storage(&mut self, _diff: &crate::storage::meter::Diff) {} + + fn debug_buffer_enabled(&self) -> bool { + true + } fn append_debug_buffer(&mut self, msg: &str) -> bool { self.debug_buffer.extend(msg.as_bytes()); true diff --git a/substrate/frame/contracts/src/wasm/runtime.rs b/substrate/frame/contracts/src/wasm/runtime.rs index 3af0d04a3ad..74b19910c37 100644 --- a/substrate/frame/contracts/src/wasm/runtime.rs +++ b/substrate/frame/contracts/src/wasm/runtime.rs @@ -21,7 +21,6 @@ use crate::{ exec::{ExecError, ExecResult, Ext, Key, TopicOf}, gas::{ChargedAmount, Token}, primitives::ExecReturnValue, - schedule::HostFnWeights, BalanceOf, CodeHash, Config, DebugBufferVec, Error, SENTINEL, }; use codec::{Decode, DecodeLimit, Encode, MaxEncodedLen}; @@ -235,6 +234,8 @@ pub enum RuntimeCosts { ChainExtension(Weight), /// Weight charged for calling into the runtime. CallRuntime(Weight), + /// Weight charged for calling xcm_execute. + CallXcmExecute(Weight), /// Weight of calling `seal_set_code_hash` SetCodeHash, /// Weight of calling `ecdsa_to_eth_address` @@ -251,10 +252,18 @@ pub enum RuntimeCosts { RemoveDelegateDependency, } -impl RuntimeCosts { - fn token(&self, s: &HostFnWeights) -> RuntimeToken { +impl Token for RuntimeCosts { + fn influence_lowest_gas_limit(&self) -> bool { + match self { + &Self::CallXcmExecute(_) => false, + _ => true, + } + } + + fn weight(&self) -> Weight { + let s = T::Schedule::get().host_fn_weights; use self::RuntimeCosts::*; - let weight = match *self { + match *self { CopyFromContract(len) => s.return_per_byte.saturating_mul(len.into()), CopyToContract(len) => s.input_per_byte.saturating_mul(len.into()), Caller => s.caller, @@ -323,8 +332,7 @@ impl RuntimeCosts { Sr25519Verify(len) => s .sr25519_verify .saturating_add(s.sr25519_verify_per_byte.saturating_mul(len.into())), - ChainExtension(weight) => weight, - CallRuntime(weight) => weight, + ChainExtension(weight) | CallRuntime(weight) | CallXcmExecute(weight) => weight, SetCodeHash => s.set_code_hash, EcdsaToEthAddress => s.ecdsa_to_eth_address, ReentrantCount => s.reentrance_count, @@ -332,11 +340,6 @@ impl RuntimeCosts { InstantationNonce => s.instantiation_nonce, AddDelegateDependency => s.add_delegate_dependency, RemoveDelegateDependency => s.remove_delegate_dependency, - }; - RuntimeToken { - #[cfg(test)] - _created_from: *self, - weight, } } } @@ -347,25 +350,10 @@ impl RuntimeCosts { /// a function won't work out. macro_rules! charge_gas { ($runtime:expr, $costs:expr) => {{ - let token = $costs.token(&$runtime.ext.schedule().host_fn_weights); - $runtime.ext.gas_meter_mut().charge(token) + $runtime.ext.gas_meter_mut().charge($costs) }}; } -#[cfg_attr(test, derive(Debug, PartialEq, Eq))] -#[derive(Copy, Clone)] -struct RuntimeToken { - #[cfg(test)] - _created_from: RuntimeCosts, - weight: Weight, -} - -impl Token for RuntimeToken { - fn weight(&self) -> Weight { - self.weight - } -} - /// The kind of call that should be performed. enum CallType { /// Execute another instantiated contract @@ -506,28 +494,25 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { /// This is when a maximum a priori amount was charged and then should be partially /// refunded to match the actual amount. pub fn adjust_gas(&mut self, charged: ChargedAmount, actual_costs: RuntimeCosts) { - let token = actual_costs.token(&self.ext.schedule().host_fn_weights); - self.ext.gas_meter_mut().adjust_gas(charged, token); + self.ext.gas_meter_mut().adjust_gas(charged, actual_costs); } /// Charge, Run and adjust gas, for executing the given dispatchable. - fn call_dispatchable< - ErrorReturnCode: Get, - F: FnOnce(&mut Self) -> DispatchResultWithPostInfo, - >( + fn call_dispatchable>( &mut self, dispatch_info: DispatchInfo, - run: F, + runtime_cost: impl Fn(Weight) -> RuntimeCosts, + run: impl FnOnce(&mut Self) -> DispatchResultWithPostInfo, ) -> Result { use frame_support::dispatch::extract_actual_weight; - let charged = self.charge_gas(RuntimeCosts::CallRuntime(dispatch_info.weight))?; + let charged = self.charge_gas(runtime_cost(dispatch_info.weight))?; let result = run(self); let actual_weight = extract_actual_weight(&result, &dispatch_info); - self.adjust_gas(charged, RuntimeCosts::CallRuntime(actual_weight)); + self.adjust_gas(charged, runtime_cost(actual_weight)); match result { Ok(_) => Ok(ReturnErrorCode::Success), Err(e) => { - if self.ext.append_debug_buffer("") { + if self.ext.debug_buffer_enabled() { self.ext.append_debug_buffer("call failed with: "); self.ext.append_debug_buffer(e.into()); }; @@ -2113,9 +2098,11 @@ pub mod env { ctx.charge_gas(RuntimeCosts::CopyFromContract(call_len))?; let call: ::RuntimeCall = ctx.read_sandbox_memory_as_unbounded(memory, call_ptr, call_len)?; - ctx.call_dispatchable::(call.get_dispatch_info(), |ctx| { - ctx.ext.call_runtime(call) - }) + ctx.call_dispatchable::( + call.get_dispatch_info(), + RuntimeCosts::CallRuntime, + |ctx| ctx.ext.call_runtime(call), + ) } /// Execute an XCM program locally, using the contract's address as the origin. @@ -2126,7 +2113,6 @@ pub mod env { memory: _, msg_ptr: u32, msg_len: u32, - output_ptr: u32, ) -> Result { use frame_support::dispatch::DispatchInfo; use xcm::VersionedXcm; @@ -2135,26 +2121,27 @@ pub mod env { ctx.charge_gas(RuntimeCosts::CopyFromContract(msg_len))?; let message: VersionedXcm> = ctx.read_sandbox_memory_as_unbounded(memory, msg_ptr, msg_len)?; + ensure_executable::(&message)?; let execute_weight = <::Xcm as ExecuteController<_, _>>::WeightInfo::execute(); let weight = ctx.ext.gas_meter().gas_left().max(execute_weight); let dispatch_info = DispatchInfo { weight, ..Default::default() }; - ensure_executable::(&message)?; - ctx.call_dispatchable::(dispatch_info, |ctx| { - let origin = crate::RawOrigin::Signed(ctx.ext.address().clone()).into(); - let outcome = <::Xcm>::execute( - origin, - Box::new(message), - weight.saturating_sub(execute_weight), - )?; + ctx.call_dispatchable::( + dispatch_info, + RuntimeCosts::CallXcmExecute, + |ctx| { + let origin = crate::RawOrigin::Signed(ctx.ext.address().clone()).into(); + let weight_used = <::Xcm>::execute( + origin, + Box::new(message), + weight.saturating_sub(execute_weight), + )?; - ctx.write_sandbox_memory(memory, output_ptr, &outcome.encode())?; - let pre_dispatch_weight = - <::Xcm as ExecuteController<_, _>>::WeightInfo::execute(); - Ok(Some(outcome.weight_used().saturating_add(pre_dispatch_weight)).into()) - }) + Ok(Some(weight_used.saturating_add(execute_weight)).into()) + }, + ) } /// Send an XCM program from the contract to the specified destination. diff --git a/substrate/frame/contracts/uapi/src/host.rs b/substrate/frame/contracts/uapi/src/host.rs index c8b9ae8b2de..7894dc3a210 100644 --- a/substrate/frame/contracts/uapi/src/host.rs +++ b/substrate/frame/contracts/uapi/src/host.rs @@ -792,7 +792,7 @@ pub trait HostFn { #[deprecated( note = "Unstable function. Behaviour can change without further notice. Use only for testing." )] - fn xcm_execute(msg: &[u8], output: &mut &mut [u8]) -> Result; + fn xcm_execute(msg: &[u8]) -> Result; /// Send an XCM program from the contract to the specified destination. /// This is equivalent to dispatching `pallet_xcm::send` through `call_runtime`, except that @@ -804,7 +804,6 @@ pub trait HostFn { /// traps otherwise. /// - `msg`: The message, should be decodable as a [VersionedXcm](https://paritytech.github.io/polkadot-sdk/master/staging_xcm/enum.VersionedXcm.html), /// traps otherwise. - /// - `output`: A reference to the output data buffer to write the [XcmHash](https://paritytech.github.io/polkadot-sdk/master/staging_xcm/v3/type.XcmHash.html) /// /// # Return /// diff --git a/substrate/frame/contracts/uapi/src/host/riscv32.rs b/substrate/frame/contracts/uapi/src/host/riscv32.rs index b1934cc469e..dc1c48308df 100644 --- a/substrate/frame/contracts/uapi/src/host/riscv32.rs +++ b/substrate/frame/contracts/uapi/src/host/riscv32.rs @@ -297,7 +297,7 @@ impl HostFn for HostFnImpl { todo!() } - fn xcm_execute(msg: &[u8], output: &mut &mut [u8]) -> Result { + fn xcm_execute(msg: &[u8]) -> Result { todo!() } diff --git a/substrate/frame/contracts/uapi/src/host/wasm32.rs b/substrate/frame/contracts/uapi/src/host/wasm32.rs index 77cf22891e2..3df3abeb7dc 100644 --- a/substrate/frame/contracts/uapi/src/host/wasm32.rs +++ b/substrate/frame/contracts/uapi/src/host/wasm32.rs @@ -160,7 +160,7 @@ mod sys { pub fn weight_to_fee(gas: u64, output_ptr: *mut u8, output_len_ptr: *mut u32); - pub fn xcm_execute(msg_ptr: *const u8, msg_len: u32, output_ptr: *mut u8) -> ReturnCode; + pub fn xcm_execute(msg_ptr: *const u8, msg_len: u32) -> ReturnCode; pub fn xcm_send( dest_ptr: *const u8, @@ -819,9 +819,8 @@ impl HostFn for HostFnImpl { unsafe { sys::reentrance_count() } } - fn xcm_execute(msg: &[u8], output: &mut &mut [u8]) -> Result { - let ret_code = - unsafe { sys::xcm_execute(msg.as_ptr(), msg.len() as _, output.as_mut_ptr()) }; + fn xcm_execute(msg: &[u8]) -> Result { + let ret_code = unsafe { sys::xcm_execute(msg.as_ptr(), msg.len() as _) }; ret_code.into() } -- GitLab From 5f8b6f38a3406b5ca901b8972860c78300313659 Mon Sep 17 00:00:00 2001 From: girazoki Date: Tue, 20 Feb 2024 15:28:05 +0100 Subject: [PATCH 091/133] implement ConversionToAssetBalance in asset-rate (#2903) Implements the `ConversionToAssetBalance` trait to asset-rate by doing `1/rate*(balance)`. --- prdoc/pr_2903.prdoc | 15 +++++++++++ substrate/frame/asset-rate/src/lib.rs | 34 +++++++++++++++++++++++-- substrate/frame/asset-rate/src/tests.rs | 29 +++++++++++++++++++-- 3 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 prdoc/pr_2903.prdoc diff --git a/prdoc/pr_2903.prdoc b/prdoc/pr_2903.prdoc new file mode 100644 index 00000000000..dd22fa0eea3 --- /dev/null +++ b/prdoc/pr_2903.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Implement `ConversionToAssetBalance` in asset-rate" + +doc: + - audience: Runtime Dev + description: | + Implements the `ConversionToAssetBalance` trait to the asset-rate pallet. + + Previously only the `ConversionFromAssetBalance` trait was implemented, which would allow to convert an asset balance into the corresponding native balance. + + The `ConversionToAssetBalance` allows to use pallet-asset-rate, e.g., as a mechanism to charge XCM fees in an asset that is not the native. +crates: [ ] + diff --git a/substrate/frame/asset-rate/src/lib.rs b/substrate/frame/asset-rate/src/lib.rs index d4afca8b73c..befabfe54aa 100644 --- a/substrate/frame/asset-rate/src/lib.rs +++ b/substrate/frame/asset-rate/src/lib.rs @@ -59,8 +59,14 @@ #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::traits::{fungible::Inspect, tokens::ConversionFromAssetBalance}; -use sp_runtime::{traits::Zero, FixedPointNumber, FixedU128}; +use frame_support::traits::{ + fungible::Inspect, + tokens::{ConversionFromAssetBalance, ConversionToAssetBalance}, +}; +use sp_runtime::{ + traits::{CheckedDiv, Zero}, + FixedPointNumber, FixedU128, +}; use sp_std::boxed::Box; pub use pallet::*; @@ -144,6 +150,8 @@ pub mod pallet { UnknownAssetKind, /// The given asset ID already has an assigned conversion rate and cannot be re-created. AlreadyExists, + /// Overflow ocurred when calculating the inverse rate. + Overflow, } #[pallet::call] @@ -246,3 +254,25 @@ where pallet::ConversionRateToNative::::set(asset_id.clone(), Some(1.into())); } } + +/// Exposes conversion of a native balance to an asset balance. +impl ConversionToAssetBalance, AssetKindOf, BalanceOf> for Pallet +where + T: Config, +{ + type Error = pallet::Error; + + fn to_asset_balance( + balance: BalanceOf, + asset_kind: AssetKindOf, + ) -> Result, pallet::Error> { + let rate = pallet::ConversionRateToNative::::get(asset_kind) + .ok_or(pallet::Error::::UnknownAssetKind.into())?; + + // We cannot use `saturating_div` here so we use `checked_div`. + Ok(FixedU128::from_u32(1) + .checked_div(&rate) + .ok_or(pallet::Error::::Overflow.into())? + .saturating_mul_int(balance)) + } +} diff --git a/substrate/frame/asset-rate/src/tests.rs b/substrate/frame/asset-rate/src/tests.rs index 45226547609..fc7bf2f1503 100644 --- a/substrate/frame/asset-rate/src/tests.rs +++ b/substrate/frame/asset-rate/src/tests.rs @@ -131,12 +131,19 @@ fn convert_works() { FixedU128::from_float(2.51) )); - let conversion = , ::AssetKind, BalanceOf, >>::from_asset_balance(10, ASSET_ID); - assert_eq!(conversion.expect("Conversion rate exists for asset"), 25); + assert_eq!(conversion_from_asset.expect("Conversion rate exists for asset"), 25); + + let conversion_to_asset = , + ::AssetKind, + BalanceOf, + >>::to_asset_balance(25, ASSET_ID); + assert_eq!(conversion_to_asset.expect("Conversion rate exists for asset"), 9); }); } @@ -151,3 +158,21 @@ fn convert_unknown_throws() { assert!(conversion.is_err()); }); } + +#[test] +fn convert_overflow_throws() { + new_test_ext().execute_with(|| { + assert_ok!(AssetRate::create( + RuntimeOrigin::root(), + Box::new(ASSET_ID), + FixedU128::from_u32(0) + )); + + let conversion = , + ::AssetKind, + BalanceOf, + >>::to_asset_balance(10, ASSET_ID); + assert!(conversion.is_err()); + }); +} -- GitLab From ef6ac94f5218368fd89ce73493909bbfbc0eace8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 20 Feb 2024 15:28:05 +0100 Subject: [PATCH 092/133] Downgrade log message to `trace` (#3405) This spams logs in `Debug` with no useful information. --- polkadot/node/core/prospective-parachains/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polkadot/node/core/prospective-parachains/src/lib.rs b/polkadot/node/core/prospective-parachains/src/lib.rs index 6e6915b9272..5937a1c1fb9 100644 --- a/polkadot/node/core/prospective-parachains/src/lib.rs +++ b/polkadot/node/core/prospective-parachains/src/lib.rs @@ -298,7 +298,7 @@ async fn handle_active_leaves_update( ) .expect("ancestors are provided in reverse order and correctly; qed"); - gum::debug!( + gum::trace!( target: LOG_TARGET, relay_parent = ?hash, min_relay_parent = scope.earliest_relay_parent().number, -- GitLab From e89d0fca351de0712f104c55fe45ed124b5c6968 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 20 Feb 2024 15:28:05 +0100 Subject: [PATCH 093/133] Lift dependencies to the workspace (Part 2/x) (#3366) Lifting some more dependencies to the workspace. Just using the most-often updated ones for now. It can be reproduced locally. ```sh # First you can check if there would be semver incompatible bumps (looks good in this case): $ zepter transpose dependency lift-to-workspace --ignore-errors syn quote thiserror "regex:^serde.*" # Then apply the changes: $ zepter transpose dependency lift-to-workspace --version-resolver=highest syn quote thiserror "regex:^serde.*" --fix # And format the changes: $ taplo format --config .config/taplo.toml ``` --------- Signed-off-by: Oliver Tale-Yazdi --- .config/taplo.toml | 1 + Cargo.toml | 8 ++++++++ bridges/primitives/header-chain/Cargo.toml | 2 +- bridges/primitives/messages/Cargo.toml | 2 +- bridges/primitives/polkadot-core/Cargo.toml | 2 +- bridges/primitives/runtime/Cargo.toml | 2 +- bridges/snowbridge/pallets/ethereum-client/Cargo.toml | 8 ++++---- bridges/snowbridge/pallets/inbound-queue/Cargo.toml | 2 +- bridges/snowbridge/pallets/outbound-queue/Cargo.toml | 2 +- bridges/snowbridge/primitives/beacon/Cargo.toml | 2 +- bridges/snowbridge/primitives/core/Cargo.toml | 2 +- bridges/snowbridge/primitives/ethereum/Cargo.toml | 6 +++--- bridges/snowbridge/primitives/router/Cargo.toml | 2 +- bridges/snowbridge/runtime/test-common/Cargo.toml | 2 +- cumulus/client/consensus/proposer/Cargo.toml | 2 +- cumulus/client/relay-chain-interface/Cargo.toml | 2 +- cumulus/client/relay-chain-rpc-interface/Cargo.toml | 6 +++--- cumulus/pallets/parachain-system/proc-macro/Cargo.toml | 4 ++-- cumulus/parachain-template/node/Cargo.toml | 4 ++-- cumulus/parachain-template/pallets/template/Cargo.toml | 2 +- .../runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml | 2 +- .../runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml | 2 +- .../runtimes/coretime/coretime-rococo/Cargo.toml | 2 +- .../runtimes/coretime/coretime-westend/Cargo.toml | 2 +- .../parachains/runtimes/people/people-rococo/Cargo.toml | 2 +- .../parachains/runtimes/people/people-westend/Cargo.toml | 2 +- cumulus/polkadot-parachain/Cargo.toml | 4 ++-- cumulus/test/service/Cargo.toml | 4 ++-- polkadot/cli/Cargo.toml | 2 +- polkadot/erasure-coding/Cargo.toml | 2 +- polkadot/node/collation-generation/Cargo.toml | 2 +- polkadot/node/core/approval-voting/Cargo.toml | 2 +- polkadot/node/core/av-store/Cargo.toml | 2 +- polkadot/node/core/backing/Cargo.toml | 2 +- polkadot/node/core/bitfield-signing/Cargo.toml | 2 +- polkadot/node/core/chain-selection/Cargo.toml | 2 +- polkadot/node/core/dispute-coordinator/Cargo.toml | 2 +- polkadot/node/core/parachains-inherent/Cargo.toml | 2 +- polkadot/node/core/prospective-parachains/Cargo.toml | 2 +- polkadot/node/core/provisioner/Cargo.toml | 2 +- polkadot/node/core/pvf-checker/Cargo.toml | 2 +- polkadot/node/core/pvf/Cargo.toml | 2 +- polkadot/node/core/pvf/common/Cargo.toml | 2 +- polkadot/node/gum/proc-macro/Cargo.toml | 4 ++-- polkadot/node/jaeger/Cargo.toml | 2 +- .../node/network/availability-distribution/Cargo.toml | 2 +- polkadot/node/network/availability-recovery/Cargo.toml | 2 +- polkadot/node/network/bridge/Cargo.toml | 2 +- polkadot/node/network/collator-protocol/Cargo.toml | 2 +- polkadot/node/network/dispute-distribution/Cargo.toml | 2 +- polkadot/node/network/protocol/Cargo.toml | 2 +- polkadot/node/network/statement-distribution/Cargo.toml | 2 +- polkadot/node/primitives/Cargo.toml | 4 ++-- polkadot/node/service/Cargo.toml | 6 +++--- polkadot/node/subsystem-bench/Cargo.toml | 4 ++-- polkadot/node/subsystem-types/Cargo.toml | 2 +- polkadot/node/subsystem-util/Cargo.toml | 2 +- polkadot/node/test/service/Cargo.toml | 2 +- polkadot/node/zombienet-backchannel/Cargo.toml | 6 +++--- polkadot/parachain/Cargo.toml | 2 +- polkadot/primitives/Cargo.toml | 2 +- polkadot/runtime/common/Cargo.toml | 6 +++--- polkadot/runtime/parachains/Cargo.toml | 4 ++-- polkadot/runtime/rococo/Cargo.toml | 6 +++--- polkadot/runtime/test-runtime/Cargo.toml | 6 +++--- polkadot/runtime/westend/Cargo.toml | 6 +++--- polkadot/xcm/Cargo.toml | 2 +- polkadot/xcm/pallet-xcm/Cargo.toml | 2 +- polkadot/xcm/procedural/Cargo.toml | 4 ++-- substrate/bin/minimal/node/Cargo.toml | 2 +- substrate/bin/node-template/node/Cargo.toml | 2 +- substrate/bin/node-template/runtime/Cargo.toml | 2 +- substrate/bin/node/bench/Cargo.toml | 4 ++-- substrate/bin/node/cli/Cargo.toml | 6 +++--- substrate/bin/node/inspect/Cargo.toml | 2 +- substrate/bin/node/runtime/Cargo.toml | 2 +- substrate/bin/utils/chain-spec-builder/Cargo.toml | 2 +- substrate/client/allocator/Cargo.toml | 2 +- substrate/client/api/Cargo.toml | 2 +- substrate/client/authority-discovery/Cargo.toml | 2 +- substrate/client/chain-spec/Cargo.toml | 4 ++-- substrate/client/chain-spec/derive/Cargo.toml | 4 ++-- substrate/client/cli/Cargo.toml | 6 +++--- substrate/client/consensus/aura/Cargo.toml | 2 +- substrate/client/consensus/babe/Cargo.toml | 2 +- substrate/client/consensus/babe/rpc/Cargo.toml | 6 +++--- substrate/client/consensus/beefy/Cargo.toml | 4 ++-- substrate/client/consensus/beefy/rpc/Cargo.toml | 6 +++--- substrate/client/consensus/common/Cargo.toml | 4 ++-- substrate/client/consensus/grandpa/Cargo.toml | 6 +++--- substrate/client/consensus/grandpa/rpc/Cargo.toml | 4 ++-- substrate/client/consensus/manual-seal/Cargo.toml | 4 ++-- substrate/client/consensus/pow/Cargo.toml | 2 +- substrate/client/executor/common/Cargo.toml | 2 +- substrate/client/keystore/Cargo.toml | 4 ++-- substrate/client/merkle-mountain-range/rpc/Cargo.toml | 4 ++-- substrate/client/mixnet/Cargo.toml | 2 +- substrate/client/network/Cargo.toml | 6 +++--- substrate/client/network/bitswap/Cargo.toml | 2 +- substrate/client/network/light/Cargo.toml | 2 +- substrate/client/network/sync/Cargo.toml | 2 +- substrate/client/rpc-api/Cargo.toml | 6 +++--- substrate/client/rpc-servers/Cargo.toml | 2 +- substrate/client/rpc-spec-v2/Cargo.toml | 6 +++--- substrate/client/rpc/Cargo.toml | 2 +- substrate/client/service/Cargo.toml | 6 +++--- substrate/client/storage-monitor/Cargo.toml | 2 +- substrate/client/sync-state-rpc/Cargo.toml | 6 +++--- substrate/client/sysinfo/Cargo.toml | 4 ++-- substrate/client/telemetry/Cargo.toml | 6 +++--- substrate/client/tracing/Cargo.toml | 4 ++-- substrate/client/tracing/proc-macro/Cargo.toml | 4 ++-- substrate/client/transaction-pool/Cargo.toml | 4 ++-- substrate/client/transaction-pool/api/Cargo.toml | 6 +++--- substrate/frame/beefy-mmr/Cargo.toml | 2 +- substrate/frame/beefy/Cargo.toml | 2 +- substrate/frame/benchmarking/Cargo.toml | 2 +- substrate/frame/contracts/Cargo.toml | 2 +- substrate/frame/contracts/proc-macro/Cargo.toml | 4 ++-- substrate/frame/conviction-voting/Cargo.toml | 2 +- substrate/frame/democracy/Cargo.toml | 2 +- .../election-provider-support/solution-type/Cargo.toml | 4 ++-- substrate/frame/message-queue/Cargo.toml | 2 +- substrate/frame/mixnet/Cargo.toml | 2 +- substrate/frame/offences/Cargo.toml | 2 +- substrate/frame/parameters/Cargo.toml | 2 +- substrate/frame/referenda/Cargo.toml | 2 +- substrate/frame/remark/Cargo.toml | 2 +- substrate/frame/staking/Cargo.toml | 2 +- substrate/frame/staking/reward-curve/Cargo.toml | 4 ++-- substrate/frame/state-trie-migration/Cargo.toml | 2 +- substrate/frame/support/Cargo.toml | 4 ++-- substrate/frame/support/procedural/Cargo.toml | 4 ++-- substrate/frame/support/procedural/tools/Cargo.toml | 4 ++-- .../frame/support/procedural/tools/derive/Cargo.toml | 4 ++-- substrate/frame/support/test/Cargo.toml | 2 +- substrate/frame/support/test/pallet/Cargo.toml | 2 +- substrate/frame/system/Cargo.toml | 2 +- substrate/frame/tips/Cargo.toml | 2 +- substrate/frame/transaction-payment/Cargo.toml | 4 ++-- .../frame/transaction-payment/asset-tx-payment/Cargo.toml | 4 ++-- substrate/frame/transaction-storage/Cargo.toml | 2 +- substrate/frame/treasury/Cargo.toml | 2 +- substrate/primitives/api/Cargo.toml | 2 +- substrate/primitives/api/proc-macro/Cargo.toml | 4 ++-- substrate/primitives/application-crypto/Cargo.toml | 2 +- substrate/primitives/arithmetic/Cargo.toml | 2 +- substrate/primitives/blockchain/Cargo.toml | 2 +- substrate/primitives/consensus/babe/Cargo.toml | 2 +- substrate/primitives/consensus/beefy/Cargo.toml | 2 +- substrate/primitives/consensus/common/Cargo.toml | 2 +- substrate/primitives/consensus/grandpa/Cargo.toml | 2 +- substrate/primitives/consensus/sassafras/Cargo.toml | 2 +- substrate/primitives/consensus/slots/Cargo.toml | 2 +- substrate/primitives/core/Cargo.toml | 6 +++--- substrate/primitives/crypto/hashing/proc-macro/Cargo.toml | 4 ++-- substrate/primitives/debug-derive/Cargo.toml | 4 ++-- substrate/primitives/genesis-builder/Cargo.toml | 2 +- substrate/primitives/inherents/Cargo.toml | 2 +- substrate/primitives/maybe-compressed-blob/Cargo.toml | 2 +- substrate/primitives/merkle-mountain-range/Cargo.toml | 4 ++-- substrate/primitives/npos-elections/Cargo.toml | 2 +- substrate/primitives/rpc/Cargo.toml | 4 ++-- .../primitives/runtime-interface/proc-macro/Cargo.toml | 4 ++-- substrate/primitives/runtime/Cargo.toml | 4 ++-- substrate/primitives/staking/Cargo.toml | 2 +- substrate/primitives/state-machine/Cargo.toml | 2 +- substrate/primitives/statement-store/Cargo.toml | 2 +- substrate/primitives/storage/Cargo.toml | 2 +- substrate/primitives/test-primitives/Cargo.toml | 2 +- substrate/primitives/timestamp/Cargo.toml | 2 +- substrate/primitives/trie/Cargo.toml | 2 +- substrate/primitives/version/Cargo.toml | 4 ++-- substrate/primitives/version/proc-macro/Cargo.toml | 4 ++-- substrate/primitives/weights/Cargo.toml | 2 +- substrate/test-utils/client/Cargo.toml | 4 ++-- substrate/test-utils/runtime/Cargo.toml | 4 ++-- substrate/test-utils/runtime/transaction-pool/Cargo.toml | 2 +- substrate/utils/frame/benchmarking-cli/Cargo.toml | 6 +++--- substrate/utils/frame/remote-externalities/Cargo.toml | 2 +- substrate/utils/frame/rpc/client/Cargo.toml | 2 +- .../utils/frame/rpc/state-trie-migration-rpc/Cargo.toml | 4 ++-- substrate/utils/frame/rpc/support/Cargo.toml | 2 +- substrate/utils/frame/try-runtime/cli/Cargo.toml | 4 ++-- substrate/utils/prometheus/Cargo.toml | 2 +- 185 files changed, 283 insertions(+), 274 deletions(-) diff --git a/.config/taplo.toml b/.config/taplo.toml index a45204923cf..2c6ccfb2b34 100644 --- a/.config/taplo.toml +++ b/.config/taplo.toml @@ -7,6 +7,7 @@ exclude = [ "polkadot/node/malus/integrationtests/**", "polkadot/zombienet_tests/**", "substrate/zombienet/**", + "target/**", ] # global rules diff --git a/Cargo.toml b/Cargo.toml index d09f19f280c..7e666755922 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -537,6 +537,14 @@ default_constructed_unit_structs = { level = "allow", priority = 2 } # stylistic polkavm-linker = "0.8.2" polkavm-derive = "0.8.0" log = { version = "0.4.20", default-features = false } +quote = { version = "1.0.33" } +serde = { version = "1.0.196", default-features = false } +serde-big-array = { version = "0.3.2" } +serde_derive = { version = "1.0.117" } +serde_json = { version = "1.0.113", default-features = false } +serde_yaml = { version = "0.9" } +syn = { version = "2.0.49" } +thiserror = { version = "1.0.48" } [profile.release] # Polkadot runtime requires unwinding. diff --git a/bridges/primitives/header-chain/Cargo.toml b/bridges/primitives/header-chain/Cargo.toml index 828b567bb94..205b593365e 100644 --- a/bridges/primitives/header-chain/Cargo.toml +++ b/bridges/primitives/header-chain/Cargo.toml @@ -13,7 +13,7 @@ workspace = true codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } finality-grandpa = { version = "0.16.2", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } +serde = { features = ["alloc", "derive"], workspace = true } # Bridge dependencies diff --git a/bridges/primitives/messages/Cargo.toml b/bridges/primitives/messages/Cargo.toml index 54aae4a2f76..8aa6b4b05e5 100644 --- a/bridges/primitives/messages/Cargo.toml +++ b/bridges/primitives/messages/Cargo.toml @@ -12,7 +12,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["bit-vec", "derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["bit-vec", "derive"] } -serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } +serde = { features = ["alloc", "derive"], workspace = true } # Bridge dependencies diff --git a/bridges/primitives/polkadot-core/Cargo.toml b/bridges/primitives/polkadot-core/Cargo.toml index e667c283a07..c0dae684b5f 100644 --- a/bridges/primitives/polkadot-core/Cargo.toml +++ b/bridges/primitives/polkadot-core/Cargo.toml @@ -13,7 +13,7 @@ workspace = true codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive"] } parity-util-mem = { version = "0.12.0", optional = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0", optional = true, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } # Bridge Dependencies diff --git a/bridges/primitives/runtime/Cargo.toml b/bridges/primitives/runtime/Cargo.toml index 2ea31c26d6b..22206fb2c37 100644 --- a/bridges/primitives/runtime/Cargo.toml +++ b/bridges/primitives/runtime/Cargo.toml @@ -16,7 +16,7 @@ impl-trait-for-tuples = "0.2.2" log = { workspace = true } num-traits = { version = "0.2", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } +serde = { features = ["alloc", "derive"], workspace = true } # Substrate Dependencies diff --git a/bridges/snowbridge/pallets/ethereum-client/Cargo.toml b/bridges/snowbridge/pallets/ethereum-client/Cargo.toml index 541ded26737..99b42905311 100644 --- a/bridges/snowbridge/pallets/ethereum-client/Cargo.toml +++ b/bridges/snowbridge/pallets/ethereum-client/Cargo.toml @@ -15,8 +15,8 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.196", optional = true } -serde_json = { version = "1.0.113", optional = true } +serde = { optional = true, workspace = true, default-features = true } +serde_json = { optional = true, workspace = true, default-features = true } codec = { version = "3.6.1", package = "parity-scale-codec", default-features = false, features = ["derive"] } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } ssz_rs = { version = "0.9.0", default-features = false } @@ -45,12 +45,12 @@ pallet-timestamp = { path = "../../../../substrate/frame/timestamp", default-fea [dev-dependencies] rand = "0.8.5" sp-keyring = { path = "../../../../substrate/primitives/keyring" } -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } hex-literal = "0.4.1" pallet-timestamp = { path = "../../../../substrate/frame/timestamp" } snowbridge-pallet-ethereum-client-fixtures = { path = "./fixtures" } sp-io = { path = "../../../../substrate/primitives/io" } -serde = "1.0.196" +serde = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/bridges/snowbridge/pallets/inbound-queue/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue/Cargo.toml index 09653fa3b48..c26ef90a22d 100644 --- a/bridges/snowbridge/pallets/inbound-queue/Cargo.toml +++ b/bridges/snowbridge/pallets/inbound-queue/Cargo.toml @@ -15,7 +15,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.196", optional = true } +serde = { optional = true, workspace = true, default-features = true } codec = { version = "3.6.1", package = "parity-scale-codec", default-features = false, features = ["derive"] } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1", optional = true } diff --git a/bridges/snowbridge/pallets/outbound-queue/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue/Cargo.toml index 0956ac76678..40e57db4bbd 100644 --- a/bridges/snowbridge/pallets/outbound-queue/Cargo.toml +++ b/bridges/snowbridge/pallets/outbound-queue/Cargo.toml @@ -15,7 +15,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.196", features = ["alloc", "derive"], default-features = false } +serde = { features = ["alloc", "derive"], workspace = true } codec = { version = "3.6.1", package = "parity-scale-codec", default-features = false, features = ["derive"] } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1", optional = true } diff --git a/bridges/snowbridge/primitives/beacon/Cargo.toml b/bridges/snowbridge/primitives/beacon/Cargo.toml index 4f5213b3422..e021bb01071 100644 --- a/bridges/snowbridge/primitives/beacon/Cargo.toml +++ b/bridges/snowbridge/primitives/beacon/Cargo.toml @@ -12,7 +12,7 @@ categories = ["cryptography::cryptocurrencies"] workspace = true [dependencies] -serde = { version = "1.0.196", optional = true, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } hex = { version = "0.4", default-features = false } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } diff --git a/bridges/snowbridge/primitives/core/Cargo.toml b/bridges/snowbridge/primitives/core/Cargo.toml index 460c6c3442e..090c48c403f 100644 --- a/bridges/snowbridge/primitives/core/Cargo.toml +++ b/bridges/snowbridge/primitives/core/Cargo.toml @@ -12,7 +12,7 @@ categories = ["cryptography::cryptocurrencies"] workspace = true [dependencies] -serde = { version = "1.0.196", optional = true, features = ["alloc", "derive"], default-features = false } +serde = { optional = true, features = ["alloc", "derive"], workspace = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1" } diff --git a/bridges/snowbridge/primitives/ethereum/Cargo.toml b/bridges/snowbridge/primitives/ethereum/Cargo.toml index a9e2fe65421..399139cef38 100644 --- a/bridges/snowbridge/primitives/ethereum/Cargo.toml +++ b/bridges/snowbridge/primitives/ethereum/Cargo.toml @@ -12,8 +12,8 @@ categories = ["cryptography::cryptocurrencies"] workspace = true [dependencies] -serde = { version = "1.0.196", optional = true, features = ["derive"] } -serde-big-array = { version = "0.3.2", optional = true, features = ["const-generics"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } +serde-big-array = { optional = true, features = ["const-generics"], workspace = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } ethbloom = { version = "0.13.0", default-features = false } @@ -33,7 +33,7 @@ ethabi = { package = "ethabi-decode", version = "1.0.0", default-features = fals [dev-dependencies] wasm-bindgen-test = "0.3.19" rand = "0.8.5" -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/bridges/snowbridge/primitives/router/Cargo.toml b/bridges/snowbridge/primitives/router/Cargo.toml index c4fc688a946..750a2a73e63 100644 --- a/bridges/snowbridge/primitives/router/Cargo.toml +++ b/bridges/snowbridge/primitives/router/Cargo.toml @@ -12,7 +12,7 @@ categories = ["cryptography::cryptocurrencies"] workspace = true [dependencies] -serde = { version = "1.0.196", optional = true, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } log = { workspace = true } diff --git a/bridges/snowbridge/runtime/test-common/Cargo.toml b/bridges/snowbridge/runtime/test-common/Cargo.toml index 868f72b7b86..ec4466b3426 100644 --- a/bridges/snowbridge/runtime/test-common/Cargo.toml +++ b/bridges/snowbridge/runtime/test-common/Cargo.toml @@ -15,7 +15,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = hex-literal = { version = "0.4.1" } log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", optional = true, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } smallvec = "1.11.0" # Substrate diff --git a/cumulus/client/consensus/proposer/Cargo.toml b/cumulus/client/consensus/proposer/Cargo.toml index 8a559c603f3..b37232bb448 100644 --- a/cumulus/client/consensus/proposer/Cargo.toml +++ b/cumulus/client/consensus/proposer/Cargo.toml @@ -12,7 +12,7 @@ workspace = true [dependencies] anyhow = "1.0" async-trait = "0.1.74" -thiserror = "1.0.48" +thiserror = { workspace = true } # Substrate sp-consensus = { path = "../../../../substrate/primitives/consensus/common" } diff --git a/cumulus/client/relay-chain-interface/Cargo.toml b/cumulus/client/relay-chain-interface/Cargo.toml index 5cdfff23ae1..6e652b89210 100644 --- a/cumulus/client/relay-chain-interface/Cargo.toml +++ b/cumulus/client/relay-chain-interface/Cargo.toml @@ -21,6 +21,6 @@ sc-client-api = { path = "../../../substrate/client/api" } futures = "0.3.28" async-trait = "0.1.74" -thiserror = "1.0.48" +thiserror = { workspace = true } jsonrpsee-core = "0.22" parity-scale-codec = "3.6.4" diff --git a/cumulus/client/relay-chain-rpc-interface/Cargo.toml b/cumulus/client/relay-chain-rpc-interface/Cargo.toml index 1534537e0d4..801712b1ad1 100644 --- a/cumulus/client/relay-chain-rpc-interface/Cargo.toml +++ b/cumulus/client/relay-chain-rpc-interface/Cargo.toml @@ -37,12 +37,12 @@ jsonrpsee = { version = "0.22", features = ["ws-client"] } tracing = "0.1.37" async-trait = "0.1.74" url = "2.4.0" -serde_json = "1.0.113" -serde = "1.0.196" +serde_json = { workspace = true, default-features = true } +serde = { workspace = true, default-features = true } schnellru = "0.2.1" smoldot = { version = "0.11.0", default_features = false, features = ["std"] } smoldot-light = { version = "0.9.0", default_features = false, features = ["std"] } either = "1.8.1" -thiserror = "1.0.48" +thiserror = { workspace = true } rand = "0.8.5" pin-project = "1.1.3" diff --git a/cumulus/pallets/parachain-system/proc-macro/Cargo.toml b/cumulus/pallets/parachain-system/proc-macro/Cargo.toml index 51737452739..0a90c30e033 100644 --- a/cumulus/pallets/parachain-system/proc-macro/Cargo.toml +++ b/cumulus/pallets/parachain-system/proc-macro/Cargo.toml @@ -13,9 +13,9 @@ workspace = true proc-macro = true [dependencies] -syn = "2.0.49" +syn = { workspace = true } proc-macro2 = "1.0.64" -quote = "1.0.33" +quote = { workspace = true } proc-macro-crate = "3.0.0" [features] diff --git a/cumulus/parachain-template/node/Cargo.toml b/cumulus/parachain-template/node/Cargo.toml index 77b484913b8..0ef678c4cba 100644 --- a/cumulus/parachain-template/node/Cargo.toml +++ b/cumulus/parachain-template/node/Cargo.toml @@ -17,10 +17,10 @@ workspace = true clap = { version = "4.5.1", features = ["derive"] } log = { workspace = true, default-features = true } codec = { package = "parity-scale-codec", version = "3.0.0" } -serde = { version = "1.0.196", features = ["derive"] } +serde = { features = ["derive"], workspace = true, default-features = true } jsonrpsee = { version = "0.22", features = ["server"] } futures = "0.3.28" -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } # Local parachain-template-runtime = { path = "../runtime" } diff --git a/cumulus/parachain-template/pallets/template/Cargo.toml b/cumulus/parachain-template/pallets/template/Cargo.toml index bd9d21d2dd2..04858a161fa 100644 --- a/cumulus/parachain-template/pallets/template/Cargo.toml +++ b/cumulus/parachain-template/pallets/template/Cargo.toml @@ -24,7 +24,7 @@ frame-support = { path = "../../../../substrate/frame/support", default-features frame-system = { path = "../../../../substrate/frame/system", default-features = false } [dev-dependencies] -serde = { version = "1.0.196" } +serde = { workspace = true, default-features = true } # Substrate sp-core = { path = "../../../../substrate/primitives/core", default-features = false } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index 02e51782700..7a1951fd24b 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -21,7 +21,7 @@ log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = [ "derive", ] } -serde = { version = "1.0.196", optional = true, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } # Substrate frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml index 6015251a238..8623f7cb366 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -17,7 +17,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = hex-literal = { version = "0.4.1" } log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", optional = true, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } # Substrate frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml index 4c141fc691c..0bc3b510ed5 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml @@ -17,7 +17,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = hex-literal = "0.4.1" log = { workspace = true } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", optional = true, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } # Substrate frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml index 135ce95f30f..a7d52dfd784 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml @@ -17,7 +17,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = hex-literal = "0.4.1" log = { workspace = true } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", optional = true, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } # Substrate frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } diff --git a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml index 8ff031b3e33..c0b8fb7636b 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml @@ -15,7 +15,7 @@ enumflags2 = { version = "0.7.7" } hex-literal = { version = "0.4.1" } log = { workspace = true } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", optional = true, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } # Substrate frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } diff --git a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml index a08b9702160..e87e825a34e 100644 --- a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml @@ -15,7 +15,7 @@ enumflags2 = { version = "0.7.7" } hex-literal = { version = "0.4.1" } log = { workspace = true } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", optional = true, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } # Substrate frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } diff --git a/cumulus/polkadot-parachain/Cargo.toml b/cumulus/polkadot-parachain/Cargo.toml index d5624941c03..84a232e954f 100644 --- a/cumulus/polkadot-parachain/Cargo.toml +++ b/cumulus/polkadot-parachain/Cargo.toml @@ -21,8 +21,8 @@ codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.28" hex-literal = "0.4.1" log = { workspace = true, default-features = true } -serde = { version = "1.0.196", features = ["derive"] } -serde_json = "1.0.113" +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } # Local rococo-parachain-runtime = { path = "../parachains/runtimes/testing/rococo-parachain" } diff --git a/cumulus/test/service/Cargo.toml b/cumulus/test/service/Cargo.toml index 66bc9c66697..b26f0b9967c 100644 --- a/cumulus/test/service/Cargo.toml +++ b/cumulus/test/service/Cargo.toml @@ -19,8 +19,8 @@ codec = { package = "parity-scale-codec", version = "3.0.0" } criterion = { version = "0.5.1", features = ["async_tokio"] } jsonrpsee = { version = "0.22", features = ["server"] } rand = "0.8.5" -serde = { version = "1.0.196", features = ["derive"] } -serde_json = "1.0.113" +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } tokio = { version = "1.32.0", features = ["macros"] } tracing = "0.1.37" url = "2.4.0" diff --git a/polkadot/cli/Cargo.toml b/polkadot/cli/Cargo.toml index ee2673ac20f..b9232f95981 100644 --- a/polkadot/cli/Cargo.toml +++ b/polkadot/cli/Cargo.toml @@ -21,7 +21,7 @@ crate-type = ["cdylib", "rlib"] cfg-if = "1.0" clap = { version = "4.5.1", features = ["derive"], optional = true } log = { workspace = true, default-features = true } -thiserror = "1.0.48" +thiserror = { workspace = true } futures = "0.3.21" pyro = { package = "pyroscope", version = "0.5.3", optional = true } pyroscope_pprofrs = { version = "0.2", optional = true } diff --git a/polkadot/erasure-coding/Cargo.toml b/polkadot/erasure-coding/Cargo.toml index 8705c14c3a4..677f15c4b9a 100644 --- a/polkadot/erasure-coding/Cargo.toml +++ b/polkadot/erasure-coding/Cargo.toml @@ -16,7 +16,7 @@ novelpoly = { package = "reed-solomon-novelpoly", version = "2.0.0" } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive", "std"] } sp-core = { path = "../../substrate/primitives/core" } sp-trie = { path = "../../substrate/primitives/trie" } -thiserror = "1.0.48" +thiserror = { workspace = true } [dev-dependencies] criterion = { version = "0.4.0", default-features = false, features = ["cargo_bench_support"] } diff --git a/polkadot/node/collation-generation/Cargo.toml b/polkadot/node/collation-generation/Cargo.toml index 8c6644ad6da..8df0c2b1eda 100644 --- a/polkadot/node/collation-generation/Cargo.toml +++ b/polkadot/node/collation-generation/Cargo.toml @@ -19,7 +19,7 @@ polkadot-node-subsystem-util = { path = "../subsystem-util" } polkadot-primitives = { path = "../../primitives" } sp-core = { path = "../../../substrate/primitives/core" } sp-maybe-compressed-blob = { path = "../../../substrate/primitives/maybe-compressed-blob" } -thiserror = "1.0.48" +thiserror = { workspace = true } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["bit-vec", "derive"] } [dev-dependencies] diff --git a/polkadot/node/core/approval-voting/Cargo.toml b/polkadot/node/core/approval-voting/Cargo.toml index 1010779a1b3..f6d89dbc152 100644 --- a/polkadot/node/core/approval-voting/Cargo.toml +++ b/polkadot/node/core/approval-voting/Cargo.toml @@ -20,7 +20,7 @@ merlin = "3.0" schnorrkel = "0.11.4" kvdb = "0.13.0" derive_more = "0.99.17" -thiserror = "1.0.48" +thiserror = { workspace = true } itertools = "0.10.5" polkadot-node-subsystem = { path = "../../subsystem" } diff --git a/polkadot/node/core/av-store/Cargo.toml b/polkadot/node/core/av-store/Cargo.toml index 4f5c18a68d6..05212da7479 100644 --- a/polkadot/node/core/av-store/Cargo.toml +++ b/polkadot/node/core/av-store/Cargo.toml @@ -13,7 +13,7 @@ workspace = true futures = "0.3.21" futures-timer = "3.0.2" kvdb = "0.13.0" -thiserror = "1.0.48" +thiserror = { workspace = true } gum = { package = "tracing-gum", path = "../../gum" } bitvec = "1.0.0" diff --git a/polkadot/node/core/backing/Cargo.toml b/polkadot/node/core/backing/Cargo.toml index 1c69fc441b3..b0cf041e38d 100644 --- a/polkadot/node/core/backing/Cargo.toml +++ b/polkadot/node/core/backing/Cargo.toml @@ -20,7 +20,7 @@ erasure-coding = { package = "polkadot-erasure-coding", path = "../../../erasure statement-table = { package = "polkadot-statement-table", path = "../../../statement-table" } bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } gum = { package = "tracing-gum", path = "../../gum" } -thiserror = "1.0.48" +thiserror = { workspace = true } fatality = "0.0.6" [dev-dependencies] diff --git a/polkadot/node/core/bitfield-signing/Cargo.toml b/polkadot/node/core/bitfield-signing/Cargo.toml index f42e8f8d22b..6ecfffd7249 100644 --- a/polkadot/node/core/bitfield-signing/Cargo.toml +++ b/polkadot/node/core/bitfield-signing/Cargo.toml @@ -17,7 +17,7 @@ polkadot-node-subsystem = { path = "../../subsystem" } polkadot-node-subsystem-util = { path = "../../subsystem-util" } sp-keystore = { path = "../../../../substrate/primitives/keystore" } wasm-timer = "0.2.5" -thiserror = "1.0.48" +thiserror = { workspace = true } [dev-dependencies] polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" } diff --git a/polkadot/node/core/chain-selection/Cargo.toml b/polkadot/node/core/chain-selection/Cargo.toml index 27c2df85235..96fd42785cd 100644 --- a/polkadot/node/core/chain-selection/Cargo.toml +++ b/polkadot/node/core/chain-selection/Cargo.toml @@ -18,7 +18,7 @@ polkadot-node-primitives = { path = "../../primitives" } polkadot-node-subsystem = { path = "../../subsystem" } polkadot-node-subsystem-util = { path = "../../subsystem-util" } kvdb = "0.13.0" -thiserror = "1.0.48" +thiserror = { workspace = true } parity-scale-codec = "3.6.1" [dev-dependencies] diff --git a/polkadot/node/core/dispute-coordinator/Cargo.toml b/polkadot/node/core/dispute-coordinator/Cargo.toml index 8a414a35f39..1fff0a77170 100644 --- a/polkadot/node/core/dispute-coordinator/Cargo.toml +++ b/polkadot/node/core/dispute-coordinator/Cargo.toml @@ -14,7 +14,7 @@ futures = "0.3.21" gum = { package = "tracing-gum", path = "../../gum" } parity-scale-codec = "3.6.1" kvdb = "0.13.0" -thiserror = "1.0.48" +thiserror = { workspace = true } schnellru = "0.2.1" fatality = "0.0.6" diff --git a/polkadot/node/core/parachains-inherent/Cargo.toml b/polkadot/node/core/parachains-inherent/Cargo.toml index 3b2fa634102..24da4dc1e31 100644 --- a/polkadot/node/core/parachains-inherent/Cargo.toml +++ b/polkadot/node/core/parachains-inherent/Cargo.toml @@ -13,7 +13,7 @@ workspace = true futures = "0.3.21" futures-timer = "3.0.2" gum = { package = "tracing-gum", path = "../../gum" } -thiserror = "1.0.48" +thiserror = { workspace = true } async-trait = "0.1.74" polkadot-node-subsystem = { path = "../../subsystem" } polkadot-overseer = { path = "../../overseer" } diff --git a/polkadot/node/core/prospective-parachains/Cargo.toml b/polkadot/node/core/prospective-parachains/Cargo.toml index 5f0b2c0fdc9..5b62d90c1d4 100644 --- a/polkadot/node/core/prospective-parachains/Cargo.toml +++ b/polkadot/node/core/prospective-parachains/Cargo.toml @@ -13,7 +13,7 @@ workspace = true futures = "0.3.19" gum = { package = "tracing-gum", path = "../../gum" } parity-scale-codec = "3.6.4" -thiserror = "1.0.48" +thiserror = { workspace = true } fatality = "0.0.6" bitvec = "1" diff --git a/polkadot/node/core/provisioner/Cargo.toml b/polkadot/node/core/provisioner/Cargo.toml index 175980e90a0..24cdfd6b57b 100644 --- a/polkadot/node/core/provisioner/Cargo.toml +++ b/polkadot/node/core/provisioner/Cargo.toml @@ -13,7 +13,7 @@ workspace = true bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } futures = "0.3.21" gum = { package = "tracing-gum", path = "../../gum" } -thiserror = "1.0.48" +thiserror = { workspace = true } polkadot-primitives = { path = "../../../primitives" } polkadot-node-primitives = { path = "../../primitives" } polkadot-node-subsystem = { path = "../../subsystem" } diff --git a/polkadot/node/core/pvf-checker/Cargo.toml b/polkadot/node/core/pvf-checker/Cargo.toml index 3f02c2fa74a..f4f954e316c 100644 --- a/polkadot/node/core/pvf-checker/Cargo.toml +++ b/polkadot/node/core/pvf-checker/Cargo.toml @@ -11,7 +11,7 @@ workspace = true [dependencies] futures = "0.3.21" -thiserror = "1.0.48" +thiserror = { workspace = true } gum = { package = "tracing-gum", path = "../../gum" } polkadot-node-primitives = { path = "../../primitives" } diff --git a/polkadot/node/core/pvf/Cargo.toml b/polkadot/node/core/pvf/Cargo.toml index d7ea573cfd2..9ed64b88ffd 100644 --- a/polkadot/node/core/pvf/Cargo.toml +++ b/polkadot/node/core/pvf/Cargo.toml @@ -23,7 +23,7 @@ pin-project = "1.0.9" rand = "0.8.5" slotmap = "1.0" tempfile = "3.3.0" -thiserror = "1.0.31" +thiserror = { workspace = true } tokio = { version = "1.24.2", features = ["fs", "process"] } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } diff --git a/polkadot/node/core/pvf/common/Cargo.toml b/polkadot/node/core/pvf/common/Cargo.toml index 631d4335b52..56bad9792fa 100644 --- a/polkadot/node/core/pvf/common/Cargo.toml +++ b/polkadot/node/core/pvf/common/Cargo.toml @@ -15,7 +15,7 @@ cpu-time = "1.0.0" futures = "0.3.21" gum = { package = "tracing-gum", path = "../../../gum" } libc = "0.2.152" -thiserror = "1.0.31" +thiserror = { workspace = true } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } diff --git a/polkadot/node/gum/proc-macro/Cargo.toml b/polkadot/node/gum/proc-macro/Cargo.toml index 4c81c8314df..70126b4f433 100644 --- a/polkadot/node/gum/proc-macro/Cargo.toml +++ b/polkadot/node/gum/proc-macro/Cargo.toml @@ -16,8 +16,8 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -syn = { version = "2.0.49", features = ["extra-traits", "full"] } -quote = "1.0.28" +syn = { features = ["extra-traits", "full"], workspace = true } +quote = { workspace = true } proc-macro2 = "1.0.56" proc-macro-crate = "3.0.0" expander = "2.0.0" diff --git a/polkadot/node/jaeger/Cargo.toml b/polkadot/node/jaeger/Cargo.toml index c8fd3d46283..23ab8f84210 100644 --- a/polkadot/node/jaeger/Cargo.toml +++ b/polkadot/node/jaeger/Cargo.toml @@ -17,7 +17,7 @@ polkadot-primitives = { path = "../../primitives" } polkadot-node-primitives = { path = "../primitives" } sc-network = { path = "../../../substrate/client/network" } sp-core = { path = "../../../substrate/primitives/core" } -thiserror = "1.0.48" +thiserror = { workspace = true } tokio = "1.24.2" log = { workspace = true, default-features = true } parity-scale-codec = { version = "3.6.1", default-features = false } diff --git a/polkadot/node/network/availability-distribution/Cargo.toml b/polkadot/node/network/availability-distribution/Cargo.toml index a1484429ed6..432501ed23f 100644 --- a/polkadot/node/network/availability-distribution/Cargo.toml +++ b/polkadot/node/network/availability-distribution/Cargo.toml @@ -21,7 +21,7 @@ polkadot-node-subsystem-util = { path = "../../subsystem-util" } polkadot-node-primitives = { path = "../../primitives" } sp-core = { path = "../../../../substrate/primitives/core", features = ["std"] } sp-keystore = { path = "../../../../substrate/primitives/keystore" } -thiserror = "1.0.48" +thiserror = { workspace = true } rand = "0.8.5" derive_more = "0.99.17" schnellru = "0.2.1" diff --git a/polkadot/node/network/availability-recovery/Cargo.toml b/polkadot/node/network/availability-recovery/Cargo.toml index e86445730ff..9eddf5c86d2 100644 --- a/polkadot/node/network/availability-recovery/Cargo.toml +++ b/polkadot/node/network/availability-recovery/Cargo.toml @@ -15,7 +15,7 @@ tokio = "1.24.2" schnellru = "0.2.1" rand = "0.8.5" fatality = "0.0.6" -thiserror = "1.0.48" +thiserror = { workspace = true } async-trait = "0.1.74" gum = { package = "tracing-gum", path = "../../gum" } diff --git a/polkadot/node/network/bridge/Cargo.toml b/polkadot/node/network/bridge/Cargo.toml index 0cdd7bfbcb1..2e889fc30eb 100644 --- a/polkadot/node/network/bridge/Cargo.toml +++ b/polkadot/node/network/bridge/Cargo.toml @@ -25,7 +25,7 @@ polkadot-overseer = { path = "../../overseer" } parking_lot = "0.12.1" bytes = "1" fatality = "0.0.6" -thiserror = "1" +thiserror = { workspace = true } [dev-dependencies] assert_matches = "1.4.0" diff --git a/polkadot/node/network/collator-protocol/Cargo.toml b/polkadot/node/network/collator-protocol/Cargo.toml index f88e8182ecc..f0f8be0f7ba 100644 --- a/polkadot/node/network/collator-protocol/Cargo.toml +++ b/polkadot/node/network/collator-protocol/Cargo.toml @@ -25,7 +25,7 @@ polkadot-node-primitives = { path = "../../primitives" } polkadot-node-subsystem-util = { path = "../../subsystem-util" } polkadot-node-subsystem = { path = "../../subsystem" } fatality = "0.0.6" -thiserror = "1.0.48" +thiserror = { workspace = true } tokio-util = "0.7.1" [dev-dependencies] diff --git a/polkadot/node/network/dispute-distribution/Cargo.toml b/polkadot/node/network/dispute-distribution/Cargo.toml index 319b743aa58..14d59d04f2b 100644 --- a/polkadot/node/network/dispute-distribution/Cargo.toml +++ b/polkadot/node/network/dispute-distribution/Cargo.toml @@ -24,7 +24,7 @@ polkadot-node-primitives = { path = "../../primitives" } sc-network = { path = "../../../../substrate/client/network" } sp-application-crypto = { path = "../../../../substrate/primitives/application-crypto" } sp-keystore = { path = "../../../../substrate/primitives/keystore" } -thiserror = "1.0.48" +thiserror = { workspace = true } fatality = "0.0.6" schnellru = "0.2.1" indexmap = "2.0.0" diff --git a/polkadot/node/network/protocol/Cargo.toml b/polkadot/node/network/protocol/Cargo.toml index a8b0cf2737e..7efa0b8ca82 100644 --- a/polkadot/node/network/protocol/Cargo.toml +++ b/polkadot/node/network/protocol/Cargo.toml @@ -21,7 +21,7 @@ sc-network = { path = "../../../../substrate/client/network" } sc-authority-discovery = { path = "../../../../substrate/client/authority-discovery" } strum = { version = "0.24", features = ["derive"] } futures = "0.3.21" -thiserror = "1.0.48" +thiserror = { workspace = true } fatality = "0.0.6" rand = "0.8" derive_more = "0.99" diff --git a/polkadot/node/network/statement-distribution/Cargo.toml b/polkadot/node/network/statement-distribution/Cargo.toml index b07d84390dc..01f7d818c1c 100644 --- a/polkadot/node/network/statement-distribution/Cargo.toml +++ b/polkadot/node/network/statement-distribution/Cargo.toml @@ -23,7 +23,7 @@ polkadot-node-network-protocol = { path = "../protocol" } arrayvec = "0.7.4" indexmap = "2.0.0" parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } -thiserror = "1.0.48" +thiserror = { workspace = true } fatality = "0.0.6" bitvec = "1" diff --git a/polkadot/node/primitives/Cargo.toml b/polkadot/node/primitives/Cargo.toml index b5560929b2e..b4541bcc346 100644 --- a/polkadot/node/primitives/Cargo.toml +++ b/polkadot/node/primitives/Cargo.toml @@ -22,9 +22,9 @@ sp-maybe-compressed-blob = { path = "../../../substrate/primitives/maybe-compres sp-runtime = { path = "../../../substrate/primitives/runtime" } polkadot-parachain-primitives = { path = "../../parachain", default-features = false } schnorrkel = "0.11.4" -thiserror = "1.0.48" +thiserror = { workspace = true } bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } -serde = { version = "1.0.196", features = ["derive"] } +serde = { features = ["derive"], workspace = true, default-features = true } [target.'cfg(not(target_os = "unknown"))'.dependencies] zstd = { version = "0.12.4", default-features = false } diff --git a/polkadot/node/service/Cargo.toml b/polkadot/node/service/Cargo.toml index dc6acd506c8..8fd9f20b7bc 100644 --- a/polkadot/node/service/Cargo.toml +++ b/polkadot/node/service/Cargo.toml @@ -85,9 +85,9 @@ is_executable = "1.0.1" gum = { package = "tracing-gum", path = "../gum" } log = { workspace = true, default-features = true } schnellru = "0.2.1" -serde = { version = "1.0.196", features = ["derive"] } -serde_json = "1.0.113" -thiserror = "1.0.48" +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } +thiserror = { workspace = true } kvdb = "0.13.0" kvdb-rocksdb = { version = "0.19.0", optional = true } parity-db = { version = "0.4.12", optional = true } diff --git a/polkadot/node/subsystem-bench/Cargo.toml b/polkadot/node/subsystem-bench/Cargo.toml index 203cfe05db1..7de91d8cd5d 100644 --- a/polkadot/node/subsystem-bench/Cargo.toml +++ b/polkadot/node/subsystem-bench/Cargo.toml @@ -65,8 +65,8 @@ itertools = "0.11.0" polkadot-primitives-test-helpers = { path = "../../primitives/test-helpers" } prometheus_endpoint = { package = "substrate-prometheus-endpoint", path = "../../../substrate/utils/prometheus" } prometheus = { version = "0.13.0", default-features = false } -serde = "1.0.196" -serde_yaml = "0.9" +serde = { workspace = true, default-features = true } +serde_yaml = { workspace = true } polkadot-node-core-approval-voting = { path = "../core/approval-voting" } polkadot-approval-distribution = { path = "../network/approval-distribution" } diff --git a/polkadot/node/subsystem-types/Cargo.toml b/polkadot/node/subsystem-types/Cargo.toml index ffd05cca2e9..54c8f7e2ade 100644 --- a/polkadot/node/subsystem-types/Cargo.toml +++ b/polkadot/node/subsystem-types/Cargo.toml @@ -28,6 +28,6 @@ sc-client-api = { path = "../../../substrate/client/api" } sc-transaction-pool-api = { path = "../../../substrate/client/transaction-pool/api" } smallvec = "1.8.0" substrate-prometheus-endpoint = { path = "../../../substrate/utils/prometheus" } -thiserror = "1.0.48" +thiserror = { workspace = true } async-trait = "0.1.74" bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } diff --git a/polkadot/node/subsystem-util/Cargo.toml b/polkadot/node/subsystem-util/Cargo.toml index b2a643d92f6..a668f8de76a 100644 --- a/polkadot/node/subsystem-util/Cargo.toml +++ b/polkadot/node/subsystem-util/Cargo.toml @@ -18,7 +18,7 @@ parity-scale-codec = { version = "3.6.1", default-features = false, features = [ parking_lot = "0.12.1" pin-project = "1.0.9" rand = "0.8.5" -thiserror = "1.0.48" +thiserror = { workspace = true } fatality = "0.0.6" gum = { package = "tracing-gum", path = "../gum" } derive_more = "0.99.17" diff --git a/polkadot/node/test/service/Cargo.toml b/polkadot/node/test/service/Cargo.toml index 47a754892ed..e7892abcd87 100644 --- a/polkadot/node/test/service/Cargo.toml +++ b/polkadot/node/test/service/Cargo.toml @@ -14,7 +14,7 @@ futures = "0.3.21" hex = "0.4.3" gum = { package = "tracing-gum", path = "../../gum" } rand = "0.8.5" -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } tempfile = "3.2.0" tokio = "1.24.2" diff --git a/polkadot/node/zombienet-backchannel/Cargo.toml b/polkadot/node/zombienet-backchannel/Cargo.toml index ec0521a592d..fa99490a997 100644 --- a/polkadot/node/zombienet-backchannel/Cargo.toml +++ b/polkadot/node/zombienet-backchannel/Cargo.toml @@ -19,7 +19,7 @@ futures-util = "0.3.30" lazy_static = "1.4.0" parity-scale-codec = { version = "3.6.1", features = ["derive"] } reqwest = { version = "0.11", features = ["rustls-tls"], default-features = false } -thiserror = "1.0.48" +thiserror = { workspace = true } gum = { package = "tracing-gum", path = "../gum" } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0.113" +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } diff --git a/polkadot/parachain/Cargo.toml b/polkadot/parachain/Cargo.toml index 843c4fe3e95..d8c3cea7ad8 100644 --- a/polkadot/parachain/Cargo.toml +++ b/polkadot/parachain/Cargo.toml @@ -24,7 +24,7 @@ derive_more = "0.99.11" bounded-collections = { version = "0.2.0", default-features = false, features = ["serde"] } # all optional crates. -serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"] } +serde = { features = ["alloc", "derive"], workspace = true } [features] default = ["std"] diff --git a/polkadot/primitives/Cargo.toml b/polkadot/primitives/Cargo.toml index fa3e949d691..58d3d100161 100644 --- a/polkadot/primitives/Cargo.toml +++ b/polkadot/primitives/Cargo.toml @@ -14,7 +14,7 @@ bitvec = { version = "1.0.0", default-features = false, features = ["alloc", "se hex-literal = "0.4.1" parity-scale-codec = { version = "3.6.1", default-features = false, features = ["bit-vec", "derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["bit-vec", "derive", "serde"] } -serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"] } +serde = { features = ["alloc", "derive"], workspace = true } application-crypto = { package = "sp-application-crypto", path = "../../substrate/primitives/application-crypto", default-features = false, features = ["serde"] } inherents = { package = "sp-inherents", path = "../../substrate/primitives/inherents", default-features = false } diff --git a/polkadot/runtime/common/Cargo.toml b/polkadot/runtime/common/Cargo.toml index 52fbfd9ac77..2467d123f02 100644 --- a/polkadot/runtime/common/Cargo.toml +++ b/polkadot/runtime/common/Cargo.toml @@ -16,8 +16,8 @@ parity-scale-codec = { version = "3.6.1", default-features = false, features = [ log = { workspace = true } rustc-hex = { version = "2.1.0", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", default-features = false, features = ["alloc"] } -serde_derive = { version = "1.0.117" } +serde = { features = ["alloc"], workspace = true } +serde_derive = { workspace = true } static_assertions = "1.1.0" sp-api = { path = "../../../substrate/primitives/api", default-features = false } @@ -69,7 +69,7 @@ pallet-babe = { path = "../../../substrate/frame/babe" } pallet-treasury = { path = "../../../substrate/frame/treasury" } sp-keystore = { path = "../../../substrate/primitives/keystore" } sp-keyring = { path = "../../../substrate/primitives/keyring" } -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } libsecp256k1 = "0.7.0" test-helpers = { package = "polkadot-primitives-test-helpers", path = "../../primitives/test-helpers" } diff --git a/polkadot/runtime/parachains/Cargo.toml b/polkadot/runtime/parachains/Cargo.toml index 1de34400758..311a62b6c91 100644 --- a/polkadot/runtime/parachains/Cargo.toml +++ b/polkadot/runtime/parachains/Cargo.toml @@ -16,7 +16,7 @@ parity-scale-codec = { version = "3.6.1", default-features = false, features = [ log = { workspace = true } rustc-hex = { version = "2.1.0", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"] } +serde = { features = ["alloc", "derive"], workspace = true } derive_more = "0.99.17" bitflags = "1.3.2" @@ -69,7 +69,7 @@ sp-tracing = { path = "../../../substrate/primitives/tracing" } sp-crypto-hashing = { path = "../../../substrate/primitives/crypto/hashing" } thousands = "0.2.0" assert_matches = "1" -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/polkadot/runtime/rococo/Cargo.toml b/polkadot/runtime/rococo/Cargo.toml index 68ded4aeaec..3dc59cc1728 100644 --- a/polkadot/runtime/rococo/Cargo.toml +++ b/polkadot/runtime/rococo/Cargo.toml @@ -14,8 +14,8 @@ workspace = true parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } log = { workspace = true } -serde = { version = "1.0.196", default-features = false } -serde_derive = { version = "1.0.117", optional = true } +serde = { workspace = true } +serde_derive = { optional = true, workspace = true } static_assertions = "1.1.0" smallvec = "1.8.0" @@ -111,7 +111,7 @@ keyring = { package = "sp-keyring", path = "../../../substrate/primitives/keyrin remote-externalities = { package = "frame-remote-externalities", path = "../../../substrate/utils/frame/remote-externalities" } sp-trie = { path = "../../../substrate/primitives/trie" } separator = "0.4.1" -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } sp-tracing = { path = "../../../substrate/primitives/tracing", default-features = false } tokio = { version = "1.24.2", features = ["macros"] } diff --git a/polkadot/runtime/test-runtime/Cargo.toml b/polkadot/runtime/test-runtime/Cargo.toml index 1f041fdfeaa..9753a409304 100644 --- a/polkadot/runtime/test-runtime/Cargo.toml +++ b/polkadot/runtime/test-runtime/Cargo.toml @@ -16,8 +16,8 @@ parity-scale-codec = { version = "3.6.1", default-features = false, features = [ log = { workspace = true } rustc-hex = { version = "2.1.0", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", default-features = false } -serde_derive = { version = "1.0.117", optional = true } +serde = { workspace = true } +serde_derive = { optional = true, workspace = true } smallvec = "1.8.0" authority-discovery-primitives = { package = "sp-authority-discovery", path = "../../../substrate/primitives/authority-discovery", default-features = false } @@ -74,7 +74,7 @@ hex-literal = "0.4.1" tiny-keccak = { version = "2.0.2", features = ["keccak"] } keyring = { package = "sp-keyring", path = "../../../substrate/primitives/keyring" } sp-trie = { path = "../../../substrate/primitives/trie" } -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } [build-dependencies] substrate-wasm-builder = { path = "../../../substrate/utils/wasm-builder" } diff --git a/polkadot/runtime/westend/Cargo.toml b/polkadot/runtime/westend/Cargo.toml index 31b3f8351d8..4180828bcfb 100644 --- a/polkadot/runtime/westend/Cargo.toml +++ b/polkadot/runtime/westend/Cargo.toml @@ -16,8 +16,8 @@ parity-scale-codec = { version = "3.6.1", default-features = false, features = [ scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } log = { workspace = true } rustc-hex = { version = "2.1.0", default-features = false } -serde = { version = "1.0.196", default-features = false } -serde_derive = { version = "1.0.117", optional = true } +serde = { workspace = true } +serde_derive = { optional = true, workspace = true } smallvec = "1.8.0" authority-discovery-primitives = { package = "sp-authority-discovery", path = "../../../substrate/primitives/authority-discovery", default-features = false } @@ -119,7 +119,7 @@ xcm-builder = { package = "staging-xcm-builder", path = "../../xcm/xcm-builder", hex-literal = "0.4.1" tiny-keccak = { version = "2.0.2", features = ["keccak"] } keyring = { package = "sp-keyring", path = "../../../substrate/primitives/keyring" } -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } remote-externalities = { package = "frame-remote-externalities", path = "../../../substrate/utils/frame/remote-externalities" } tokio = { version = "1.24.2", features = ["macros"] } sp-tracing = { path = "../../../substrate/primitives/tracing", default-features = false } diff --git a/polkadot/xcm/Cargo.toml b/polkadot/xcm/Cargo.toml index 888757150b4..f9ccfb9833a 100644 --- a/polkadot/xcm/Cargo.toml +++ b/polkadot/xcm/Cargo.toml @@ -18,7 +18,7 @@ log = { workspace = true } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } sp-weights = { path = "../../substrate/primitives/weights", default-features = false, features = ["serde"] } -serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive", "rc"] } +serde = { features = ["alloc", "derive", "rc"], workspace = true } schemars = { version = "0.8.13", default-features = true, optional = true } xcm-procedural = { path = "procedural" } environmental = { version = "1.1.4", default-features = false } diff --git a/polkadot/xcm/pallet-xcm/Cargo.toml b/polkadot/xcm/pallet-xcm/Cargo.toml index ac39e5f7429..4840b6127f5 100644 --- a/polkadot/xcm/pallet-xcm/Cargo.toml +++ b/polkadot/xcm/pallet-xcm/Cargo.toml @@ -13,7 +13,7 @@ workspace = true bounded-collections = { version = "0.2.0", default-features = false } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", optional = true, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } log = { workspace = true } frame-support = { path = "../../../substrate/frame/support", default-features = false } diff --git a/polkadot/xcm/procedural/Cargo.toml b/polkadot/xcm/procedural/Cargo.toml index 1af4d2db4f1..ca9fb351bd3 100644 --- a/polkadot/xcm/procedural/Cargo.toml +++ b/polkadot/xcm/procedural/Cargo.toml @@ -15,8 +15,8 @@ proc-macro = true [dependencies] proc-macro2 = "1.0.56" -quote = "1.0.28" -syn = "2.0.49" +quote = { workspace = true } +syn = { workspace = true } Inflector = "0.11.4" [dev-dependencies] diff --git a/substrate/bin/minimal/node/Cargo.toml b/substrate/bin/minimal/node/Cargo.toml index 02b3b0a735b..65644861c9a 100644 --- a/substrate/bin/minimal/node/Cargo.toml +++ b/substrate/bin/minimal/node/Cargo.toml @@ -24,7 +24,7 @@ clap = { version = "4.5.1", features = ["derive"] } futures = { version = "0.3.21", features = ["thread-pool"] } futures-timer = "3.0.1" jsonrpsee = { version = "0.22", features = ["server"] } -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } sc-cli = { path = "../../../client/cli" } sc-executor = { path = "../../../client/executor" } diff --git a/substrate/bin/node-template/node/Cargo.toml b/substrate/bin/node-template/node/Cargo.toml index 7a6955d2cec..9cba2b5a366 100644 --- a/substrate/bin/node-template/node/Cargo.toml +++ b/substrate/bin/node-template/node/Cargo.toml @@ -22,7 +22,7 @@ name = "node-template" [dependencies] clap = { version = "4.5.1", features = ["derive"] } futures = { version = "0.3.21", features = ["thread-pool"] } -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } sc-cli = { path = "../../../client/cli" } sp-core = { path = "../../../primitives/core" } diff --git a/substrate/bin/node-template/runtime/Cargo.toml b/substrate/bin/node-template/runtime/Cargo.toml index 95d92e6eadf..86bab0ca375 100644 --- a/substrate/bin/node-template/runtime/Cargo.toml +++ b/substrate/bin/node-template/runtime/Cargo.toml @@ -42,7 +42,7 @@ sp-std = { path = "../../../primitives/std", default-features = false } sp-storage = { path = "../../../primitives/storage", default-features = false } sp-transaction-pool = { path = "../../../primitives/transaction-pool", default-features = false } sp-version = { path = "../../../primitives/version", default-features = false, features = ["serde"] } -serde_json = { version = "1.0.113", default-features = false, features = ["alloc"] } +serde_json = { features = ["alloc"], workspace = true } sp-genesis-builder = { default-features = false, path = "../../../primitives/genesis-builder" } # Used for the node template's RPCs diff --git a/substrate/bin/node/bench/Cargo.toml b/substrate/bin/node/bench/Cargo.toml index ada58c79c77..8e0f7fb93b3 100644 --- a/substrate/bin/node/bench/Cargo.toml +++ b/substrate/bin/node/bench/Cargo.toml @@ -24,8 +24,8 @@ kitchensink-runtime = { path = "../runtime" } sc-client-api = { path = "../../../client/api" } sp-runtime = { path = "../../../primitives/runtime" } sp-state-machine = { path = "../../../primitives/state-machine" } -serde = "1.0.196" -serde_json = "1.0.113" +serde = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } derive_more = { version = "0.99.17", default-features = false, features = ["display"] } kvdb = "0.13.0" kvdb-rocksdb = "0.19.0" diff --git a/substrate/bin/node/cli/Cargo.toml b/substrate/bin/node/cli/Cargo.toml index aed2101a3f5..abe284c41da 100644 --- a/substrate/bin/node/cli/Cargo.toml +++ b/substrate/bin/node/cli/Cargo.toml @@ -43,7 +43,7 @@ crate-type = ["cdylib", "rlib"] array-bytes = "6.1" clap = { version = "4.5.1", features = ["derive"], optional = true } codec = { package = "parity-scale-codec", version = "3.6.1" } -serde = { version = "1.0.196", features = ["derive"] } +serde = { features = ["derive"], workspace = true, default-features = true } jsonrpsee = { version = "0.22", features = ["server"] } futures = "0.3.21" log = { workspace = true, default-features = true } @@ -116,7 +116,7 @@ sc-cli = { path = "../../../client/cli", optional = true } frame-benchmarking-cli = { path = "../../../utils/frame/benchmarking-cli", optional = true } node-inspect = { package = "staging-node-inspect", path = "../inspect", optional = true } try-runtime-cli = { path = "../../../utils/frame/try-runtime/cli", optional = true } -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } [dev-dependencies] sc-keystore = { path = "../../../client/keystore" } @@ -159,7 +159,7 @@ sp-consensus-babe = { path = "../../../primitives/consensus/babe" } sp-externalities = { path = "../../../primitives/externalities" } sp-keyring = { path = "../../../primitives/keyring" } sp-runtime = { path = "../../../primitives/runtime" } -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } scale-info = { version = "2.10.0", features = ["derive", "serde"] } sp-trie = { path = "../../../primitives/trie" } sp-state-machine = { path = "../../../primitives/state-machine" } diff --git a/substrate/bin/node/inspect/Cargo.toml b/substrate/bin/node/inspect/Cargo.toml index 6c9be0fe1ac..7db6e3efd3a 100644 --- a/substrate/bin/node/inspect/Cargo.toml +++ b/substrate/bin/node/inspect/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] clap = { version = "4.5.1", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.6.1" } -thiserror = "1.0" +thiserror = { workspace = true } sc-cli = { path = "../../../client/cli" } sc-client-api = { path = "../../../client/api" } sc-service = { path = "../../../client/service", default-features = false } diff --git a/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml index 7bc608ba89e..aa13c3d292b 100644 --- a/substrate/bin/node/runtime/Cargo.toml +++ b/substrate/bin/node/runtime/Cargo.toml @@ -26,7 +26,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } static_assertions = "1.1.0" log = { workspace = true } -serde_json = { version = "1.0.113", default-features = false, features = ["alloc", "arbitrary_precision"] } +serde_json = { features = ["alloc", "arbitrary_precision"], workspace = true } # pallet-asset-conversion: turn on "num-traits" feature primitive-types = { version = "0.12.0", default-features = false, features = ["codec", "num-traits", "scale-info"] } diff --git a/substrate/bin/utils/chain-spec-builder/Cargo.toml b/substrate/bin/utils/chain-spec-builder/Cargo.toml index 8b3180fbbcd..996372c8a0f 100644 --- a/substrate/bin/utils/chain-spec-builder/Cargo.toml +++ b/substrate/bin/utils/chain-spec-builder/Cargo.toml @@ -26,5 +26,5 @@ crate-type = ["rlib"] clap = { version = "4.5.1", features = ["derive"] } log = { workspace = true, default-features = true } sc-chain-spec = { path = "../../../client/chain-spec" } -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } sp-tracing = { path = "../../../primitives/tracing" } diff --git a/substrate/client/allocator/Cargo.toml b/substrate/client/allocator/Cargo.toml index 662125814fb..2c268b548ea 100644 --- a/substrate/client/allocator/Cargo.toml +++ b/substrate/client/allocator/Cargo.toml @@ -18,6 +18,6 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] log = { workspace = true, default-features = true } -thiserror = "1.0.48" +thiserror = { workspace = true } sp-core = { path = "../../primitives/core" } sp-wasm-interface = { path = "../../primitives/wasm-interface" } diff --git a/substrate/client/api/Cargo.toml b/substrate/client/api/Cargo.toml index 09f60a47670..cd7b613e277 100644 --- a/substrate/client/api/Cargo.toml +++ b/substrate/client/api/Cargo.toml @@ -41,6 +41,6 @@ sp-storage = { path = "../../primitives/storage" } sp-trie = { path = "../../primitives/trie" } [dev-dependencies] -thiserror = "1.0.48" +thiserror = { workspace = true } sp-test-primitives = { path = "../../primitives/test-primitives" } substrate-test-runtime = { path = "../../test-utils/runtime" } diff --git a/substrate/client/authority-discovery/Cargo.toml b/substrate/client/authority-discovery/Cargo.toml index 78f7144f436..cdd4052f0b0 100644 --- a/substrate/client/authority-discovery/Cargo.toml +++ b/substrate/client/authority-discovery/Cargo.toml @@ -32,7 +32,7 @@ multihash = { version = "0.18.1", default-features = false, features = [ log = { workspace = true, default-features = true } prost = "0.12" rand = "0.8.5" -thiserror = "1.0" +thiserror = { workspace = true } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } sc-client-api = { path = "../api" } sc-network = { path = "../network" } diff --git a/substrate/client/chain-spec/Cargo.toml b/substrate/client/chain-spec/Cargo.toml index 82a5f7f55b0..f2138c07d71 100644 --- a/substrate/client/chain-spec/Cargo.toml +++ b/substrate/client/chain-spec/Cargo.toml @@ -18,8 +18,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } memmap2 = "0.9.3" -serde = { version = "1.0.196", features = ["derive"] } -serde_json = "1.0.113" +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sc-client-api = { path = "../api" } sc-chain-spec-derive = { path = "derive" } sc-executor = { path = "../executor" } diff --git a/substrate/client/chain-spec/derive/Cargo.toml b/substrate/client/chain-spec/derive/Cargo.toml index 4e95206df6b..521eee578ec 100644 --- a/substrate/client/chain-spec/derive/Cargo.toml +++ b/substrate/client/chain-spec/derive/Cargo.toml @@ -20,5 +20,5 @@ proc-macro = true [dependencies] proc-macro-crate = "3.0.0" proc-macro2 = "1.0.56" -quote = "1.0.28" -syn = "2.0.49" +quote = { workspace = true } +syn = { workspace = true } diff --git a/substrate/client/cli/Cargo.toml b/substrate/client/cli/Cargo.toml index 1b4d3733baa..0582018283c 100644 --- a/substrate/client/cli/Cargo.toml +++ b/substrate/client/cli/Cargo.toml @@ -29,9 +29,9 @@ parity-scale-codec = "3.6.1" rand = "0.8.5" regex = "1.6.0" rpassword = "7.0.0" -serde = "1.0.196" -serde_json = "1.0.113" -thiserror = "1.0.48" +serde = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } +thiserror = { workspace = true } bip39 = "2.0.0" tokio = { version = "1.22.0", features = ["parking_lot", "rt-multi-thread", "signal"] } sc-client-api = { path = "../api" } diff --git a/substrate/client/consensus/aura/Cargo.toml b/substrate/client/consensus/aura/Cargo.toml index d1076da35f9..213f75974da 100644 --- a/substrate/client/consensus/aura/Cargo.toml +++ b/substrate/client/consensus/aura/Cargo.toml @@ -20,7 +20,7 @@ async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" log = { workspace = true, default-features = true } -thiserror = "1.0" +thiserror = { workspace = true } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-block-builder = { path = "../../block-builder" } sc-client-api = { path = "../../api" } diff --git a/substrate/client/consensus/babe/Cargo.toml b/substrate/client/consensus/babe/Cargo.toml index 9bfed7e541b..c98fb7112b7 100644 --- a/substrate/client/consensus/babe/Cargo.toml +++ b/substrate/client/consensus/babe/Cargo.toml @@ -25,7 +25,7 @@ num-bigint = "0.4.3" num-rational = "0.4.1" num-traits = "0.2.17" parking_lot = "0.12.1" -thiserror = "1.0" +thiserror = { workspace = true } fork-tree = { path = "../../../utils/fork-tree" } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-client-api = { path = "../../api" } diff --git a/substrate/client/consensus/babe/rpc/Cargo.toml b/substrate/client/consensus/babe/rpc/Cargo.toml index 342c9259cc6..043b566673e 100644 --- a/substrate/client/consensus/babe/rpc/Cargo.toml +++ b/substrate/client/consensus/babe/rpc/Cargo.toml @@ -18,8 +18,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } futures = "0.3.21" -serde = { version = "1.0.196", features = ["derive"] } -thiserror = "1.0" +serde = { features = ["derive"], workspace = true, default-features = true } +thiserror = { workspace = true } sc-consensus-babe = { path = ".." } sc-consensus-epochs = { path = "../../epochs" } sc-rpc-api = { path = "../../../rpc-api" } @@ -33,7 +33,7 @@ sp-keystore = { path = "../../../../primitives/keystore" } sp-runtime = { path = "../../../../primitives/runtime" } [dev-dependencies] -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } tokio = "1.22.0" sc-consensus = { path = "../../common" } sc-keystore = { path = "../../../keystore" } diff --git a/substrate/client/consensus/beefy/Cargo.toml b/substrate/client/consensus/beefy/Cargo.toml index d6b8a57b988..8552a490022 100644 --- a/substrate/client/consensus/beefy/Cargo.toml +++ b/substrate/client/consensus/beefy/Cargo.toml @@ -20,7 +20,7 @@ fnv = "1.0.6" futures = "0.3" log = { workspace = true, default-features = true } parking_lot = "0.12.1" -thiserror = "1.0" +thiserror = { workspace = true } wasm-timer = "0.2.5" prometheus = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-client-api = { path = "../../api" } @@ -44,7 +44,7 @@ tokio = "1.22.0" [dev-dependencies] -serde = "1.0.196" +serde = { workspace = true, default-features = true } tempfile = "3.1.0" sc-block-builder = { path = "../../block-builder" } sc-network-test = { path = "../../network/test" } diff --git a/substrate/client/consensus/beefy/rpc/Cargo.toml b/substrate/client/consensus/beefy/rpc/Cargo.toml index e83c95ab106..bb2ae4a0896 100644 --- a/substrate/client/consensus/beefy/rpc/Cargo.toml +++ b/substrate/client/consensus/beefy/rpc/Cargo.toml @@ -17,8 +17,8 @@ futures = "0.3.21" jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } log = { workspace = true, default-features = true } parking_lot = "0.12.1" -serde = { version = "1.0.196", features = ["derive"] } -thiserror = "1.0" +serde = { features = ["derive"], workspace = true, default-features = true } +thiserror = { workspace = true } sc-consensus-beefy = { path = ".." } sp-consensus-beefy = { path = "../../../../primitives/consensus/beefy" } sc-rpc = { path = "../../../rpc" } @@ -26,7 +26,7 @@ sp-core = { path = "../../../../primitives/core" } sp-runtime = { path = "../../../../primitives/runtime" } [dev-dependencies] -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } sc-rpc = { path = "../../../rpc", features = ["test-helpers"] } substrate-test-runtime-client = { path = "../../../../test-utils/runtime/client" } tokio = { version = "1.22.0", features = ["macros"] } diff --git a/substrate/client/consensus/common/Cargo.toml b/substrate/client/consensus/common/Cargo.toml index 496e6c82740..7f36dfc09ef 100644 --- a/substrate/client/consensus/common/Cargo.toml +++ b/substrate/client/consensus/common/Cargo.toml @@ -23,8 +23,8 @@ libp2p-identity = { version = "0.1.3", features = ["ed25519", "peerid"] } log = { workspace = true, default-features = true } mockall = "0.11.3" parking_lot = "0.12.1" -serde = { version = "1.0", features = ["derive"] } -thiserror = "1.0.48" +serde = { features = ["derive"], workspace = true, default-features = true } +thiserror = { workspace = true } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-client-api = { path = "../../api" } sc-utils = { path = "../../utils" } diff --git a/substrate/client/consensus/grandpa/Cargo.toml b/substrate/client/consensus/grandpa/Cargo.toml index e342377d820..1ab95352522 100644 --- a/substrate/client/consensus/grandpa/Cargo.toml +++ b/substrate/client/consensus/grandpa/Cargo.toml @@ -28,8 +28,8 @@ log = { workspace = true, default-features = true } parity-scale-codec = { version = "3.6.1", features = ["derive"] } parking_lot = "0.12.1" rand = "0.8.5" -serde_json = "1.0.113" -thiserror = "1.0" +serde_json = { workspace = true, default-features = true } +thiserror = { workspace = true } fork-tree = { path = "../../../utils/fork-tree" } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-block-builder = { path = "../../block-builder" } @@ -57,7 +57,7 @@ sp-runtime = { path = "../../../primitives/runtime" } [dev-dependencies] assert_matches = "1.3.0" finality-grandpa = { version = "0.16.2", features = ["derive-codec", "test-helpers"] } -serde = "1.0.196" +serde = { workspace = true, default-features = true } tokio = "1.22.0" sc-network = { path = "../../network" } sc-network-test = { path = "../../network/test" } diff --git a/substrate/client/consensus/grandpa/rpc/Cargo.toml b/substrate/client/consensus/grandpa/rpc/Cargo.toml index ca0c2ac371a..f7e87415448 100644 --- a/substrate/client/consensus/grandpa/rpc/Cargo.toml +++ b/substrate/client/consensus/grandpa/rpc/Cargo.toml @@ -18,8 +18,8 @@ futures = "0.3.16" jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } log = { workspace = true, default-features = true } parity-scale-codec = { version = "3.6.1", features = ["derive"] } -serde = { version = "1.0.196", features = ["derive"] } -thiserror = "1.0" +serde = { features = ["derive"], workspace = true, default-features = true } +thiserror = { workspace = true } sc-client-api = { path = "../../../api" } sc-consensus-grandpa = { path = ".." } sc-rpc = { path = "../../../rpc" } diff --git a/substrate/client/consensus/manual-seal/Cargo.toml b/substrate/client/consensus/manual-seal/Cargo.toml index 51745c996aa..ac32fed7228 100644 --- a/substrate/client/consensus/manual-seal/Cargo.toml +++ b/substrate/client/consensus/manual-seal/Cargo.toml @@ -23,8 +23,8 @@ codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" futures-timer = "3.0.1" log = { workspace = true, default-features = true } -serde = { version = "1.0", features = ["derive"] } -thiserror = "1.0" +serde = { features = ["derive"], workspace = true, default-features = true } +thiserror = { workspace = true } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-client-api = { path = "../../api" } sc-consensus = { path = "../common" } diff --git a/substrate/client/consensus/pow/Cargo.toml b/substrate/client/consensus/pow/Cargo.toml index 6caccb9879d..0791514035b 100644 --- a/substrate/client/consensus/pow/Cargo.toml +++ b/substrate/client/consensus/pow/Cargo.toml @@ -22,7 +22,7 @@ futures = "0.3.21" futures-timer = "3.0.1" log = { workspace = true, default-features = true } parking_lot = "0.12.1" -thiserror = "1.0" +thiserror = { workspace = true } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-client-api = { path = "../../api" } sc-consensus = { path = "../common" } diff --git a/substrate/client/executor/common/Cargo.toml b/substrate/client/executor/common/Cargo.toml index 648fb9f0f50..bfd1fa6b740 100644 --- a/substrate/client/executor/common/Cargo.toml +++ b/substrate/client/executor/common/Cargo.toml @@ -17,7 +17,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -thiserror = "1.0.48" +thiserror = { workspace = true } wasm-instrument = "0.4" sc-allocator = { path = "../../allocator" } sp-maybe-compressed-blob = { path = "../../../primitives/maybe-compressed-blob" } diff --git a/substrate/client/keystore/Cargo.toml b/substrate/client/keystore/Cargo.toml index f7f3b138ec8..908e0aa8f38 100644 --- a/substrate/client/keystore/Cargo.toml +++ b/substrate/client/keystore/Cargo.toml @@ -19,8 +19,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] array-bytes = "6.1" parking_lot = "0.12.1" -serde_json = "1.0.113" -thiserror = "1.0" +serde_json = { workspace = true, default-features = true } +thiserror = { workspace = true } sp-application-crypto = { path = "../../primitives/application-crypto" } sp-core = { path = "../../primitives/core" } sp-keystore = { path = "../../primitives/keystore" } diff --git a/substrate/client/merkle-mountain-range/rpc/Cargo.toml b/substrate/client/merkle-mountain-range/rpc/Cargo.toml index 901c573b6bd..9b391b76ea0 100644 --- a/substrate/client/merkle-mountain-range/rpc/Cargo.toml +++ b/substrate/client/merkle-mountain-range/rpc/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } -serde = { version = "1.0.196", features = ["derive"] } +serde = { features = ["derive"], workspace = true, default-features = true } sp-api = { path = "../../../primitives/api" } sp-blockchain = { path = "../../../primitives/blockchain" } sp-core = { path = "../../../primitives/core" } @@ -25,4 +25,4 @@ sp-mmr-primitives = { path = "../../../primitives/merkle-mountain-range" } sp-runtime = { path = "../../../primitives/runtime" } [dev-dependencies] -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } diff --git a/substrate/client/mixnet/Cargo.toml b/substrate/client/mixnet/Cargo.toml index 8bcd963bd56..736184f4668 100644 --- a/substrate/client/mixnet/Cargo.toml +++ b/substrate/client/mixnet/Cargo.toml @@ -37,4 +37,4 @@ sp-core = { path = "../../primitives/core" } sp-keystore = { path = "../../primitives/keystore" } sp-mixnet = { path = "../../primitives/mixnet" } sp-runtime = { path = "../../primitives/runtime" } -thiserror = "1.0" +thiserror = { workspace = true } diff --git a/substrate/client/network/Cargo.toml b/substrate/client/network/Cargo.toml index d2ca8ba8c3f..cbf74440dc1 100644 --- a/substrate/client/network/Cargo.toml +++ b/substrate/client/network/Cargo.toml @@ -36,10 +36,10 @@ parking_lot = "0.12.1" partial_sort = "0.2.0" pin-project = "1.0.12" rand = "0.8.5" -serde = { version = "1.0.196", features = ["derive"] } -serde_json = "1.0.113" +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } smallvec = "1.11.0" -thiserror = "1.0" +thiserror = { workspace = true } tokio = { version = "1.22.0", features = ["macros", "sync"] } tokio-stream = "0.1.7" unsigned-varint = { version = "0.7.1", features = ["asynchronous_codec", "futures"] } diff --git a/substrate/client/network/bitswap/Cargo.toml b/substrate/client/network/bitswap/Cargo.toml index 63544b069e5..7ef3ea21242 100644 --- a/substrate/client/network/bitswap/Cargo.toml +++ b/substrate/client/network/bitswap/Cargo.toml @@ -25,7 +25,7 @@ futures = "0.3.21" libp2p-identity = { version = "0.1.3", features = ["peerid"] } log = { workspace = true, default-features = true } prost = "0.12" -thiserror = "1.0" +thiserror = { workspace = true } unsigned-varint = { version = "0.7.1", features = ["asynchronous_codec", "futures"] } sc-client-api = { path = "../../api" } sc-network = { path = ".." } diff --git a/substrate/client/network/light/Cargo.toml b/substrate/client/network/light/Cargo.toml index a83be7538be..c757f727fb7 100644 --- a/substrate/client/network/light/Cargo.toml +++ b/substrate/client/network/light/Cargo.toml @@ -33,4 +33,4 @@ sc-client-api = { path = "../../api" } sc-network = { path = ".." } sp-core = { path = "../../../primitives/core" } sp-runtime = { path = "../../../primitives/runtime" } -thiserror = "1.0" +thiserror = { workspace = true } diff --git a/substrate/client/network/sync/Cargo.toml b/substrate/client/network/sync/Cargo.toml index 19f86e87ac5..32ba3b6356c 100644 --- a/substrate/client/network/sync/Cargo.toml +++ b/substrate/client/network/sync/Cargo.toml @@ -31,7 +31,7 @@ mockall = "0.11.3" prost = "0.12" schnellru = "0.2.1" smallvec = "1.11.0" -thiserror = "1.0" +thiserror = { workspace = true } tokio-stream = "0.1.14" tokio = { version = "1.32.0", features = ["macros", "time"] } fork-tree = { path = "../../../utils/fork-tree" } diff --git a/substrate/client/rpc-api/Cargo.toml b/substrate/client/rpc-api/Cargo.toml index 27f8a1b9062..1b7af6a4a52 100644 --- a/substrate/client/rpc-api/Cargo.toml +++ b/substrate/client/rpc-api/Cargo.toml @@ -18,9 +18,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", features = ["derive"] } -serde_json = "1.0.113" -thiserror = "1.0" +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } +thiserror = { workspace = true } sc-chain-spec = { path = "../chain-spec" } sc-mixnet = { path = "../mixnet" } sc-transaction-pool-api = { path = "../transaction-pool/api" } diff --git a/substrate/client/rpc-servers/Cargo.toml b/substrate/client/rpc-servers/Cargo.toml index 3e04b927a22..7f7a86799e0 100644 --- a/substrate/client/rpc-servers/Cargo.toml +++ b/substrate/client/rpc-servers/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] jsonrpsee = { version = "0.22", features = ["server"] } log = { workspace = true, default-features = true } -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } tokio = { version = "1.22.0", features = ["parking_lot"] } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } tower-http = { version = "0.4.0", features = ["cors"] } diff --git a/substrate/client/rpc-spec-v2/Cargo.toml b/substrate/client/rpc-spec-v2/Cargo.toml index cb109becaf2..c62b3e789d3 100644 --- a/substrate/client/rpc-spec-v2/Cargo.toml +++ b/substrate/client/rpc-spec-v2/Cargo.toml @@ -31,8 +31,8 @@ sc-client-api = { path = "../api" } sc-utils = { path = "../utils" } sc-rpc = { path = "../rpc" } codec = { package = "parity-scale-codec", version = "3.6.1" } -thiserror = "1.0" -serde = "1.0" +thiserror = { workspace = true } +serde = { workspace = true, default-features = true } hex = "0.4" futures = "0.3.21" parking_lot = "0.12.1" @@ -44,7 +44,7 @@ futures-util = { version = "0.3.30", default-features = false } rand = "0.8.5" [dev-dependencies] -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } tokio = { version = "1.22.0", features = ["macros"] } substrate-test-runtime-client = { path = "../../test-utils/runtime/client" } substrate-test-runtime = { path = "../../test-utils/runtime" } diff --git a/substrate/client/rpc/Cargo.toml b/substrate/client/rpc/Cargo.toml index aa97eb87bb5..f65e6c9a59e 100644 --- a/substrate/client/rpc/Cargo.toml +++ b/substrate/client/rpc/Cargo.toml @@ -21,7 +21,7 @@ futures = "0.3.21" jsonrpsee = { version = "0.22", features = ["server"] } log = { workspace = true, default-features = true } parking_lot = "0.12.1" -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } sc-block-builder = { path = "../block-builder" } sc-chain-spec = { path = "../chain-spec" } sc-client-api = { path = "../api" } diff --git a/substrate/client/service/Cargo.toml b/substrate/client/service/Cargo.toml index a6fa360b74f..73edceb2ef3 100644 --- a/substrate/client/service/Cargo.toml +++ b/substrate/client/service/Cargo.toml @@ -29,7 +29,7 @@ runtime-benchmarks = [ [dependencies] jsonrpsee = { version = "0.22", features = ["server"] } -thiserror = "1.0.48" +thiserror = { workspace = true } futures = "0.3.21" rand = "0.8.5" parking_lot = "0.12.1" @@ -37,8 +37,8 @@ log = { workspace = true, default-features = true } futures-timer = "3.0.1" exit-future = "0.2.0" pin-project = "1.0.12" -serde = "1.0.196" -serde_json = "1.0.113" +serde = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sc-keystore = { path = "../keystore" } sp-runtime = { path = "../../primitives/runtime" } sp-trie = { path = "../../primitives/trie" } diff --git a/substrate/client/storage-monitor/Cargo.toml b/substrate/client/storage-monitor/Cargo.toml index b0f04644142..b2120b3efc4 100644 --- a/substrate/client/storage-monitor/Cargo.toml +++ b/substrate/client/storage-monitor/Cargo.toml @@ -17,4 +17,4 @@ log = { workspace = true, default-features = true } fs4 = "0.7.0" sp-core = { path = "../../primitives/core" } tokio = { version = "1.22.0", features = ["time"] } -thiserror = "1.0.48" +thiserror = { workspace = true } diff --git a/substrate/client/sync-state-rpc/Cargo.toml b/substrate/client/sync-state-rpc/Cargo.toml index fc0efd6630c..09dc611caa0 100644 --- a/substrate/client/sync-state-rpc/Cargo.toml +++ b/substrate/client/sync-state-rpc/Cargo.toml @@ -17,9 +17,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } -serde = { version = "1.0.196", features = ["derive"] } -serde_json = "1.0.113" -thiserror = "1.0.48" +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } +thiserror = { workspace = true } sc-chain-spec = { path = "../chain-spec" } sc-client-api = { path = "../api" } sc-consensus-babe = { path = "../consensus/babe" } diff --git a/substrate/client/sysinfo/Cargo.toml b/substrate/client/sysinfo/Cargo.toml index ede0bccaedc..ba58452ffb5 100644 --- a/substrate/client/sysinfo/Cargo.toml +++ b/substrate/client/sysinfo/Cargo.toml @@ -24,8 +24,8 @@ rand = "0.8.5" rand_pcg = "0.3.1" derive_more = "0.99" regex = "1" -serde = { version = "1.0.196", features = ["derive"] } -serde_json = "1.0.113" +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sc-telemetry = { path = "../telemetry" } sp-core = { path = "../../primitives/core" } sp-crypto-hashing = { path = "../../primitives/crypto/hashing" } diff --git a/substrate/client/telemetry/Cargo.toml b/substrate/client/telemetry/Cargo.toml index e35fbc16142..8ab00202f0b 100644 --- a/substrate/client/telemetry/Cargo.toml +++ b/substrate/client/telemetry/Cargo.toml @@ -25,7 +25,7 @@ parking_lot = "0.12.1" pin-project = "1.0.12" sc-utils = { path = "../utils" } rand = "0.8.5" -serde = { version = "1.0.196", features = ["derive"] } -serde_json = "1.0.113" -thiserror = "1.0.48" +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } +thiserror = { workspace = true } wasm-timer = "0.2.5" diff --git a/substrate/client/tracing/Cargo.toml b/substrate/client/tracing/Cargo.toml index bd5a661bb3a..61e6f7d0bab 100644 --- a/substrate/client/tracing/Cargo.toml +++ b/substrate/client/tracing/Cargo.toml @@ -26,8 +26,8 @@ log = { workspace = true, default-features = true } parking_lot = "0.12.1" regex = "1.6.0" rustc-hash = "1.1.0" -serde = "1.0.196" -thiserror = "1.0.48" +serde = { workspace = true, default-features = true } +thiserror = { workspace = true } tracing = "0.1.29" tracing-log = "0.1.3" tracing-subscriber = { version = "0.2.25", features = ["parking_lot"] } diff --git a/substrate/client/tracing/proc-macro/Cargo.toml b/substrate/client/tracing/proc-macro/Cargo.toml index aa6ae7d6fd4..fec34aa0bca 100644 --- a/substrate/client/tracing/proc-macro/Cargo.toml +++ b/substrate/client/tracing/proc-macro/Cargo.toml @@ -20,5 +20,5 @@ proc-macro = true [dependencies] proc-macro-crate = "3.0.0" proc-macro2 = "1.0.56" -quote = { version = "1.0.28", features = ["proc-macro"] } -syn = { version = "2.0.49", features = ["extra-traits", "full", "parsing", "proc-macro"] } +quote = { features = ["proc-macro"], workspace = true } +syn = { features = ["extra-traits", "full", "parsing", "proc-macro"], workspace = true } diff --git a/substrate/client/transaction-pool/Cargo.toml b/substrate/client/transaction-pool/Cargo.toml index c7a2540f8ed..2ca37afd61b 100644 --- a/substrate/client/transaction-pool/Cargo.toml +++ b/substrate/client/transaction-pool/Cargo.toml @@ -23,8 +23,8 @@ futures-timer = "3.0.2" linked-hash-map = "0.5.4" log = { workspace = true, default-features = true } parking_lot = "0.12.1" -serde = { version = "1.0.196", features = ["derive"] } -thiserror = "1.0.48" +serde = { features = ["derive"], workspace = true, default-features = true } +thiserror = { workspace = true } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } sc-client-api = { path = "../api" } sc-transaction-pool-api = { path = "api" } diff --git a/substrate/client/transaction-pool/api/Cargo.toml b/substrate/client/transaction-pool/api/Cargo.toml index 79449b2ee87..d52e4783fab 100644 --- a/substrate/client/transaction-pool/api/Cargo.toml +++ b/substrate/client/transaction-pool/api/Cargo.toml @@ -16,11 +16,11 @@ async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" log = { workspace = true, default-features = true } -serde = { version = "1.0.196", features = ["derive"] } -thiserror = "1.0.48" +serde = { features = ["derive"], workspace = true, default-features = true } +thiserror = { workspace = true } sp-blockchain = { path = "../../../primitives/blockchain" } sp-core = { path = "../../../primitives/core", default-features = false } sp-runtime = { path = "../../../primitives/runtime", default-features = false } [dev-dependencies] -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } diff --git a/substrate/frame/beefy-mmr/Cargo.toml b/substrate/frame/beefy-mmr/Cargo.toml index 25290890fcd..17707731773 100644 --- a/substrate/frame/beefy-mmr/Cargo.toml +++ b/substrate/frame/beefy-mmr/Cargo.toml @@ -16,7 +16,7 @@ array-bytes = { version = "6.1", optional = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", optional = true } +serde = { optional = true, workspace = true, default-features = true } binary-merkle-tree = { path = "../../utils/binary-merkle-tree", default-features = false } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/beefy/Cargo.toml b/substrate/frame/beefy/Cargo.toml index 866b032c331..e38eaa6fb07 100644 --- a/substrate/frame/beefy/Cargo.toml +++ b/substrate/frame/beefy/Cargo.toml @@ -15,7 +15,7 @@ workspace = true codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } -serde = { version = "1.0.196", optional = true } +serde = { optional = true, workspace = true, default-features = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } pallet-authorship = { path = "../authorship", default-features = false } diff --git a/substrate/frame/benchmarking/Cargo.toml b/substrate/frame/benchmarking/Cargo.toml index 8be7a500d3f..bf42aae979c 100644 --- a/substrate/frame/benchmarking/Cargo.toml +++ b/substrate/frame/benchmarking/Cargo.toml @@ -21,7 +21,7 @@ linregress = { version = "0.5.1", optional = true } log = { workspace = true } paste = "1.0" scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", optional = true } +serde = { optional = true, workspace = true, default-features = true } frame-support = { path = "../support", default-features = false } frame-support-procedural = { path = "../support/procedural", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/contracts/Cargo.toml b/substrate/frame/contracts/Cargo.toml index 0b127a5a454..be3bafcd23f 100644 --- a/substrate/frame/contracts/Cargo.toml +++ b/substrate/frame/contracts/Cargo.toml @@ -25,7 +25,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = ] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } log = { workspace = true } -serde = { version = "1", optional = true, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } smallvec = { version = "1", default-features = false, features = [ "const_generics", ] } diff --git a/substrate/frame/contracts/proc-macro/Cargo.toml b/substrate/frame/contracts/proc-macro/Cargo.toml index 7e4a6c58666..4080cd0442d 100644 --- a/substrate/frame/contracts/proc-macro/Cargo.toml +++ b/substrate/frame/contracts/proc-macro/Cargo.toml @@ -19,5 +19,5 @@ proc-macro = true [dependencies] proc-macro2 = "1.0.56" -quote = "1.0.28" -syn = { version = "2.0.49", features = ["full"] } +quote = { workspace = true } +syn = { features = ["full"], workspace = true } diff --git a/substrate/frame/conviction-voting/Cargo.toml b/substrate/frame/conviction-voting/Cargo.toml index d4921bf7b4b..ff5af995026 100644 --- a/substrate/frame/conviction-voting/Cargo.toml +++ b/substrate/frame/conviction-voting/Cargo.toml @@ -22,7 +22,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = "max-encoded-len", ] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", features = ["derive"], optional = true } +serde = { features = ["derive"], optional = true, workspace = true, default-features = true } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/democracy/Cargo.toml b/substrate/frame/democracy/Cargo.toml index e1e8964dd9c..9a55cda5340 100644 --- a/substrate/frame/democracy/Cargo.toml +++ b/substrate/frame/democracy/Cargo.toml @@ -20,7 +20,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = "derive", ] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", features = ["derive"], optional = true } +serde = { features = ["derive"], optional = true, workspace = true, default-features = true } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/election-provider-support/solution-type/Cargo.toml b/substrate/frame/election-provider-support/solution-type/Cargo.toml index b5d6d1dca54..1bf1165229a 100644 --- a/substrate/frame/election-provider-support/solution-type/Cargo.toml +++ b/substrate/frame/election-provider-support/solution-type/Cargo.toml @@ -18,8 +18,8 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -syn = { version = "2.0.49", features = ["full", "visit"] } -quote = "1.0.28" +syn = { features = ["full", "visit"], workspace = true } +quote = { workspace = true } proc-macro2 = "1.0.56" proc-macro-crate = "3.0.0" diff --git a/substrate/frame/message-queue/Cargo.toml b/substrate/frame/message-queue/Cargo.toml index 104660a9886..8d9da7df39e 100644 --- a/substrate/frame/message-queue/Cargo.toml +++ b/substrate/frame/message-queue/Cargo.toml @@ -14,7 +14,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", optional = true, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } log = { workspace = true } environmental = { version = "1.1.4", default-features = false } diff --git a/substrate/frame/mixnet/Cargo.toml b/substrate/frame/mixnet/Cargo.toml index e0334b6692d..d1bb01dde1a 100644 --- a/substrate/frame/mixnet/Cargo.toml +++ b/substrate/frame/mixnet/Cargo.toml @@ -22,7 +22,7 @@ frame-support = { default-features = false, path = "../support" } frame-system = { default-features = false, path = "../system" } log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", default-features = false, features = ["derive"] } +serde = { features = ["derive"], workspace = true } sp-application-crypto = { default-features = false, path = "../../primitives/application-crypto" } sp-arithmetic = { default-features = false, path = "../../primitives/arithmetic" } sp-io = { default-features = false, path = "../../primitives/io" } diff --git a/substrate/frame/offences/Cargo.toml b/substrate/frame/offences/Cargo.toml index e1bf71f70df..b3ef4671ce5 100644 --- a/substrate/frame/offences/Cargo.toml +++ b/substrate/frame/offences/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", optional = true } +serde = { optional = true, workspace = true, default-features = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } pallet-balances = { path = "../balances", default-features = false } diff --git a/substrate/frame/parameters/Cargo.toml b/substrate/frame/parameters/Cargo.toml index 82059e3a73d..07ebeea52d5 100644 --- a/substrate/frame/parameters/Cargo.toml +++ b/substrate/frame/parameters/Cargo.toml @@ -11,7 +11,7 @@ edition.workspace = true codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["max-encoded-len"] } scale-info = { version = "2.1.2", default-features = false, features = ["derive"] } paste = { version = "1.0.14", default-features = false } -serde = { version = "1.0.196", features = ["derive"], optional = true } +serde = { features = ["derive"], optional = true, workspace = true, default-features = true } docify = "0.2.5" frame-support = { path = "../support", default-features = false, features = ["experimental"] } diff --git a/substrate/frame/referenda/Cargo.toml b/substrate/frame/referenda/Cargo.toml index 13c8cfc5961..2dfb25a2fd3 100644 --- a/substrate/frame/referenda/Cargo.toml +++ b/substrate/frame/referenda/Cargo.toml @@ -21,7 +21,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = "derive", ] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", features = ["derive"], optional = true } +serde = { features = ["derive"], optional = true, workspace = true, default-features = true } sp-arithmetic = { path = "../../primitives/arithmetic", default-features = false } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/remark/Cargo.toml b/substrate/frame/remark/Cargo.toml index c5f39a649ec..45710f0539e 100644 --- a/substrate/frame/remark/Cargo.toml +++ b/substrate/frame/remark/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", optional = true } +serde = { optional = true, workspace = true, default-features = true } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/staking/Cargo.toml b/substrate/frame/staking/Cargo.toml index 51aa8c79fad..d2a46146931 100644 --- a/substrate/frame/staking/Cargo.toml +++ b/substrate/frame/staking/Cargo.toml @@ -16,7 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"] } +serde = { features = ["alloc", "derive"], workspace = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } diff --git a/substrate/frame/staking/reward-curve/Cargo.toml b/substrate/frame/staking/reward-curve/Cargo.toml index bae730f446f..e2a2782db2d 100644 --- a/substrate/frame/staking/reward-curve/Cargo.toml +++ b/substrate/frame/staking/reward-curve/Cargo.toml @@ -20,8 +20,8 @@ proc-macro = true [dependencies] proc-macro-crate = "3.0.0" proc-macro2 = "1.0.56" -quote = "1.0.28" -syn = { version = "2.0.49", features = ["full", "visit"] } +quote = { workspace = true } +syn = { features = ["full", "visit"], workspace = true } [dev-dependencies] sp-runtime = { path = "../../../primitives/runtime" } diff --git a/substrate/frame/state-trie-migration/Cargo.toml b/substrate/frame/state-trie-migration/Cargo.toml index 5f86fb1595a..e837956613e 100644 --- a/substrate/frame/state-trie-migration/Cargo.toml +++ b/substrate/frame/state-trie-migration/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", optional = true } +serde = { optional = true, workspace = true, default-features = true } thousands = { version = "0.2.0", optional = true } zstd = { version = "0.12.4", default-features = false, optional = true } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } diff --git a/substrate/frame/support/Cargo.toml b/substrate/frame/support/Cargo.toml index 73abc1a6aed..72e2d0bfa56 100644 --- a/substrate/frame/support/Cargo.toml +++ b/substrate/frame/support/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] array-bytes = { version = "6.1", default-features = false } -serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"] } +serde = { features = ["alloc", "derive"], workspace = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-metadata = { version = "16.0.0", default-features = false, features = ["current"] } @@ -46,7 +46,7 @@ sp-crypto-hashing-proc-macro = { path = "../../primitives/crypto/hashing/proc-ma k256 = { version = "0.13.1", default-features = false, features = ["ecdsa"] } environmental = { version = "1.1.4", default-features = false } sp-genesis-builder = { path = "../../primitives/genesis-builder", default-features = false } -serde_json = { version = "1.0.113", default-features = false, features = ["alloc"] } +serde_json = { features = ["alloc"], workspace = true } docify = "0.2.7" static_assertions = "1.1.0" diff --git a/substrate/frame/support/procedural/Cargo.toml b/substrate/frame/support/procedural/Cargo.toml index f3434bd2491..85947503802 100644 --- a/substrate/frame/support/procedural/Cargo.toml +++ b/substrate/frame/support/procedural/Cargo.toml @@ -23,8 +23,8 @@ Inflector = "0.11.4" cfg-expr = "0.15.5" itertools = "0.10.3" proc-macro2 = "1.0.56" -quote = "1.0.28" -syn = { version = "2.0.49", features = ["full", "visit-mut"] } +quote = { workspace = true } +syn = { features = ["full", "visit-mut"], workspace = true } frame-support-procedural-tools = { path = "tools" } macro_magic = { version = "0.5.0", features = ["proc_support"] } proc-macro-warning = { version = "1.0.0", default-features = false } diff --git a/substrate/frame/support/procedural/tools/Cargo.toml b/substrate/frame/support/procedural/tools/Cargo.toml index 9be5ca0bfd4..a75307aca79 100644 --- a/substrate/frame/support/procedural/tools/Cargo.toml +++ b/substrate/frame/support/procedural/tools/Cargo.toml @@ -17,6 +17,6 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] proc-macro-crate = "3.0.0" proc-macro2 = "1.0.56" -quote = "1.0.28" -syn = { version = "2.0.49", features = ["extra-traits", "full", "visit"] } +quote = { workspace = true } +syn = { features = ["extra-traits", "full", "visit"], workspace = true } frame-support-procedural-tools-derive = { path = "derive" } diff --git a/substrate/frame/support/procedural/tools/derive/Cargo.toml b/substrate/frame/support/procedural/tools/derive/Cargo.toml index bb9935ba58c..b39d99a822f 100644 --- a/substrate/frame/support/procedural/tools/derive/Cargo.toml +++ b/substrate/frame/support/procedural/tools/derive/Cargo.toml @@ -19,5 +19,5 @@ proc-macro = true [dependencies] proc-macro2 = "1.0.56" -quote = { version = "1.0.28", features = ["proc-macro"] } -syn = { version = "2.0.49", features = ["extra-traits", "full", "parsing", "proc-macro"] } +quote = { features = ["proc-macro"], workspace = true } +syn = { features = ["extra-traits", "full", "parsing", "proc-macro"], workspace = true } diff --git a/substrate/frame/support/test/Cargo.toml b/substrate/frame/support/test/Cargo.toml index 1dbf46109c8..ae2c56a531f 100644 --- a/substrate/frame/support/test/Cargo.toml +++ b/substrate/frame/support/test/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] static_assertions = "1.1.0" -serde = { version = "1.0.196", default-features = false, features = ["derive"] } +serde = { features = ["derive"], workspace = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-metadata = { version = "16.0.0", default-features = false, features = ["current"] } diff --git a/substrate/frame/support/test/pallet/Cargo.toml b/substrate/frame/support/test/pallet/Cargo.toml index 94afab76d89..ca889faef87 100644 --- a/substrate/frame/support/test/pallet/Cargo.toml +++ b/substrate/frame/support/test/pallet/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", default-features = false, features = ["derive"] } +serde = { features = ["derive"], workspace = true } frame-support = { path = "../..", default-features = false } frame-system = { path = "../../../system", default-features = false } sp-runtime = { path = "../../../../primitives/runtime", default-features = false } diff --git a/substrate/frame/system/Cargo.toml b/substrate/frame/system/Cargo.toml index b852aa99b97..416969e9c47 100644 --- a/substrate/frame/system/Cargo.toml +++ b/substrate/frame/system/Cargo.toml @@ -20,7 +20,7 @@ cfg-if = "1.0" codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } -serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"] } +serde = { features = ["alloc", "derive"], workspace = true } frame-support = { path = "../support", default-features = false } sp-core = { path = "../../primitives/core", default-features = false, features = ["serde"] } sp-io = { path = "../../primitives/io", default-features = false } diff --git a/substrate/frame/tips/Cargo.toml b/substrate/frame/tips/Cargo.toml index ae308cf67f0..7339cf0a8cc 100644 --- a/substrate/frame/tips/Cargo.toml +++ b/substrate/frame/tips/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", features = ["derive"], optional = true } +serde = { features = ["derive"], optional = true, workspace = true, default-features = true } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/transaction-payment/Cargo.toml b/substrate/frame/transaction-payment/Cargo.toml index 56c1cc4caa7..275e1c01f92 100644 --- a/substrate/frame/transaction-payment/Cargo.toml +++ b/substrate/frame/transaction-payment/Cargo.toml @@ -20,7 +20,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = "derive", ] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", optional = true } +serde = { optional = true, workspace = true, default-features = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } sp-core = { path = "../../primitives/core", default-features = false } @@ -29,7 +29,7 @@ sp-runtime = { path = "../../primitives/runtime", default-features = false } sp-std = { path = "../../primitives/std", default-features = false } [dev-dependencies] -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } pallet-balances = { path = "../balances" } [features] diff --git a/substrate/frame/transaction-payment/asset-tx-payment/Cargo.toml b/substrate/frame/transaction-payment/asset-tx-payment/Cargo.toml index 9a80081e04f..1da3237df08 100644 --- a/substrate/frame/transaction-payment/asset-tx-payment/Cargo.toml +++ b/substrate/frame/transaction-payment/asset-tx-payment/Cargo.toml @@ -30,10 +30,10 @@ frame-benchmarking = { path = "../../benchmarking", default-features = false, op # Other dependencies codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", optional = true } +serde = { optional = true, workspace = true, default-features = true } [dev-dependencies] -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } sp-storage = { path = "../../../primitives/storage", default-features = false } diff --git a/substrate/frame/transaction-storage/Cargo.toml b/substrate/frame/transaction-storage/Cargo.toml index 59b6e578571..1386d9b5a56 100644 --- a/substrate/frame/transaction-storage/Cargo.toml +++ b/substrate/frame/transaction-storage/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] array-bytes = { version = "6.1", optional = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", optional = true } +serde = { optional = true, workspace = true, default-features = true } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/treasury/Cargo.toml b/substrate/frame/treasury/Cargo.toml index edc68b386bc..5f90904123d 100644 --- a/substrate/frame/treasury/Cargo.toml +++ b/substrate/frame/treasury/Cargo.toml @@ -23,7 +23,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = docify = "0.2.7" impl-trait-for-tuples = "0.2.2" scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", features = ["derive"], optional = true } +serde = { features = ["derive"], optional = true, workspace = true, default-features = true } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/primitives/api/Cargo.toml b/substrate/primitives/api/Cargo.toml index 3330d2a88de..f4b1d13c520 100644 --- a/substrate/primitives/api/Cargo.toml +++ b/substrate/primitives/api/Cargo.toml @@ -27,7 +27,7 @@ sp-version = { path = "../version", default-features = false } sp-state-machine = { path = "../state-machine", default-features = false, optional = true } sp-trie = { path = "../trie", default-features = false, optional = true } hash-db = { version = "0.16.0", optional = true } -thiserror = { version = "1.0.48", optional = true } +thiserror = { optional = true, workspace = true } scale-info = { version = "2.10.0", default-features = false, features = [ "derive", ] } diff --git a/substrate/primitives/api/proc-macro/Cargo.toml b/substrate/primitives/api/proc-macro/Cargo.toml index f0644eea8f4..f5406758687 100644 --- a/substrate/primitives/api/proc-macro/Cargo.toml +++ b/substrate/primitives/api/proc-macro/Cargo.toml @@ -19,8 +19,8 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -quote = "1.0.28" -syn = { version = "2.0.49", features = ["extra-traits", "fold", "full", "visit"] } +quote = { workspace = true } +syn = { features = ["extra-traits", "fold", "full", "visit"], workspace = true } proc-macro2 = "1.0.56" blake2 = { version = "0.10.4", default-features = false } proc-macro-crate = "3.0.0" diff --git a/substrate/primitives/application-crypto/Cargo.toml b/substrate/primitives/application-crypto/Cargo.toml index cf57aa4b76d..6f90a2b6262 100644 --- a/substrate/primitives/application-crypto/Cargo.toml +++ b/substrate/primitives/application-crypto/Cargo.toml @@ -21,7 +21,7 @@ targets = ["x86_64-unknown-linux-gnu"] sp-core = { path = "../core", default-features = false } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", default-features = false, optional = true, features = ["alloc", "derive"] } +serde = { optional = true, features = ["alloc", "derive"], workspace = true } sp-std = { path = "../std", default-features = false } sp-io = { path = "../io", default-features = false } diff --git a/substrate/primitives/arithmetic/Cargo.toml b/substrate/primitives/arithmetic/Cargo.toml index 92f9d23620e..301821ad689 100644 --- a/substrate/primitives/arithmetic/Cargo.toml +++ b/substrate/primitives/arithmetic/Cargo.toml @@ -24,7 +24,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = integer-sqrt = "0.1.2" num-traits = { version = "0.2.17", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { features = ["alloc", "derive"], optional = true, workspace = true } static_assertions = "1.1.0" sp-std = { path = "../std", default-features = false } diff --git a/substrate/primitives/blockchain/Cargo.toml b/substrate/primitives/blockchain/Cargo.toml index 26ba41645e5..9d13d627eeb 100644 --- a/substrate/primitives/blockchain/Cargo.toml +++ b/substrate/primitives/blockchain/Cargo.toml @@ -22,7 +22,7 @@ futures = "0.3.21" log = { workspace = true, default-features = true } parking_lot = "0.12.1" schnellru = "0.2.1" -thiserror = "1.0.48" +thiserror = { workspace = true } sp-api = { path = "../api" } sp-consensus = { path = "../consensus/common" } sp-database = { path = "../database" } diff --git a/substrate/primitives/consensus/babe/Cargo.toml b/substrate/primitives/consensus/babe/Cargo.toml index f6bc94e3adf..8b3006f79a7 100644 --- a/substrate/primitives/consensus/babe/Cargo.toml +++ b/substrate/primitives/consensus/babe/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] async-trait = { version = "0.1.74", optional = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { features = ["alloc", "derive"], optional = true, workspace = true } sp-api = { path = "../../api", default-features = false } sp-application-crypto = { path = "../../application-crypto", default-features = false } sp-consensus-slots = { path = "../slots", default-features = false } diff --git a/substrate/primitives/consensus/beefy/Cargo.toml b/substrate/primitives/consensus/beefy/Cargo.toml index 38fc67b7f52..8ab817d52ef 100644 --- a/substrate/primitives/consensus/beefy/Cargo.toml +++ b/substrate/primitives/consensus/beefy/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", default-features = false, optional = true, features = ["alloc", "derive"] } +serde = { optional = true, features = ["alloc", "derive"], workspace = true } sp-api = { path = "../../api", default-features = false } sp-application-crypto = { path = "../../application-crypto", default-features = false } sp-core = { path = "../../core", default-features = false } diff --git a/substrate/primitives/consensus/common/Cargo.toml b/substrate/primitives/consensus/common/Cargo.toml index cbca0d94d3e..048e31b0265 100644 --- a/substrate/primitives/consensus/common/Cargo.toml +++ b/substrate/primitives/consensus/common/Cargo.toml @@ -20,7 +20,7 @@ targets = ["x86_64-unknown-linux-gnu"] async-trait = "0.1.74" futures = { version = "0.3.21", features = ["thread-pool"] } log = { workspace = true, default-features = true } -thiserror = "1.0.48" +thiserror = { workspace = true } sp-core = { path = "../../core" } sp-inherents = { path = "../../inherents" } sp-runtime = { path = "../../runtime" } diff --git a/substrate/primitives/consensus/grandpa/Cargo.toml b/substrate/primitives/consensus/grandpa/Cargo.toml index b556b43bc95..b06208a4308 100644 --- a/substrate/primitives/consensus/grandpa/Cargo.toml +++ b/substrate/primitives/consensus/grandpa/Cargo.toml @@ -21,7 +21,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = grandpa = { package = "finality-grandpa", version = "0.16.2", default-features = false, features = ["derive-codec"] } log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", features = ["alloc", "derive"], default-features = false, optional = true } +serde = { features = ["alloc", "derive"], optional = true, workspace = true } sp-api = { path = "../../api", default-features = false } sp-application-crypto = { path = "../../application-crypto", default-features = false } sp-core = { path = "../../core", default-features = false } diff --git a/substrate/primitives/consensus/sassafras/Cargo.toml b/substrate/primitives/consensus/sassafras/Cargo.toml index 4c908201127..b707ad18b5b 100644 --- a/substrate/primitives/consensus/sassafras/Cargo.toml +++ b/substrate/primitives/consensus/sassafras/Cargo.toml @@ -20,7 +20,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] scale-codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", default-features = false, features = ["derive"], optional = true } +serde = { features = ["derive"], optional = true, workspace = true } sp-api = { path = "../../api", default-features = false } sp-application-crypto = { path = "../../application-crypto", default-features = false, features = ["bandersnatch-experimental"] } sp-consensus-slots = { path = "../slots", default-features = false } diff --git a/substrate/primitives/consensus/slots/Cargo.toml b/substrate/primitives/consensus/slots/Cargo.toml index fadcd1215ee..8372b2b04a6 100644 --- a/substrate/primitives/consensus/slots/Cargo.toml +++ b/substrate/primitives/consensus/slots/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { features = ["alloc", "derive"], optional = true, workspace = true } sp-std = { path = "../../std", default-features = false } sp-timestamp = { path = "../../timestamp", default-features = false } diff --git a/substrate/primitives/core/Cargo.toml b/substrate/primitives/core/Cargo.toml index c06dd9197ed..8fcabfeb238 100644 --- a/substrate/primitives/core/Cargo.toml +++ b/substrate/primitives/core/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } log = { workspace = true } -serde = { version = "1.0.196", optional = true, default-features = false, features = ["alloc", "derive"] } +serde = { optional = true, features = ["alloc", "derive"], workspace = true } bounded-collections = { version = "0.2.0", default-features = false } primitive-types = { version = "0.12.0", default-features = false, features = ["codec", "scale-info"] } impl-serde = { version = "0.4.0", default-features = false, optional = true } @@ -39,7 +39,7 @@ sp-storage = { path = "../storage", default-features = false } sp-externalities = { path = "../externalities", optional = true } futures = { version = "0.3.21", optional = true } dyn-clonable = { version = "0.9.0", optional = true } -thiserror = { version = "1.0.48", optional = true } +thiserror = { optional = true, workspace = true } tracing = { version = "0.1.29", optional = true } bitflags = "1.3" paste = "1.0.7" @@ -63,7 +63,7 @@ bandersnatch_vrfs = { git = "https://github.com/w3f/ring-vrf", rev = "e9782f9", [dev-dependencies] criterion = "0.4.0" -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } lazy_static = "1.4.0" regex = "1.6.0" diff --git a/substrate/primitives/crypto/hashing/proc-macro/Cargo.toml b/substrate/primitives/crypto/hashing/proc-macro/Cargo.toml index 2856c93c7ee..f244b02ca10 100644 --- a/substrate/primitives/crypto/hashing/proc-macro/Cargo.toml +++ b/substrate/primitives/crypto/hashing/proc-macro/Cargo.toml @@ -19,6 +19,6 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -quote = "1.0.28" -syn = { version = "2.0.49", features = ["full", "parsing"] } +quote = { workspace = true } +syn = { features = ["full", "parsing"], workspace = true } sp-crypto-hashing = { path = "..", default-features = false } diff --git a/substrate/primitives/debug-derive/Cargo.toml b/substrate/primitives/debug-derive/Cargo.toml index aaf5e618189..debf964aa3d 100644 --- a/substrate/primitives/debug-derive/Cargo.toml +++ b/substrate/primitives/debug-derive/Cargo.toml @@ -19,8 +19,8 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -quote = "1.0.28" -syn = "2.0.49" +quote = { workspace = true } +syn = { workspace = true } proc-macro2 = "1.0.56" [features] diff --git a/substrate/primitives/genesis-builder/Cargo.toml b/substrate/primitives/genesis-builder/Cargo.toml index 6497465a881..bf12433c5f4 100644 --- a/substrate/primitives/genesis-builder/Cargo.toml +++ b/substrate/primitives/genesis-builder/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] sp-api = { path = "../api", default-features = false } sp-runtime = { path = "../runtime", default-features = false } sp-std = { path = "../std", default-features = false } -serde_json = { version = "1.0.113", default-features = false, features = ["alloc", "arbitrary_precision"] } +serde_json = { features = ["alloc", "arbitrary_precision"], workspace = true } [features] default = ["std"] diff --git a/substrate/primitives/inherents/Cargo.toml b/substrate/primitives/inherents/Cargo.toml index 2a9d205e166..bfb1d773347 100644 --- a/substrate/primitives/inherents/Cargo.toml +++ b/substrate/primitives/inherents/Cargo.toml @@ -21,7 +21,7 @@ async-trait = { version = "0.1.74", optional = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } impl-trait-for-tuples = "0.2.2" -thiserror = { version = "1.0.48", optional = true } +thiserror = { optional = true, workspace = true } sp-runtime = { path = "../runtime", default-features = false, optional = true } sp-std = { path = "../std", default-features = false } diff --git a/substrate/primitives/maybe-compressed-blob/Cargo.toml b/substrate/primitives/maybe-compressed-blob/Cargo.toml index 84a5592dcaf..fa5383d03b1 100644 --- a/substrate/primitives/maybe-compressed-blob/Cargo.toml +++ b/substrate/primitives/maybe-compressed-blob/Cargo.toml @@ -14,5 +14,5 @@ readme = "README.md" workspace = true [dependencies] -thiserror = "1.0" +thiserror = { workspace = true } zstd = { version = "0.12.4", default-features = false } diff --git a/substrate/primitives/merkle-mountain-range/Cargo.toml b/substrate/primitives/merkle-mountain-range/Cargo.toml index f859bb5514e..50d84778239 100644 --- a/substrate/primitives/merkle-mountain-range/Cargo.toml +++ b/substrate/primitives/merkle-mountain-range/Cargo.toml @@ -19,13 +19,13 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } log = { workspace = true } mmr-lib = { package = "ckb-merkle-mountain-range", version = "0.5.2", default-features = false } -serde = { version = "1.0.196", features = ["alloc", "derive"], default-features = false, optional = true } +serde = { features = ["alloc", "derive"], optional = true, workspace = true } sp-api = { path = "../api", default-features = false } sp-core = { path = "../core", default-features = false } sp-debug-derive = { path = "../debug-derive", default-features = false } sp-runtime = { path = "../runtime", default-features = false } sp-std = { path = "../std", default-features = false } -thiserror = { version = "1.0", optional = true } +thiserror = { optional = true, workspace = true } [dev-dependencies] array-bytes = "6.1" diff --git a/substrate/primitives/npos-elections/Cargo.toml b/substrate/primitives/npos-elections/Cargo.toml index 8c3aee1d086..7373aa849cb 100644 --- a/substrate/primitives/npos-elections/Cargo.toml +++ b/substrate/primitives/npos-elections/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { features = ["alloc", "derive"], optional = true, workspace = true } sp-arithmetic = { path = "../arithmetic", default-features = false } sp-core = { path = "../core", default-features = false } sp-runtime = { path = "../runtime", default-features = false } diff --git a/substrate/primitives/rpc/Cargo.toml b/substrate/primitives/rpc/Cargo.toml index 04bbe3eea53..dce0eeee9f9 100644 --- a/substrate/primitives/rpc/Cargo.toml +++ b/substrate/primitives/rpc/Cargo.toml @@ -17,8 +17,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] rustc-hash = "1.1.0" -serde = { version = "1.0.196", features = ["derive"] } +serde = { features = ["derive"], workspace = true, default-features = true } sp-core = { path = "../core" } [dev-dependencies] -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } diff --git a/substrate/primitives/runtime-interface/proc-macro/Cargo.toml b/substrate/primitives/runtime-interface/proc-macro/Cargo.toml index 8031ba75b9b..7dbd810fea9 100644 --- a/substrate/primitives/runtime-interface/proc-macro/Cargo.toml +++ b/substrate/primitives/runtime-interface/proc-macro/Cargo.toml @@ -22,6 +22,6 @@ proc-macro = true Inflector = "0.11.4" proc-macro-crate = "3.0.0" proc-macro2 = "1.0.56" -quote = "1.0.28" +quote = { workspace = true } expander = "2.0.0" -syn = { version = "2.0.49", features = ["extra-traits", "fold", "full", "visit"] } +syn = { features = ["extra-traits", "fold", "full", "visit"], workspace = true } diff --git a/substrate/primitives/runtime/Cargo.toml b/substrate/primitives/runtime/Cargo.toml index 8e3b63a55de..cacfc059722 100644 --- a/substrate/primitives/runtime/Cargo.toml +++ b/substrate/primitives/runtime/Cargo.toml @@ -25,7 +25,7 @@ log = { workspace = true } paste = "1.0" rand = { version = "0.8.5", optional = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { features = ["alloc", "derive"], optional = true, workspace = true } sp-application-crypto = { path = "../application-crypto", default-features = false } sp-arithmetic = { path = "../arithmetic", default-features = false } sp-core = { path = "../core", default-features = false } @@ -38,7 +38,7 @@ simple-mermaid = { version = "0.1.1", optional = true } [dev-dependencies] rand = "0.8.5" -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } zstd = { version = "0.12.4", default-features = false } sp-api = { path = "../api" } sp-state-machine = { path = "../state-machine" } diff --git a/substrate/primitives/staking/Cargo.toml b/substrate/primitives/staking/Cargo.toml index 9429432babb..21346fbaca5 100644 --- a/substrate/primitives/staking/Cargo.toml +++ b/substrate/primitives/staking/Cargo.toml @@ -16,7 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { features = ["alloc", "derive"], optional = true, workspace = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } impl-trait-for-tuples = "0.2.2" diff --git a/substrate/primitives/state-machine/Cargo.toml b/substrate/primitives/state-machine/Cargo.toml index 489209ecc33..09994f1ae91 100644 --- a/substrate/primitives/state-machine/Cargo.toml +++ b/substrate/primitives/state-machine/Cargo.toml @@ -23,7 +23,7 @@ log = { workspace = true } parking_lot = { version = "0.12.1", optional = true } rand = { version = "0.8.5", optional = true } smallvec = "1.11.0" -thiserror = { version = "1.0.48", optional = true } +thiserror = { optional = true, workspace = true } tracing = { version = "0.1.29", optional = true } sp-core = { path = "../core", default-features = false } sp-externalities = { path = "../externalities", default-features = false } diff --git a/substrate/primitives/statement-store/Cargo.toml b/substrate/primitives/statement-store/Cargo.toml index 14af57a7b41..652ab3ef13a 100644 --- a/substrate/primitives/statement-store/Cargo.toml +++ b/substrate/primitives/statement-store/Cargo.toml @@ -26,7 +26,7 @@ sp-api = { path = "../api", default-features = false } sp-application-crypto = { path = "../application-crypto", default-features = false } sp-runtime-interface = { path = "../runtime-interface", default-features = false } sp-externalities = { path = "../externalities", default-features = false } -thiserror = { version = "1.0", optional = true } +thiserror = { optional = true, workspace = true } # ECIES dependencies ed25519-dalek = { version = "2.1", optional = true } diff --git a/substrate/primitives/storage/Cargo.toml b/substrate/primitives/storage/Cargo.toml index e15a3296d52..d3ade87ea47 100644 --- a/substrate/primitives/storage/Cargo.toml +++ b/substrate/primitives/storage/Cargo.toml @@ -20,7 +20,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } impl-serde = { version = "0.4.0", optional = true, default-features = false } ref-cast = "1.0.0" -serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { features = ["alloc", "derive"], optional = true, workspace = true } sp-debug-derive = { path = "../debug-derive", default-features = false } sp-std = { path = "../std", default-features = false } diff --git a/substrate/primitives/test-primitives/Cargo.toml b/substrate/primitives/test-primitives/Cargo.toml index 0411326c28b..f310216dd58 100644 --- a/substrate/primitives/test-primitives/Cargo.toml +++ b/substrate/primitives/test-primitives/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", default-features = false, features = ["derive"], optional = true } +serde = { features = ["derive"], optional = true, workspace = true } sp-application-crypto = { path = "../application-crypto", default-features = false } sp-core = { path = "../core", default-features = false } sp-runtime = { path = "../runtime", default-features = false } diff --git a/substrate/primitives/timestamp/Cargo.toml b/substrate/primitives/timestamp/Cargo.toml index 914c1e7865e..9e2b802bfb1 100644 --- a/substrate/primitives/timestamp/Cargo.toml +++ b/substrate/primitives/timestamp/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = { version = "0.1.74", optional = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -thiserror = { version = "1.0.48", optional = true } +thiserror = { optional = true, workspace = true } sp-inherents = { path = "../inherents", default-features = false } sp-runtime = { path = "../runtime", default-features = false } sp-std = { path = "../std", default-features = false } diff --git a/substrate/primitives/trie/Cargo.toml b/substrate/primitives/trie/Cargo.toml index f871f462d36..16d3ca19a17 100644 --- a/substrate/primitives/trie/Cargo.toml +++ b/substrate/primitives/trie/Cargo.toml @@ -30,7 +30,7 @@ nohash-hasher = { version = "0.2.0", optional = true } parking_lot = { version = "0.12.1", optional = true } rand = { version = "0.8", optional = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -thiserror = { version = "1.0.48", optional = true } +thiserror = { optional = true, workspace = true } tracing = { version = "0.1.29", optional = true } trie-db = { version = "0.28.0", default-features = false } trie-root = { version = "0.18.0", default-features = false } diff --git a/substrate/primitives/version/Cargo.toml b/substrate/primitives/version/Cargo.toml index 21f95776375..a94e2322430 100644 --- a/substrate/primitives/version/Cargo.toml +++ b/substrate/primitives/version/Cargo.toml @@ -21,8 +21,8 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = impl-serde = { version = "0.4.0", default-features = false, optional = true } parity-wasm = { version = "0.45", optional = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", default-features = false, features = ["alloc", "derive"], optional = true } -thiserror = { version = "1.0.48", optional = true } +serde = { features = ["alloc", "derive"], optional = true, workspace = true } +thiserror = { optional = true, workspace = true } sp-crypto-hashing-proc-macro = { path = "../crypto/hashing/proc-macro" } sp-runtime = { path = "../runtime", default-features = false } sp-std = { path = "../std", default-features = false } diff --git a/substrate/primitives/version/proc-macro/Cargo.toml b/substrate/primitives/version/proc-macro/Cargo.toml index b99894f8ab5..f7abf88c9a6 100644 --- a/substrate/primitives/version/proc-macro/Cargo.toml +++ b/substrate/primitives/version/proc-macro/Cargo.toml @@ -21,8 +21,8 @@ proc-macro = true [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } proc-macro2 = "1.0.56" -quote = "1.0.28" -syn = { version = "2.0.49", features = ["extra-traits", "fold", "full", "visit"] } +quote = { workspace = true } +syn = { features = ["extra-traits", "fold", "full", "visit"], workspace = true } [dev-dependencies] sp-version = { path = ".." } diff --git a/substrate/primitives/weights/Cargo.toml b/substrate/primitives/weights/Cargo.toml index 87e0c6a7299..a7d61de001b 100644 --- a/substrate/primitives/weights/Cargo.toml +++ b/substrate/primitives/weights/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] bounded-collections = { version = "0.2.0", default-features = false } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.196", default-features = false, optional = true, features = ["alloc", "derive"] } +serde = { optional = true, features = ["alloc", "derive"], workspace = true } smallvec = "1.11.0" sp-arithmetic = { path = "../arithmetic", default-features = false } sp-debug-derive = { path = "../debug-derive", default-features = false } diff --git a/substrate/test-utils/client/Cargo.toml b/substrate/test-utils/client/Cargo.toml index 4ec2857b109..349b04d32d7 100644 --- a/substrate/test-utils/client/Cargo.toml +++ b/substrate/test-utils/client/Cargo.toml @@ -20,8 +20,8 @@ array-bytes = "6.1" async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" -serde = "1.0.196" -serde_json = "1.0.113" +serde = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sc-client-api = { path = "../../client/api" } sc-client-db = { path = "../../client/db", default-features = false, features = [ "test-helpers", diff --git a/substrate/test-utils/runtime/Cargo.toml b/substrate/test-utils/runtime/Cargo.toml index 49b84e04dab..3bba5cd5bf0 100644 --- a/substrate/test-utils/runtime/Cargo.toml +++ b/substrate/test-utils/runtime/Cargo.toml @@ -62,8 +62,8 @@ sc-executor-common = { path = "../../client/executor/common" } sp-consensus = { path = "../../primitives/consensus/common" } substrate-test-runtime-client = { path = "client" } sp-tracing = { path = "../../primitives/tracing" } -serde = { version = "1.0.196", features = ["alloc", "derive"], default-features = false } -serde_json = { version = "1.0.113", default-features = false, features = ["alloc"] } +serde = { features = ["alloc", "derive"], workspace = true } +serde_json = { features = ["alloc"], workspace = true } [build-dependencies] substrate-wasm-builder = { path = "../../utils/wasm-builder", optional = true } diff --git a/substrate/test-utils/runtime/transaction-pool/Cargo.toml b/substrate/test-utils/runtime/transaction-pool/Cargo.toml index b52a897438b..33e56e8e55e 100644 --- a/substrate/test-utils/runtime/transaction-pool/Cargo.toml +++ b/substrate/test-utils/runtime/transaction-pool/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" parking_lot = "0.12.1" -thiserror = "1.0" +thiserror = { workspace = true } sc-transaction-pool = { path = "../../../client/transaction-pool" } sc-transaction-pool-api = { path = "../../../client/transaction-pool/api" } sp-blockchain = { path = "../../../primitives/blockchain" } diff --git a/substrate/utils/frame/benchmarking-cli/Cargo.toml b/substrate/utils/frame/benchmarking-cli/Cargo.toml index 4f6cbc1d5d6..a2db83052c5 100644 --- a/substrate/utils/frame/benchmarking-cli/Cargo.toml +++ b/substrate/utils/frame/benchmarking-cli/Cargo.toml @@ -29,9 +29,9 @@ linked-hash-map = "0.5.4" log = { workspace = true, default-features = true } rand = { version = "0.8.5", features = ["small_rng"] } rand_pcg = "0.3.1" -serde = "1.0.196" -serde_json = "1.0.113" -thiserror = "1.0.48" +serde = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } +thiserror = { workspace = true } thousands = "0.2.0" frame-benchmarking = { path = "../../../frame/benchmarking" } frame-support = { path = "../../../frame/support" } diff --git a/substrate/utils/frame/remote-externalities/Cargo.toml b/substrate/utils/frame/remote-externalities/Cargo.toml index c96d37d4ae6..61e0a861ee0 100644 --- a/substrate/utils/frame/remote-externalities/Cargo.toml +++ b/substrate/utils/frame/remote-externalities/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] jsonrpsee = { version = "0.22", features = ["http-client"] } codec = { package = "parity-scale-codec", version = "3.6.1" } log = { workspace = true, default-features = true } -serde = "1.0.196" +serde = { workspace = true, default-features = true } sp-core = { path = "../../../primitives/core" } sp-crypto-hashing = { path = "../../../primitives/crypto/hashing" } sp-state-machine = { path = "../../../primitives/state-machine" } diff --git a/substrate/utils/frame/rpc/client/Cargo.toml b/substrate/utils/frame/rpc/client/Cargo.toml index 0057bf6eb50..b51e3f44f4e 100644 --- a/substrate/utils/frame/rpc/client/Cargo.toml +++ b/substrate/utils/frame/rpc/client/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] jsonrpsee = { version = "0.22", features = ["ws-client"] } sc-rpc-api = { path = "../../../../client/rpc-api" } async-trait = "0.1.74" -serde = "1" +serde = { workspace = true, default-features = true } sp-runtime = { path = "../../../../primitives/runtime" } log = { workspace = true, default-features = true } diff --git a/substrate/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml b/substrate/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml index 58c2f3d99ac..f9a45e21ce1 100644 --- a/substrate/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml +++ b/substrate/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -serde = { version = "1", features = ["derive"] } +serde = { features = ["derive"], workspace = true, default-features = true } sp-core = { path = "../../../../primitives/core" } sp-state-machine = { path = "../../../../primitives/state-machine" } @@ -32,4 +32,4 @@ sc-rpc-api = { path = "../../../../client/rpc-api" } sp-runtime = { path = "../../../../primitives/runtime" } [dev-dependencies] -serde_json = "1.0.113" +serde_json = { workspace = true, default-features = true } diff --git a/substrate/utils/frame/rpc/support/Cargo.toml b/substrate/utils/frame/rpc/support/Cargo.toml index a4b0a7777fb..2e4bb6a1057 100644 --- a/substrate/utils/frame/rpc/support/Cargo.toml +++ b/substrate/utils/frame/rpc/support/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } jsonrpsee = { version = "0.22", features = ["jsonrpsee-types"] } -serde = "1" +serde = { workspace = true, default-features = true } frame-support = { path = "../../../../frame/support" } sc-rpc-api = { path = "../../../../client/rpc-api" } sp-storage = { path = "../../../../primitives/storage" } diff --git a/substrate/utils/frame/try-runtime/cli/Cargo.toml b/substrate/utils/frame/try-runtime/cli/Cargo.toml index 63165f07e5c..d123fc5f62d 100644 --- a/substrate/utils/frame/try-runtime/cli/Cargo.toml +++ b/substrate/utils/frame/try-runtime/cli/Cargo.toml @@ -42,8 +42,8 @@ clap = { version = "4.5.1", features = ["derive"] } hex = { version = "0.4.3", default-features = false } log = { workspace = true, default-features = true } parity-scale-codec = "3.6.1" -serde = "1.0.196" -serde_json = "1.0.113" +serde = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } zstd = { version = "0.12.4", default-features = false } [dev-dependencies] diff --git a/substrate/utils/prometheus/Cargo.toml b/substrate/utils/prometheus/Cargo.toml index 2a09cb2bb51..36527ac6183 100644 --- a/substrate/utils/prometheus/Cargo.toml +++ b/substrate/utils/prometheus/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] hyper = { version = "0.14.16", default-features = false, features = ["http1", "server", "tcp"] } log = { workspace = true, default-features = true } prometheus = { version = "0.13.0", default-features = false } -thiserror = "1.0" +thiserror = { workspace = true } tokio = { version = "1.22.0", features = ["parking_lot"] } [dev-dependencies] -- GitLab From d250a6e4270a77f28e2737a4faa3fb78c8ea7a85 Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Tue, 20 Feb 2024 15:28:05 +0100 Subject: [PATCH 094/133] Contracts: Stabilize APIs (#3384) Remove `#[unstable]` on `call_v2`, `instantiate_v2`, `lock_delegate_dependency` and `unlock_delegate_dependency`. See ink! integrations: - call_v2: https://github.com/paritytech/ink/pull/2077 - instantiate_v2: - lock/unlock dependency: https://github.com/paritytech/ink/pull/2076 --- prdoc/pr_3384.prdoc | 14 +++++++ .../contracts/fixtures/contracts/call.rs | 8 ++-- .../fixtures/contracts/call_return_code.rs | 8 ++-- .../contracts/call_runtime_and_call.rs | 8 ++-- .../fixtures/contracts/call_with_limit.rs | 1 - .../fixtures/contracts/caller_contract.rs | 10 +---- .../contracts/chain_extension_temp_storage.rs | 8 ++-- .../contracts/create_storage_and_call.rs | 3 +- .../create_storage_and_instantiate.rs | 1 - .../contracts/destroy_and_transfer.rs | 3 -- .../contracts/instantiate_return_code.rs | 1 - ...ency.rs => locking_delegate_dependency.rs} | 12 +++--- .../contracts/reentrance_count_call.rs | 8 ++-- .../fixtures/contracts/self_destruct.rs | 6 ++- .../frame/contracts/src/benchmarking/mod.rs | 12 +++--- substrate/frame/contracts/src/exec.rs | 16 ++++---- substrate/frame/contracts/src/lib.rs | 4 +- substrate/frame/contracts/src/schedule.rs | 12 +++--- substrate/frame/contracts/src/storage.rs | 6 +-- substrate/frame/contracts/src/tests.rs | 38 +++++++++---------- substrate/frame/contracts/src/wasm/mod.rs | 20 +++++----- substrate/frame/contracts/src/wasm/runtime.rs | 30 +++++++-------- substrate/frame/contracts/src/weights.rs | 12 +++--- substrate/frame/contracts/uapi/src/host.rs | 20 +++------- .../frame/contracts/uapi/src/host/riscv32.rs | 4 +- .../frame/contracts/uapi/src/host/wasm32.rs | 12 +++--- 26 files changed, 137 insertions(+), 140 deletions(-) create mode 100644 prdoc/pr_3384.prdoc rename substrate/frame/contracts/fixtures/contracts/{add_remove_delegate_dependency.rs => locking_delegate_dependency.rs} (83%) diff --git a/prdoc/pr_3384.prdoc b/prdoc/pr_3384.prdoc new file mode 100644 index 00000000000..c1946497758 --- /dev/null +++ b/prdoc/pr_3384.prdoc @@ -0,0 +1,14 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "[pallet_contracts] stabilize `call_v2`, `instantiate_v2`, `lock_dependency` and `unlock_dependency`" + +doc: + - audience: Runtime Dev + description: | + These APIs are currently unstable and are being stabilized in this PR. + Note: `add_delegate_dependency` and `remove_delegate_dependency` have been renamed to `lock_dependency` and `unlock_dependency` respectively. + +crates: + - name: pallet-contracts-uapi + - name: pallet-contracts diff --git a/substrate/frame/contracts/fixtures/contracts/call.rs b/substrate/frame/contracts/fixtures/contracts/call.rs index f7d9d862d74..535745ffc0b 100644 --- a/substrate/frame/contracts/fixtures/contracts/call.rs +++ b/substrate/frame/contracts/fixtures/contracts/call.rs @@ -35,11 +35,13 @@ pub extern "C" fn call() { ); // Call the callee - api::call_v1( + api::call_v2( uapi::CallFlags::empty(), callee_addr, - 0u64, // How much gas to devote for the execution. 0 = all. - &0u64.to_le_bytes(), // value transferred to the contract. + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &0u64.to_le_bytes(), // Value transferred to the contract. callee_input, None, ) diff --git a/substrate/frame/contracts/fixtures/contracts/call_return_code.rs b/substrate/frame/contracts/fixtures/contracts/call_return_code.rs index 1256588d3b5..3d5a1073eae 100644 --- a/substrate/frame/contracts/fixtures/contracts/call_return_code.rs +++ b/substrate/frame/contracts/fixtures/contracts/call_return_code.rs @@ -38,11 +38,13 @@ pub extern "C" fn call() { ); // Call the callee - let err_code = match api::call_v1( + let err_code = match api::call_v2( uapi::CallFlags::empty(), callee_addr, - 0u64, // How much gas to devote for the execution. 0 = all. - &100u64.to_le_bytes(), // value transferred to the contract. + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &100u64.to_le_bytes(), // Value transferred to the contract. input, None, ) { diff --git a/substrate/frame/contracts/fixtures/contracts/call_runtime_and_call.rs b/substrate/frame/contracts/fixtures/contracts/call_runtime_and_call.rs index fdeb6097e73..1321d36dc7e 100644 --- a/substrate/frame/contracts/fixtures/contracts/call_runtime_and_call.rs +++ b/substrate/frame/contracts/fixtures/contracts/call_runtime_and_call.rs @@ -39,11 +39,13 @@ pub extern "C" fn call() { api::call_runtime(call).unwrap(); // Call the callee - api::call_v1( + api::call_v2( uapi::CallFlags::empty(), callee_addr, - 0u64, // How much gas to devote for the execution. 0 = all. - &0u64.to_le_bytes(), // value transferred to the contract. + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &0u64.to_le_bytes(), // Value transferred to the contract. callee_input, None, ) diff --git a/substrate/frame/contracts/fixtures/contracts/call_with_limit.rs b/substrate/frame/contracts/fixtures/contracts/call_with_limit.rs index 40cde234b44..f0851f32c75 100644 --- a/substrate/frame/contracts/fixtures/contracts/call_with_limit.rs +++ b/substrate/frame/contracts/fixtures/contracts/call_with_limit.rs @@ -38,7 +38,6 @@ pub extern "C" fn call() { forwarded_input: [u8], ); - #[allow(deprecated)] api::call_v2( uapi::CallFlags::empty(), callee_addr, diff --git a/substrate/frame/contracts/fixtures/contracts/caller_contract.rs b/substrate/frame/contracts/fixtures/contracts/caller_contract.rs index c2629e9fa19..fffdb66a33d 100644 --- a/substrate/frame/contracts/fixtures/contracts/caller_contract.rs +++ b/substrate/frame/contracts/fixtures/contracts/caller_contract.rs @@ -40,7 +40,6 @@ pub extern "C" fn call() { let reverted_input = [1u8, 34, 51, 68, 85, 102, 119]; // Fail to deploy the contract since it returns a non-zero exit status. - #[allow(deprecated)] let res = api::instantiate_v2( code_hash, 0u64, // How much ref_time weight to devote for the execution. 0 = all. @@ -55,7 +54,6 @@ pub extern "C" fn call() { assert!(matches!(res, Err(ReturnErrorCode::CalleeReverted))); // Fail to deploy the contract due to insufficient ref_time weight. - #[allow(deprecated)] let res = api::instantiate_v2( code_hash, 1u64, // too little ref_time weight 0u64, // How much proof_size weight to devote for the execution. 0 = all. @@ -65,7 +63,6 @@ pub extern "C" fn call() { assert!(matches!(res, Err(ReturnErrorCode::CalleeTrapped))); // Fail to deploy the contract due to insufficient proof_size weight. - #[allow(deprecated)] let res = api::instantiate_v2( code_hash, 0u64, // How much ref_time weight to devote for the execution. 0 = all. 1u64, // Too little proof_size weight @@ -78,7 +75,6 @@ pub extern "C" fn call() { let mut callee = [0u8; 32]; let callee = &mut &mut callee[..]; - #[allow(deprecated)] api::instantiate_v2( code_hash, 0u64, // How much ref_time weight to devote for the execution. 0 = all. @@ -94,7 +90,6 @@ pub extern "C" fn call() { assert_eq!(callee.len(), 32); // Call the new contract and expect it to return failing exit code. - #[allow(deprecated)] let res = api::call_v2( uapi::CallFlags::empty(), callee, @@ -108,11 +103,10 @@ pub extern "C" fn call() { assert!(matches!(res, Err(ReturnErrorCode::CalleeReverted))); // Fail to call the contract due to insufficient ref_time weight. - #[allow(deprecated)] let res = api::call_v2( uapi::CallFlags::empty(), callee, - 1u64, // too little ref_time weight + 1u64, // Too little ref_time weight. 0u64, // How much proof_size weight to devote for the execution. 0 = all. None, // No deposit limit. &value, @@ -122,7 +116,6 @@ pub extern "C" fn call() { assert!(matches!(res, Err(ReturnErrorCode::CalleeTrapped))); // Fail to call the contract due to insufficient proof_size weight. - #[allow(deprecated)] let res = api::call_v2( uapi::CallFlags::empty(), callee, @@ -137,7 +130,6 @@ pub extern "C" fn call() { // Call the contract successfully. let mut output = [0u8; 4]; - #[allow(deprecated)] api::call_v2( uapi::CallFlags::empty(), callee, diff --git a/substrate/frame/contracts/fixtures/contracts/chain_extension_temp_storage.rs b/substrate/frame/contracts/fixtures/contracts/chain_extension_temp_storage.rs index 1ab08efb3c7..2e15fb02a12 100644 --- a/substrate/frame/contracts/fixtures/contracts/chain_extension_temp_storage.rs +++ b/substrate/frame/contracts/fixtures/contracts/chain_extension_temp_storage.rs @@ -50,11 +50,13 @@ pub extern "C" fn call() { output!(addr, [0u8; 32], api::address,); // call self - api::call_v1( + api::call_v2( uapi::CallFlags::ALLOW_REENTRY, addr, - 0u64, // How much gas to devote for the execution. 0 = all. - &0u64.to_le_bytes(), // value transferred to the contract. + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &0u64.to_le_bytes(), // Value transferred to the contract. input, None, ) diff --git a/substrate/frame/contracts/fixtures/contracts/create_storage_and_call.rs b/substrate/frame/contracts/fixtures/contracts/create_storage_and_call.rs index 8b79dd87dff..f8ce0ff4fb8 100644 --- a/substrate/frame/contracts/fixtures/contracts/create_storage_and_call.rs +++ b/substrate/frame/contracts/fixtures/contracts/create_storage_and_call.rs @@ -40,14 +40,13 @@ pub extern "C" fn call() { api::set_storage(buffer, &[1u8; 4]); // Call the callee - #[allow(deprecated)] api::call_v2( uapi::CallFlags::empty(), callee, 0u64, // How much ref_time weight to devote for the execution. 0 = all. 0u64, // How much proof_size weight to devote for the execution. 0 = all. Some(deposit_limit), - &0u64.to_le_bytes(), // value transferred to the contract. + &0u64.to_le_bytes(), // Value transferred to the contract. input, None, ) diff --git a/substrate/frame/contracts/fixtures/contracts/create_storage_and_instantiate.rs b/substrate/frame/contracts/fixtures/contracts/create_storage_and_instantiate.rs index c68d99eff82..fa3b9000a7e 100644 --- a/substrate/frame/contracts/fixtures/contracts/create_storage_and_instantiate.rs +++ b/substrate/frame/contracts/fixtures/contracts/create_storage_and_instantiate.rs @@ -40,7 +40,6 @@ pub extern "C" fn call() { let mut address = [0u8; 32]; let address = &mut &mut address[..]; - #[allow(deprecated)] api::instantiate_v2( code_hash, 0u64, // How much ref_time weight to devote for the execution. 0 = all. diff --git a/substrate/frame/contracts/fixtures/contracts/destroy_and_transfer.rs b/substrate/frame/contracts/fixtures/contracts/destroy_and_transfer.rs index cfcfd60f21e..62fb63b5602 100644 --- a/substrate/frame/contracts/fixtures/contracts/destroy_and_transfer.rs +++ b/substrate/frame/contracts/fixtures/contracts/destroy_and_transfer.rs @@ -34,7 +34,6 @@ pub extern "C" fn deploy() { let address = &mut &mut address[..]; let salt = [71u8, 17u8]; - #[allow(deprecated)] api::instantiate_v2( code_hash, 0u64, // How much ref_time weight to devote for the execution. 0 = all. @@ -60,7 +59,6 @@ pub extern "C" fn call() { api::get_storage(&ADDRESS_KEY, callee_addr).unwrap(); // Calling the destination contract with non-empty input data should fail. - #[allow(deprecated)] let res = api::call_v2( uapi::CallFlags::empty(), callee_addr, @@ -74,7 +72,6 @@ pub extern "C" fn call() { assert!(matches!(res, Err(uapi::ReturnErrorCode::CalleeTrapped))); // Call the destination contract regularly, forcing it to self-destruct. - #[allow(deprecated)] api::call_v2( uapi::CallFlags::empty(), callee_addr, diff --git a/substrate/frame/contracts/fixtures/contracts/instantiate_return_code.rs b/substrate/frame/contracts/fixtures/contracts/instantiate_return_code.rs index 67cd739d85a..de194abe484 100644 --- a/substrate/frame/contracts/fixtures/contracts/instantiate_return_code.rs +++ b/substrate/frame/contracts/fixtures/contracts/instantiate_return_code.rs @@ -31,7 +31,6 @@ pub extern "C" fn call() { input!(buffer, 36, code_hash: [u8; 32],); let input = &buffer[32..]; - #[allow(deprecated)] let err_code = match api::instantiate_v2( code_hash, 0u64, // How much ref_time weight to devote for the execution. 0 = all. diff --git a/substrate/frame/contracts/fixtures/contracts/add_remove_delegate_dependency.rs b/substrate/frame/contracts/fixtures/contracts/locking_delegate_dependency.rs similarity index 83% rename from substrate/frame/contracts/fixtures/contracts/add_remove_delegate_dependency.rs rename to substrate/frame/contracts/fixtures/contracts/locking_delegate_dependency.rs index 759ff799374..bb76c942aad 100644 --- a/substrate/frame/contracts/fixtures/contracts/add_remove_delegate_dependency.rs +++ b/substrate/frame/contracts/fixtures/contracts/locking_delegate_dependency.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! This contract tests the behavior of adding / removing delegate_dependencies when delegate +//! This contract tests the behavior of locking / unlocking delegate_dependencies when delegate //! calling into a contract. #![no_std] #![no_main] @@ -34,15 +34,13 @@ fn load_input(delegate_call: bool) { ); match action { - // 1 = Add delegate dependency + // 1 = Lock delegate dependency 1 => { - #[allow(deprecated)] - api::add_delegate_dependency(code_hash); + api::lock_delegate_dependency(code_hash); }, - // 2 = Remove delegate dependency + // 2 = Unlock delegate dependency 2 => { - #[allow(deprecated)] - api::remove_delegate_dependency(code_hash); + api::unlock_delegate_dependency(code_hash); }, // 3 = Terminate 3 => { diff --git a/substrate/frame/contracts/fixtures/contracts/reentrance_count_call.rs b/substrate/frame/contracts/fixtures/contracts/reentrance_count_call.rs index 9812dce2e81..0acfe017f8d 100644 --- a/substrate/frame/contracts/fixtures/contracts/reentrance_count_call.rs +++ b/substrate/frame/contracts/fixtures/contracts/reentrance_count_call.rs @@ -42,11 +42,13 @@ pub extern "C" fn call() { if expected_reentrance_count != 5 { let count = (expected_reentrance_count + 1).to_le_bytes(); - api::call_v1( + api::call_v2( uapi::CallFlags::ALLOW_REENTRY, addr, - 0u64, // How much gas to devote for the execution. 0 = all. - &0u64.to_le_bytes(), // value transferred to the contract. + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &0u64.to_le_bytes(), // Value transferred to the contract. &count, None, ) diff --git a/substrate/frame/contracts/fixtures/contracts/self_destruct.rs b/substrate/frame/contracts/fixtures/contracts/self_destruct.rs index 94c3ac387a0..d3836d2d8c7 100644 --- a/substrate/frame/contracts/fixtures/contracts/self_destruct.rs +++ b/substrate/frame/contracts/fixtures/contracts/self_destruct.rs @@ -37,10 +37,12 @@ pub extern "C" fn call() { if !input.is_empty() { output!(addr, [0u8; 32], api::address,); - api::call_v1( + api::call_v2( uapi::CallFlags::ALLOW_REENTRY, addr, - 0u64, // How much gas to devote for the execution. 0 = all. + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. &0u64.to_le_bytes(), // Value to transfer. &[0u8; 0], None, diff --git a/substrate/frame/contracts/src/benchmarking/mod.rs b/substrate/frame/contracts/src/benchmarking/mod.rs index d6c24daa7c4..4b6ff63c6f5 100644 --- a/substrate/frame/contracts/src/benchmarking/mod.rs +++ b/substrate/frame/contracts/src/benchmarking/mod.rs @@ -895,7 +895,7 @@ benchmarks! { }, ImportedFunction { module: "seal0", - name: "add_delegate_dependency", + name: "lock_delegate_dependency", params: vec![ValueType::I32], return_type: None, } @@ -2402,7 +2402,7 @@ benchmarks! { }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) #[pov_mode = Measured] - add_delegate_dependency { + lock_delegate_dependency { let r in 0 .. T::MaxDelegateDependencies::get(); let code_hashes = (0..r) .map(|i| { @@ -2420,7 +2420,7 @@ benchmarks! { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { module: "seal0", - name: "add_delegate_dependency", + name: "lock_delegate_dependency", params: vec![ValueType::I32], return_type: None, }], @@ -2440,7 +2440,7 @@ benchmarks! { let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - remove_delegate_dependency { + unlock_delegate_dependency { let r in 0 .. T::MaxDelegateDependencies::get(); let code_hashes = (0..r) .map(|i| { @@ -2459,12 +2459,12 @@ benchmarks! { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { module: "seal0", - name: "remove_delegate_dependency", + name: "unlock_delegate_dependency", params: vec![ValueType::I32], return_type: None, }, ImportedFunction { module: "seal0", - name: "add_delegate_dependency", + name: "lock_delegate_dependency", params: vec![ValueType::I32], return_type: None }], diff --git a/substrate/frame/contracts/src/exec.rs b/substrate/frame/contracts/src/exec.rs index 79220021efe..7da321af547 100644 --- a/substrate/frame/contracts/src/exec.rs +++ b/substrate/frame/contracts/src/exec.rs @@ -348,20 +348,20 @@ pub trait Ext: sealing::Sealed { /// - [`Error::::MaxDelegateDependenciesReached`] /// - [`Error::::CannotAddSelfAsDelegateDependency`] /// - [`Error::::DelegateDependencyAlreadyExists`] - fn add_delegate_dependency( + fn lock_delegate_dependency( &mut self, code_hash: CodeHash, ) -> Result<(), DispatchError>; /// Removes a delegate dependency from [`ContractInfo`]'s `delegate_dependencies` field. /// - /// This is the counterpart of [`Self::add_delegate_dependency`]. It decreases the reference - /// count and refunds the deposit that was charged by [`Self::add_delegate_dependency`]. + /// This is the counterpart of [`Self::lock_delegate_dependency`]. It decreases the reference + /// count and refunds the deposit that was charged by [`Self::lock_delegate_dependency`]. /// /// # Errors /// /// - [`Error::::DelegateDependencyNotFound`] - fn remove_delegate_dependency( + fn unlock_delegate_dependency( &mut self, code_hash: &CodeHash, ) -> Result<(), DispatchError>; @@ -1554,7 +1554,7 @@ where }); } - fn add_delegate_dependency( + fn lock_delegate_dependency( &mut self, code_hash: CodeHash, ) -> Result<(), DispatchError> { @@ -1565,7 +1565,7 @@ where let code_info = CodeInfoOf::::get(code_hash).ok_or(Error::::CodeNotFound)?; let deposit = T::CodeHashLockupDepositPercent::get().mul_ceil(code_info.deposit()); - info.add_delegate_dependency(code_hash, deposit)?; + info.lock_delegate_dependency(code_hash, deposit)?; Self::increment_refcount(code_hash)?; frame .nested_storage @@ -1573,14 +1573,14 @@ where Ok(()) } - fn remove_delegate_dependency( + fn unlock_delegate_dependency( &mut self, code_hash: &CodeHash, ) -> Result<(), DispatchError> { let frame = self.top_frame_mut(); let info = frame.contract_info.get(&frame.account_id); - let deposit = info.remove_delegate_dependency(code_hash)?; + let deposit = info.unlock_delegate_dependency(code_hash)?; Self::decrement_refcount(*code_hash); frame .nested_storage diff --git a/substrate/frame/contracts/src/lib.rs b/substrate/frame/contracts/src/lib.rs index 533085f2b87..943bf5f85d4 100644 --- a/substrate/frame/contracts/src/lib.rs +++ b/substrate/frame/contracts/src/lib.rs @@ -325,7 +325,7 @@ pub mod pallet { type DepositPerItem: Get>; /// The percentage of the storage deposit that should be held for using a code hash. - /// Instantiating a contract, or calling [`chain_extension::Ext::add_delegate_dependency`] + /// Instantiating a contract, or calling [`chain_extension::Ext::lock_delegate_dependency`] /// protects the code from being removed. In order to prevent abuse these actions are /// protected with a percentage of the code deposit. #[pallet::constant] @@ -347,7 +347,7 @@ pub mod pallet { type MaxStorageKeyLen: Get; /// The maximum number of delegate_dependencies that a contract can lock with - /// [`chain_extension::Ext::add_delegate_dependency`]. + /// [`chain_extension::Ext::lock_delegate_dependency`]. #[pallet::constant] type MaxDelegateDependencies: Get; diff --git a/substrate/frame/contracts/src/schedule.rs b/substrate/frame/contracts/src/schedule.rs index 54e037d6be9..b2e3801deae 100644 --- a/substrate/frame/contracts/src/schedule.rs +++ b/substrate/frame/contracts/src/schedule.rs @@ -298,11 +298,11 @@ pub struct HostFnWeights { /// Weight of calling `instantiation_nonce`. pub instantiation_nonce: Weight, - /// Weight of calling `add_delegate_dependency`. - pub add_delegate_dependency: Weight, + /// Weight of calling `lock_delegate_dependency`. + pub lock_delegate_dependency: Weight, - /// Weight of calling `remove_delegate_dependency`. - pub remove_delegate_dependency: Weight, + /// Weight of calling `unlock_delegate_dependency`. + pub unlock_delegate_dependency: Weight, /// The type parameter is used in the default implementation. #[codec(skip)] @@ -435,8 +435,8 @@ impl Default for HostFnWeights { reentrance_count: cost!(seal_reentrance_count), account_reentrance_count: cost!(seal_account_reentrance_count), instantiation_nonce: cost!(seal_instantiation_nonce), - add_delegate_dependency: cost!(add_delegate_dependency), - remove_delegate_dependency: cost!(remove_delegate_dependency), + lock_delegate_dependency: cost!(lock_delegate_dependency), + unlock_delegate_dependency: cost!(unlock_delegate_dependency), _phantom: PhantomData, } } diff --git a/substrate/frame/contracts/src/storage.rs b/substrate/frame/contracts/src/storage.rs index d4d261f4ec1..3304166607d 100644 --- a/substrate/frame/contracts/src/storage.rs +++ b/substrate/frame/contracts/src/storage.rs @@ -66,7 +66,7 @@ pub struct ContractInfo { storage_base_deposit: BalanceOf, /// Map of code hashes and deposit balances. /// - /// Tracks the code hash and deposit held for adding delegate dependencies. Dependencies added + /// Tracks the code hash and deposit held for locking delegate dependencies. Dependencies added /// to the map can not be removed from the chain state and can be safely used for delegate /// calls. delegate_dependencies: BoundedBTreeMap, BalanceOf, T::MaxDelegateDependencies>, @@ -233,7 +233,7 @@ impl ContractInfo { /// /// Returns an error if the maximum number of delegate_dependencies is reached or if /// the delegate dependency already exists. - pub fn add_delegate_dependency( + pub fn lock_delegate_dependency( &mut self, code_hash: CodeHash, amount: BalanceOf, @@ -249,7 +249,7 @@ impl ContractInfo { /// dependency. /// /// Returns an error if the entry doesn't exist. - pub fn remove_delegate_dependency( + pub fn unlock_delegate_dependency( &mut self, code_hash: &CodeHash, ) -> Result, DispatchError> { diff --git a/substrate/frame/contracts/src/tests.rs b/substrate/frame/contracts/src/tests.rs index a06b65b86b5..cd4e43ed4c2 100644 --- a/substrate/frame/contracts/src/tests.rs +++ b/substrate/frame/contracts/src/tests.rs @@ -5406,21 +5406,21 @@ fn delegate_call_indeterministic_code() { } #[test] -fn add_remove_delegate_dependency_works() { +fn locking_delegate_dependency_works() { // set hash lock up deposit to 30%, to test deposit calculation. CODE_HASH_LOCKUP_DEPOSIT_PERCENT.with(|c| *c.borrow_mut() = Perbill::from_percent(30)); MAX_DELEGATE_DEPENDENCIES.with(|c| *c.borrow_mut() = 1); let (wasm_caller, self_code_hash) = - compile_module::("add_remove_delegate_dependency").unwrap(); + compile_module::("locking_delegate_dependency").unwrap(); let (wasm_callee, code_hash) = compile_module::("dummy").unwrap(); let (wasm_other, other_code_hash) = compile_module::("call").unwrap(); - // Define inputs with various actions to test adding / removing delegate_dependencies. + // Define inputs with various actions to test locking / unlocking delegate_dependencies. // See the contract for more details. let noop_input = (0u32, code_hash); - let add_delegate_dependency_input = (1u32, code_hash); - let remove_delegate_dependency_input = (2u32, code_hash); + let lock_delegate_dependency_input = (1u32, code_hash); + let unlock_delegate_dependency_input = (2u32, code_hash); let terminate_input = (3u32, code_hash); // Instantiate the caller contract with the given input. @@ -5457,9 +5457,9 @@ fn add_remove_delegate_dependency_works() { ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { let _ = Balances::set_balance(&ALICE, 1_000_000); - // Instantiate with add_delegate_dependency should fail since the code is not yet on chain. + // Instantiate with lock_delegate_dependency should fail since the code is not yet on chain. assert_err!( - instantiate(&add_delegate_dependency_input).result, + instantiate(&lock_delegate_dependency_input).result, Error::::CodeNotFound ); @@ -5469,7 +5469,7 @@ fn add_remove_delegate_dependency_works() { .unwrap(); // Instantiate should now work. - let addr_caller = instantiate(&add_delegate_dependency_input).result.unwrap().account_id; + let addr_caller = instantiate(&lock_delegate_dependency_input).result.unwrap().account_id; // There should be a dependency and a deposit. let contract = test_utils::get_contract(&addr_caller); @@ -5490,27 +5490,27 @@ fn add_remove_delegate_dependency_works() { >::CodeInUse ); - // Adding an already existing dependency should fail. + // Locking an already existing dependency should fail. assert_err!( - call(&addr_caller, &add_delegate_dependency_input).result, + call(&addr_caller, &lock_delegate_dependency_input).result, Error::::DelegateDependencyAlreadyExists ); - // Adding a dependency to self should fail. + // Locking self should fail. assert_err!( call(&addr_caller, &(1u32, self_code_hash)).result, Error::::CannotAddSelfAsDelegateDependency ); - // Adding more than the maximum allowed delegate_dependencies should fail. + // Locking more than the maximum allowed delegate_dependencies should fail. Contracts::bare_upload_code(ALICE, wasm_other, None, Determinism::Enforced).unwrap(); assert_err!( call(&addr_caller, &(1u32, other_code_hash)).result, Error::::MaxDelegateDependenciesReached ); - // Removing dependency should work. - assert_ok!(call(&addr_caller, &remove_delegate_dependency_input).result); + // Unlocking dependency should work. + assert_ok!(call(&addr_caller, &unlock_delegate_dependency_input).result); // Dependency should be removed, and deposit should be returned. let contract = test_utils::get_contract(&addr_caller); @@ -5525,18 +5525,18 @@ fn add_remove_delegate_dependency_works() { // Removing an unexisting dependency should fail. assert_err!( - call(&addr_caller, &remove_delegate_dependency_input).result, + call(&addr_caller, &unlock_delegate_dependency_input).result, Error::::DelegateDependencyNotFound ); - // Adding a dependency with a storage limit too low should fail. + // Locking a dependency with a storage limit too low should fail. DEFAULT_DEPOSIT_LIMIT.with(|c| *c.borrow_mut() = dependency_deposit - 1); assert_err!( - call(&addr_caller, &add_delegate_dependency_input).result, + call(&addr_caller, &lock_delegate_dependency_input).result, Error::::StorageDepositLimitExhausted ); - // Since we removed the dependency we should now be able to remove the code. + // Since we unlocked the dependency we should now be able to remove the code. assert_ok!(Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash)); // Calling should fail since the delegated contract is not on chain anymore. @@ -5545,7 +5545,7 @@ fn add_remove_delegate_dependency_works() { // Restore initial deposit limit and add the dependency back. DEFAULT_DEPOSIT_LIMIT.with(|c| *c.borrow_mut() = 10_000_000); Contracts::bare_upload_code(ALICE, wasm_callee, None, Determinism::Enforced).unwrap(); - call(&addr_caller, &add_delegate_dependency_input).result.unwrap(); + call(&addr_caller, &lock_delegate_dependency_input).result.unwrap(); // Call terminate should work, and return the deposit. let balance_before = test_utils::get_balance(&ALICE); diff --git a/substrate/frame/contracts/src/wasm/mod.rs b/substrate/frame/contracts/src/wasm/mod.rs index 1c7395ec3bf..5386f4d0ffd 100644 --- a/substrate/frame/contracts/src/wasm/mod.rs +++ b/substrate/frame/contracts/src/wasm/mod.rs @@ -733,14 +733,14 @@ mod tests { Ok(()) } fn decrement_refcount(_code_hash: CodeHash) {} - fn add_delegate_dependency( + fn lock_delegate_dependency( &mut self, code: CodeHash, ) -> Result<(), DispatchError> { self.delegate_dependencies.borrow_mut().insert(code); Ok(()) } - fn remove_delegate_dependency( + fn unlock_delegate_dependency( &mut self, code: &CodeHash, ) -> Result<(), DispatchError> { @@ -3399,16 +3399,16 @@ mod tests { } #[test] - fn add_remove_delegate_dependency() { - const CODE_ADD_REMOVE_DELEGATE_DEPENDENCY: &str = r#" + fn lock_unlock_delegate_dependency() { + const CODE_LOCK_UNLOCK_DELEGATE_DEPENDENCY: &str = r#" (module - (import "seal0" "add_delegate_dependency" (func $add_delegate_dependency (param i32))) - (import "seal0" "remove_delegate_dependency" (func $remove_delegate_dependency (param i32))) + (import "seal0" "lock_delegate_dependency" (func $lock_delegate_dependency (param i32))) + (import "seal0" "unlock_delegate_dependency" (func $unlock_delegate_dependency (param i32))) (import "env" "memory" (memory 1 1)) (func (export "call") - (call $add_delegate_dependency (i32.const 0)) - (call $add_delegate_dependency (i32.const 32)) - (call $remove_delegate_dependency (i32.const 32)) + (call $lock_delegate_dependency (i32.const 0)) + (call $lock_delegate_dependency (i32.const 32)) + (call $unlock_delegate_dependency (i32.const 32)) ) (func (export "deploy")) @@ -3426,7 +3426,7 @@ mod tests { ) "#; let mut mock_ext = MockExt::default(); - assert_ok!(execute(&CODE_ADD_REMOVE_DELEGATE_DEPENDENCY, vec![], &mut mock_ext)); + assert_ok!(execute(&CODE_LOCK_UNLOCK_DELEGATE_DEPENDENCY, vec![], &mut mock_ext)); let delegate_dependencies: Vec<_> = mock_ext.delegate_dependencies.into_inner().into_iter().collect(); assert_eq!(delegate_dependencies.len(), 1); diff --git a/substrate/frame/contracts/src/wasm/runtime.rs b/substrate/frame/contracts/src/wasm/runtime.rs index 74b19910c37..f440c818166 100644 --- a/substrate/frame/contracts/src/wasm/runtime.rs +++ b/substrate/frame/contracts/src/wasm/runtime.rs @@ -246,10 +246,10 @@ pub enum RuntimeCosts { AccountEntranceCount, /// Weight of calling `instantiation_nonce` InstantationNonce, - /// Weight of calling `add_delegate_dependency` - AddDelegateDependency, - /// Weight of calling `remove_delegate_dependency` - RemoveDelegateDependency, + /// Weight of calling `lock_delegate_dependency` + LockDelegateDependency, + /// Weight of calling `unlock_delegate_dependency` + UnlockDelegateDependency, } impl Token for RuntimeCosts { @@ -338,8 +338,8 @@ impl Token for RuntimeCosts { ReentrantCount => s.reentrance_count, AccountEntranceCount => s.account_reentrance_count, InstantationNonce => s.instantiation_nonce, - AddDelegateDependency => s.add_delegate_dependency, - RemoveDelegateDependency => s.remove_delegate_dependency, + LockDelegateDependency => s.lock_delegate_dependency, + UnlockDelegateDependency => s.unlock_delegate_dependency, } } } @@ -1216,7 +1216,6 @@ pub mod env { /// Make a call to another contract. /// See [`pallet_contracts_uapi::HostFn::call_v2`]. #[version(2)] - #[unstable] fn call( ctx: _, memory: _, @@ -1353,7 +1352,6 @@ pub mod env { /// Instantiate a contract with the specified code hash. /// See [`pallet_contracts_uapi::HostFn::instantiate_v2`]. #[version(2)] - #[unstable] fn instantiate( ctx: _, memory: _, @@ -2306,22 +2304,22 @@ pub mod env { } /// Adds a new delegate dependency to the contract. - /// See [`pallet_contracts_uapi::HostFn::add_delegate_dependency`]. + /// See [`pallet_contracts_uapi::HostFn::lock_delegate_dependency`]. #[unstable] - fn add_delegate_dependency(ctx: _, memory: _, code_hash_ptr: u32) -> Result<(), TrapReason> { - ctx.charge_gas(RuntimeCosts::AddDelegateDependency)?; + fn lock_delegate_dependency(ctx: _, memory: _, code_hash_ptr: u32) -> Result<(), TrapReason> { + ctx.charge_gas(RuntimeCosts::LockDelegateDependency)?; let code_hash = ctx.read_sandbox_memory_as(memory, code_hash_ptr)?; - ctx.ext.add_delegate_dependency(code_hash)?; + ctx.ext.lock_delegate_dependency(code_hash)?; Ok(()) } /// Removes the delegate dependency from the contract. - /// see [`pallet_contracts_uapi::HostFn::remove_delegate_dependency`]. + /// see [`pallet_contracts_uapi::HostFn::unlock_delegate_dependency`]. #[unstable] - fn remove_delegate_dependency(ctx: _, memory: _, code_hash_ptr: u32) -> Result<(), TrapReason> { - ctx.charge_gas(RuntimeCosts::RemoveDelegateDependency)?; + fn unlock_delegate_dependency(ctx: _, memory: _, code_hash_ptr: u32) -> Result<(), TrapReason> { + ctx.charge_gas(RuntimeCosts::UnlockDelegateDependency)?; let code_hash = ctx.read_sandbox_memory_as(memory, code_hash_ptr)?; - ctx.ext.remove_delegate_dependency(&code_hash)?; + ctx.ext.unlock_delegate_dependency(&code_hash)?; Ok(()) } } diff --git a/substrate/frame/contracts/src/weights.rs b/substrate/frame/contracts/src/weights.rs index fa9df922a7c..962591290b3 100644 --- a/substrate/frame/contracts/src/weights.rs +++ b/substrate/frame/contracts/src/weights.rs @@ -124,8 +124,8 @@ pub trait WeightInfo { fn seal_ecdsa_recover(r: u32, ) -> Weight; fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight; fn seal_set_code_hash(r: u32, ) -> Weight; - fn add_delegate_dependency(r: u32, ) -> Weight; - fn remove_delegate_dependency(r: u32, ) -> Weight; + fn lock_delegate_dependency(r: u32, ) -> Weight; + fn unlock_delegate_dependency(r: u32, ) -> Weight; fn seal_reentrance_count(r: u32, ) -> Weight; fn seal_account_reentrance_count(r: u32, ) -> Weight; fn seal_instantiation_nonce(r: u32, ) -> Weight; @@ -1891,7 +1891,7 @@ impl WeightInfo for SubstrateWeight { /// Storage: `System::EventTopics` (r:2 w:2) /// Proof: `System::EventTopics` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `r` is `[0, 32]`. - fn add_delegate_dependency(r: u32, ) -> Weight { + fn lock_delegate_dependency(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `928 + r * (131 ±0)` // Estimated: `6878 + r * (2606 ±0)` @@ -1920,7 +1920,7 @@ impl WeightInfo for SubstrateWeight { /// Storage: `System::EventTopics` (r:2 w:2) /// Proof: `System::EventTopics` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `r` is `[0, 32]`. - fn remove_delegate_dependency(r: u32, ) -> Weight { + fn unlock_delegate_dependency(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `969 + r * (183 ±0)` // Estimated: `129453 + r * (2568 ±0)` @@ -3787,7 +3787,7 @@ impl WeightInfo for () { /// Storage: `System::EventTopics` (r:2 w:2) /// Proof: `System::EventTopics` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `r` is `[0, 32]`. - fn add_delegate_dependency(r: u32, ) -> Weight { + fn lock_delegate_dependency(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `928 + r * (131 ±0)` // Estimated: `6878 + r * (2606 ±0)` @@ -3816,7 +3816,7 @@ impl WeightInfo for () { /// Storage: `System::EventTopics` (r:2 w:2) /// Proof: `System::EventTopics` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `r` is `[0, 32]`. - fn remove_delegate_dependency(r: u32, ) -> Weight { + fn unlock_delegate_dependency(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `969 + r * (183 ±0)` // Estimated: `129453 + r * (2568 ±0)` diff --git a/substrate/frame/contracts/uapi/src/host.rs b/substrate/frame/contracts/uapi/src/host.rs index 7894dc3a210..c25be4479ce 100644 --- a/substrate/frame/contracts/uapi/src/host.rs +++ b/substrate/frame/contracts/uapi/src/host.rs @@ -93,7 +93,7 @@ pub trait HostFn { /// - `output`: A reference to the output data buffer to write the address. fn address(output: &mut &mut [u8]); - /// Adds a new delegate dependency to the contract. + /// Lock a new delegate dependency to the contract. /// /// Traps if the maximum number of delegate_dependencies is reached or if /// the delegate dependency already exists. @@ -102,10 +102,7 @@ pub trait HostFn { /// /// - `code_hash`: The code hash of the dependency. Should be decodable as an `T::Hash`. Traps /// otherwise. - #[deprecated( - note = "Unstable function. Behaviour can change without further notice. Use only for testing." - )] - fn add_delegate_dependency(code_hash: &[u8]); + fn lock_delegate_dependency(code_hash: &[u8]); /// Stores the *free* balance of the current account into the supplied buffer. /// @@ -142,6 +139,7 @@ pub trait HostFn { /// /// Equivalent to the newer [`Self::call_v2`] version but works with /// *ref_time* Weight only + #[deprecated(note = "Deprecated, use newer version instead")] fn call_v1( flags: CallFlags, callee: &[u8], @@ -178,9 +176,6 @@ pub trait HostFn { /// - [CalleeTrapped][`crate::ReturnErrorCode::CalleeTrapped] /// - [TransferFailed][`crate::ReturnErrorCode::TransferFailed] /// - [NotCallable][`crate::ReturnErrorCode::NotCallable] - #[deprecated( - note = "Unstable function. Behaviour can change without further notice. Use only for testing." - )] fn call_v2( flags: CallFlags, callee: &[u8], @@ -480,6 +475,7 @@ pub trait HostFn { /// /// Equivalent to the newer [`Self::instantiate_v2`] version but works /// with *ref_time* Weight only. + #[deprecated(note = "Deprecated, use newer version instead")] fn instantiate_v1( code_hash: &[u8], gas: u64, @@ -524,9 +520,6 @@ pub trait HostFn { /// - [CalleeTrapped][`crate::ReturnErrorCode::CalleeTrapped] /// - [TransferFailed][`crate::ReturnErrorCode::TransferFailed] /// - [CodeNotFound][`crate::ReturnErrorCode::CodeNotFound] - #[deprecated( - note = "Unstable function. Behaviour can change without further notice. Use only for testing." - )] fn instantiate_v2( code_hash: &[u8], ref_time_limit: u64, @@ -602,10 +595,7 @@ pub trait HostFn { /// /// - `code_hash`: The code hash of the dependency. Should be decodable as an `T::Hash`. Traps /// otherwise. - #[deprecated( - note = "Unstable function. Behaviour can change without further notice. Use only for testing." - )] - fn remove_delegate_dependency(code_hash: &[u8]); + fn unlock_delegate_dependency(code_hash: &[u8]); /// Cease contract execution and save a data buffer as a result of the execution. /// diff --git a/substrate/frame/contracts/uapi/src/host/riscv32.rs b/substrate/frame/contracts/uapi/src/host/riscv32.rs index dc1c48308df..dbd5abc4240 100644 --- a/substrate/frame/contracts/uapi/src/host/riscv32.rs +++ b/substrate/frame/contracts/uapi/src/host/riscv32.rs @@ -281,11 +281,11 @@ impl HostFn for HostFnImpl { todo!() } - fn add_delegate_dependency(code_hash: &[u8]) { + fn lock_delegate_dependency(code_hash: &[u8]) { todo!() } - fn remove_delegate_dependency(code_hash: &[u8]) { + fn unlock_delegate_dependency(code_hash: &[u8]) { todo!() } diff --git a/substrate/frame/contracts/uapi/src/host/wasm32.rs b/substrate/frame/contracts/uapi/src/host/wasm32.rs index 3df3abeb7dc..9651aa73d6f 100644 --- a/substrate/frame/contracts/uapi/src/host/wasm32.rs +++ b/substrate/frame/contracts/uapi/src/host/wasm32.rs @@ -23,7 +23,7 @@ mod sys { extern "C" { pub fn account_reentrance_count(account_ptr: *const u8) -> u32; - pub fn add_delegate_dependency(code_hash_ptr: *const u8); + pub fn lock_delegate_dependency(code_hash_ptr: *const u8); pub fn address(output_ptr: *mut u8, output_len_ptr: *mut u32); @@ -125,7 +125,7 @@ mod sys { pub fn reentrance_count() -> u32; - pub fn remove_delegate_dependency(code_hash_ptr: *const u8); + pub fn unlock_delegate_dependency(code_hash_ptr: *const u8); pub fn seal_return(flags: u32, data_ptr: *const u8, data_len: u32) -> !; @@ -803,12 +803,12 @@ impl HostFn for HostFnImpl { unsafe { sys::account_reentrance_count(account.as_ptr()) } } - fn add_delegate_dependency(code_hash: &[u8]) { - unsafe { sys::add_delegate_dependency(code_hash.as_ptr()) } + fn lock_delegate_dependency(code_hash: &[u8]) { + unsafe { sys::lock_delegate_dependency(code_hash.as_ptr()) } } - fn remove_delegate_dependency(code_hash: &[u8]) { - unsafe { sys::remove_delegate_dependency(code_hash.as_ptr()) } + fn unlock_delegate_dependency(code_hash: &[u8]) { + unsafe { sys::unlock_delegate_dependency(code_hash.as_ptr()) } } fn instantiation_nonce() -> u64 { -- GitLab From ff40310d872547087dfdb5d60fc2e99fa9d47b16 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Tue, 20 Feb 2024 16:26:11 +0100 Subject: [PATCH 095/133] Externalize received fees checks from `receive_reserve_asset_deposited_from_different_consensus_works` (#3409) Backport of patch-fix: https://github.com/paritytech/polkadot-sdk/pull/3404 --- Cargo.lock | 1 - .../assets/asset-hub-rococo/tests/tests.rs | 176 +++++++++++++++++- .../assets/asset-hub-westend/tests/tests.rs | 169 ++++++++++++++++- .../runtimes/assets/test-utils/Cargo.toml | 2 - .../test-utils/src/test_cases_over_bridge.rs | 134 +++---------- 5 files changed, 346 insertions(+), 136 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c26c4081bbd..2f8adf0e5f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1050,7 +1050,6 @@ dependencies = [ "frame-support", "frame-system", "hex-literal", - "pallet-asset-conversion", "pallet-assets", "pallet-balances", "pallet-collator-selection", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs index cff2290222c..38c118dbb4b 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs @@ -22,13 +22,13 @@ use asset_hub_rococo_runtime::{ xcm_config::{ bridging, AssetFeeAsExistentialDepositMultiplierFeeCharger, CheckingAccount, ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger, ForeignCreatorsSovereignAccountOf, - LocationToAccountId, TokenLocation, TokenLocationV3, TrustBackedAssetsPalletLocation, - TrustBackedAssetsPalletLocationV3, XcmConfig, + LocationToAccountId, StakingPot, TokenLocation, TokenLocationV3, + TrustBackedAssetsPalletLocation, TrustBackedAssetsPalletLocationV3, XcmConfig, }, AllPalletsWithoutSystem, AssetConversion, AssetDeposit, Assets, Balances, CollatorSelection, ExistentialDeposit, ForeignAssets, ForeignAssetsInstance, MetadataDepositBase, - MetadataDepositPerByte, ParachainSystem, Runtime, RuntimeCall, RuntimeEvent, SessionKeys, - ToWestendXcmRouterInstance, TrustBackedAssetsInstance, XcmpQueue, + MetadataDepositPerByte, ParachainSystem, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, + SessionKeys, ToWestendXcmRouterInstance, TrustBackedAssetsInstance, XcmpQueue, }; use asset_test_utils::{ test_cases_over_bridge::TestBridgingConfig, CollatorSessionKey, CollatorSessionKeys, @@ -49,6 +49,7 @@ use frame_support::{ use parachains_common::{AccountId, AssetIdForTrustBackedAssets, AuraId, Balance}; use sp_consensus_aura::SlotDuration; use sp_runtime::traits::MaybeEquivalence; +use sp_std::ops::Mul; use std::convert::Into; use testnet_parachains_constants::rococo::{consensus::*, currency::UNITS, fee::WeightToFee}; use xcm::latest::prelude::{Assets as XcmAssets, *}; @@ -85,6 +86,52 @@ fn slot_durations() -> SlotDurations { } } +fn setup_pool_for_paying_fees_with_foreign_assets( + (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance): ( + AccountId, + xcm::v3::Location, + Balance, + ), +) { + let existential_deposit = ExistentialDeposit::get(); + + // setup a pool to pay fees with `foreign_asset_id_location` tokens + let pool_owner: AccountId = [14u8; 32].into(); + let native_asset = xcm::v3::Location::parent(); + let pool_liquidity: Balance = + existential_deposit.max(foreign_asset_id_minimum_balance).mul(100_000); + + let _ = Balances::force_set_balance( + RuntimeOrigin::root(), + pool_owner.clone().into(), + (existential_deposit + pool_liquidity).mul(2).into(), + ); + + assert_ok!(ForeignAssets::mint( + RuntimeOrigin::signed(foreign_asset_owner), + foreign_asset_id_location.into(), + pool_owner.clone().into(), + (foreign_asset_id_minimum_balance + pool_liquidity).mul(2).into(), + )); + + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(pool_owner.clone()), + Box::new(native_asset.into()), + Box::new(foreign_asset_id_location.into()) + )); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(pool_owner.clone()), + Box::new(native_asset.into()), + Box::new(foreign_asset_id_location.into()), + pool_liquidity, + pool_liquidity, + 1, + 1, + pool_owner, + )); +} + #[test] fn test_buy_and_refund_weight_in_native() { ExtBuilder::::default() @@ -1056,7 +1103,8 @@ fn limited_reserve_transfer_assets_for_native_asset_over_bridge_works( mod asset_hub_rococo_tests { use super::*; - use asset_hub_rococo_runtime::{PolkadotXcm, RuntimeOrigin}; + use asset_hub_rococo_runtime::PolkadotXcm; + use xcm_executor::traits::ConvertLocation; fn bridging_to_asset_hub_westend() -> TestBridgingConfig { let _ = PolkadotXcm::force_xcm_version( @@ -1081,27 +1129,135 @@ mod asset_hub_rococo_tests { } #[test] - fn receive_reserve_asset_deposited_wnd_from_asset_hub_westend_works() { + fn receive_reserve_asset_deposited_wnd_from_asset_hub_westend_fees_paid_by_pool_swap_works() { const BLOCK_AUTHOR_ACCOUNT: [u8; 32] = [13; 32]; + let block_author_account = AccountId::from(BLOCK_AUTHOR_ACCOUNT); + let staking_pot = StakingPot::get(); + + let foreign_asset_id_location = xcm::v3::Location::new( + 2, + [xcm::v3::Junction::GlobalConsensus(xcm::v3::NetworkId::Westend)], + ); + let foreign_asset_id_minimum_balance = 1_000_000_000; + // sovereign account as foreign asset owner (can be whoever for this scenario) + let foreign_asset_owner = + LocationToAccountId::convert_location(&Location::parent()).unwrap(); + let foreign_asset_create_params = + (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance); + + asset_test_utils::test_cases_over_bridge::receive_reserve_asset_deposited_from_different_consensus_works::< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + ForeignAssetsInstance, + >( + collator_session_keys().add(collator_session_key(BLOCK_AUTHOR_ACCOUNT)), + ExistentialDeposit::get(), + AccountId::from([73; 32]), + block_author_account, + // receiving WNDs + foreign_asset_create_params.clone(), + 1000000000000, + || { + // setup pool for paying fees to touch `SwapFirstAssetTrader` + setup_pool_for_paying_fees_with_foreign_assets(foreign_asset_create_params); + // staking pot account for collecting local native fees from `BuyExecution` + let _ = Balances::force_set_balance(RuntimeOrigin::root(), StakingPot::get().into(), ExistentialDeposit::get()); + // prepare bridge configuration + bridging_to_asset_hub_westend() + }, + ( + [PalletInstance(bp_bridge_hub_rococo::WITH_BRIDGE_ROCOCO_TO_WESTEND_MESSAGES_PALLET_INDEX)].into(), + GlobalConsensus(Westend), + [Parachain(1000)].into() + ), + || { + // check staking pot for ED + assert_eq!(Balances::free_balance(&staking_pot), ExistentialDeposit::get()); + // check now foreign asset for staking pot + assert_eq!( + ForeignAssets::balance( + foreign_asset_id_location.into(), + &staking_pot + ), + 0 + ); + }, + || { + // `SwapFirstAssetTrader` - staking pot receives xcm fees in ROCs + assert!( + Balances::free_balance(&staking_pot) > ExistentialDeposit::get() + ); + // staking pot receives no foreign assets + assert_eq!( + ForeignAssets::balance( + foreign_asset_id_location.into(), + &staking_pot + ), + 0 + ); + } + ) + } + + #[test] + fn receive_reserve_asset_deposited_wnd_from_asset_hub_westend_fees_paid_by_sufficient_asset_works( + ) { + const BLOCK_AUTHOR_ACCOUNT: [u8; 32] = [13; 32]; + let block_author_account = AccountId::from(BLOCK_AUTHOR_ACCOUNT); + let staking_pot = StakingPot::get(); + + let foreign_asset_id_location = xcm::v3::Location::new( + 2, + [xcm::v3::Junction::GlobalConsensus(xcm::v3::NetworkId::Westend)], + ); + let foreign_asset_id_minimum_balance = 1_000_000_000; + // sovereign account as foreign asset owner (can be whoever for this scenario) + let foreign_asset_owner = + LocationToAccountId::convert_location(&Location::parent()).unwrap(); + let foreign_asset_create_params = + (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance); + asset_test_utils::test_cases_over_bridge::receive_reserve_asset_deposited_from_different_consensus_works::< Runtime, AllPalletsWithoutSystem, XcmConfig, - LocationToAccountId, ForeignAssetsInstance, >( collator_session_keys().add(collator_session_key(BLOCK_AUTHOR_ACCOUNT)), ExistentialDeposit::get(), AccountId::from([73; 32]), - AccountId::from(BLOCK_AUTHOR_ACCOUNT), + block_author_account.clone(), // receiving WNDs - (xcm::v3::Location::new(2, [xcm::v3::Junction::GlobalConsensus(xcm::v3::NetworkId::Westend)]), 1000000000000, 1_000_000_000), + foreign_asset_create_params, + 1000000000000, bridging_to_asset_hub_westend, ( [PalletInstance(bp_bridge_hub_rococo::WITH_BRIDGE_ROCOCO_TO_WESTEND_MESSAGES_PALLET_INDEX)].into(), GlobalConsensus(Westend), [Parachain(1000)].into() - ) + ), + || { + // check block author before + assert_eq!( + ForeignAssets::balance( + foreign_asset_id_location.into(), + &block_author_account + ), + 0 + ); + }, + || { + // `TakeFirstAssetTrader` puts fees to the block author + assert!( + ForeignAssets::balance( + foreign_asset_id_location.into(), + &block_author_account + ) > 0 + ); + // `SwapFirstAssetTrader` did not work + assert_eq!(Balances::free_balance(&staking_pot), 0); + } ) } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs index 5f6e4c007cd..aa8c3cf2f14 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs @@ -22,8 +22,8 @@ use asset_hub_westend_runtime::{ xcm_config::{ bridging, AssetFeeAsExistentialDepositMultiplierFeeCharger, CheckingAccount, ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger, ForeignCreatorsSovereignAccountOf, - LocationToAccountId, TrustBackedAssetsPalletLocation, TrustBackedAssetsPalletLocationV3, - WestendLocation, WestendLocationV3, XcmConfig, + LocationToAccountId, StakingPot, TrustBackedAssetsPalletLocation, + TrustBackedAssetsPalletLocationV3, WestendLocation, WestendLocationV3, XcmConfig, }, AllPalletsWithoutSystem, Assets, Balances, ExistentialDeposit, ForeignAssets, ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, @@ -50,11 +50,11 @@ use frame_support::{ use parachains_common::{AccountId, AssetIdForTrustBackedAssets, AuraId, Balance}; use sp_consensus_aura::SlotDuration; use sp_runtime::traits::MaybeEquivalence; -use std::convert::Into; +use std::{convert::Into, ops::Mul}; use testnet_parachains_constants::westend::{consensus::*, currency::UNITS, fee::WeightToFee}; use xcm::latest::prelude::{Assets as XcmAssets, *}; use xcm_builder::V4V3LocationConverter; -use xcm_executor::traits::{JustTry, WeightTrader}; +use xcm_executor::traits::{ConvertLocation, JustTry, WeightTrader}; const ALICE: [u8; 32] = [1u8; 32]; const SOME_ASSET_ADMIN: [u8; 32] = [5u8; 32]; @@ -86,6 +86,52 @@ fn slot_durations() -> SlotDurations { } } +fn setup_pool_for_paying_fees_with_foreign_assets( + (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance): ( + AccountId, + xcm::v3::Location, + Balance, + ), +) { + let existential_deposit = ExistentialDeposit::get(); + + // setup a pool to pay fees with `foreign_asset_id_location` tokens + let pool_owner: AccountId = [14u8; 32].into(); + let native_asset = xcm::v3::Location::parent(); + let pool_liquidity: Balance = + existential_deposit.max(foreign_asset_id_minimum_balance).mul(100_000); + + let _ = Balances::force_set_balance( + RuntimeOrigin::root(), + pool_owner.clone().into(), + (existential_deposit + pool_liquidity).mul(2).into(), + ); + + assert_ok!(ForeignAssets::mint( + RuntimeOrigin::signed(foreign_asset_owner), + foreign_asset_id_location.into(), + pool_owner.clone().into(), + (foreign_asset_id_minimum_balance + pool_liquidity).mul(2).into(), + )); + + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(pool_owner.clone()), + Box::new(native_asset.into()), + Box::new(foreign_asset_id_location.into()) + )); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(pool_owner.clone()), + Box::new(native_asset.into()), + Box::new(foreign_asset_id_location.into()), + pool_liquidity, + pool_liquidity, + 1, + 1, + pool_owner, + )); +} + #[test] fn test_buy_and_refund_weight_in_native() { ExtBuilder::::default() @@ -1071,30 +1117,133 @@ fn limited_reserve_transfer_assets_for_native_asset_to_asset_hub_rococo_works() Some(xcm_config::TreasuryAccount::get()), ) } + #[test] -fn receive_reserve_asset_deposited_roc_from_asset_hub_rococo_works() { +fn receive_reserve_asset_deposited_roc_from_asset_hub_rococo_fees_paid_by_pool_swap_works() { const BLOCK_AUTHOR_ACCOUNT: [u8; 32] = [13; 32]; + let block_author_account = AccountId::from(BLOCK_AUTHOR_ACCOUNT); + let staking_pot = StakingPot::get(); + + let foreign_asset_id_location = + xcm::v3::Location::new(2, [xcm::v3::Junction::GlobalConsensus(xcm::v3::NetworkId::Rococo)]); + let foreign_asset_id_minimum_balance = 1_000_000_000; + // sovereign account as foreign asset owner (can be whoever for this scenario) + let foreign_asset_owner = LocationToAccountId::convert_location(&Location::parent()).unwrap(); + let foreign_asset_create_params = + (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance); + asset_test_utils::test_cases_over_bridge::receive_reserve_asset_deposited_from_different_consensus_works::< Runtime, AllPalletsWithoutSystem, XcmConfig, - LocationToAccountId, ForeignAssetsInstance, >( collator_session_keys().add(collator_session_key(BLOCK_AUTHOR_ACCOUNT)), ExistentialDeposit::get(), AccountId::from([73; 32]), - AccountId::from(BLOCK_AUTHOR_ACCOUNT), + block_author_account.clone(), // receiving ROCs - (xcm::v3::Location::new(2, [xcm::v3::Junction::GlobalConsensus(xcm::v3::NetworkId::Rococo)]), 1000000000000, 1_000_000_000), - bridging_to_asset_hub_rococo, + foreign_asset_create_params.clone(), + 1000000000000, + || { + // setup pool for paying fees to touch `SwapFirstAssetTrader` + setup_pool_for_paying_fees_with_foreign_assets(foreign_asset_create_params); + // staking pot account for collecting local native fees from `BuyExecution` + let _ = Balances::force_set_balance(RuntimeOrigin::root(), StakingPot::get().into(), ExistentialDeposit::get()); + // prepare bridge configuration + bridging_to_asset_hub_rococo() + }, ( [PalletInstance(bp_bridge_hub_westend::WITH_BRIDGE_WESTEND_TO_ROCOCO_MESSAGES_PALLET_INDEX)].into(), GlobalConsensus(Rococo), [Parachain(1000)].into() - ) + ), + || { + // check staking pot for ED + assert_eq!(Balances::free_balance(&staking_pot), ExistentialDeposit::get()); + // check now foreign asset for staking pot + assert_eq!( + ForeignAssets::balance( + foreign_asset_id_location.into(), + &staking_pot + ), + 0 + ); + }, + || { + // `SwapFirstAssetTrader` - staking pot receives xcm fees in ROCs + assert!( + Balances::free_balance(&staking_pot) > ExistentialDeposit::get() + ); + // staking pot receives no foreign assets + assert_eq!( + ForeignAssets::balance( + foreign_asset_id_location.into(), + &staking_pot + ), + 0 + ); + } ) } + +#[test] +fn receive_reserve_asset_deposited_roc_from_asset_hub_rococo_fees_paid_by_sufficient_asset_works() { + const BLOCK_AUTHOR_ACCOUNT: [u8; 32] = [13; 32]; + let block_author_account = AccountId::from(BLOCK_AUTHOR_ACCOUNT); + let staking_pot = StakingPot::get(); + + let foreign_asset_id_location = + xcm::v3::Location::new(2, [xcm::v3::Junction::GlobalConsensus(xcm::v3::NetworkId::Rococo)]); + let foreign_asset_id_minimum_balance = 1_000_000_000; + // sovereign account as foreign asset owner (can be whoever for this scenario) + let foreign_asset_owner = LocationToAccountId::convert_location(&Location::parent()).unwrap(); + let foreign_asset_create_params = + (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance); + + asset_test_utils::test_cases_over_bridge::receive_reserve_asset_deposited_from_different_consensus_works::< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + ForeignAssetsInstance, + >( + collator_session_keys().add(collator_session_key(BLOCK_AUTHOR_ACCOUNT)), + ExistentialDeposit::get(), + AccountId::from([73; 32]), + block_author_account.clone(), + // receiving ROCs + foreign_asset_create_params, + 1000000000000, + bridging_to_asset_hub_rococo, + ( + [PalletInstance(bp_bridge_hub_westend::WITH_BRIDGE_WESTEND_TO_ROCOCO_MESSAGES_PALLET_INDEX)].into(), + GlobalConsensus(Rococo), + [Parachain(1000)].into() + ), + || { + // check block author before + assert_eq!( + ForeignAssets::balance( + foreign_asset_id_location.into(), + &block_author_account + ), + 0 + ); + }, + || { + // `TakeFirstAssetTrader` puts fees to the block author + assert!( + ForeignAssets::balance( + foreign_asset_id_location.into(), + &block_author_account + ) > 0 + ); + // `SwapFirstAssetTrader` did not work + assert_eq!(Balances::free_balance(&staking_pot), 0); + } + ) +} + #[test] fn report_bridge_status_from_xcm_bridge_router_for_rococo_works() { asset_test_utils::test_cases_over_bridge::report_bridge_status_from_xcm_bridge_router_works::< diff --git a/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml b/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml index f1b3953ce47..89c4925cb1a 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml @@ -16,7 +16,6 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = frame-support = { path = "../../../../../substrate/frame/support", default-features = false } frame-system = { path = "../../../../../substrate/frame/system", default-features = false } pallet-assets = { path = "../../../../../substrate/frame/assets", default-features = false } -pallet-asset-conversion = { path = "../../../../../substrate/frame/asset-conversion", default-features = false } pallet-balances = { path = "../../../../../substrate/frame/balances", default-features = false } pallet-session = { path = "../../../../../substrate/frame/session", default-features = false } sp-io = { path = "../../../../../substrate/primitives/io", default-features = false } @@ -56,7 +55,6 @@ std = [ "cumulus-primitives-core/std", "frame-support/std", "frame-system/std", - "pallet-asset-conversion/std", "pallet-assets/std", "pallet-balances/std", "pallet-collator-selection/std", diff --git a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs index 4a0f31c449e..66ed3417951 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs +++ b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs @@ -30,7 +30,6 @@ use parachains_runtimes_test_utils::{ SlotDurations, ValidatorIdOf, XcmReceivedFrom, }; use sp_runtime::{traits::StaticLookup, Saturating}; -use sp_std::ops::Mul; use xcm::{latest::prelude::*, VersionedAssets}; use xcm_builder::{CreateMatcher, MatchXcm}; use xcm_executor::{traits::ConvertLocation, XcmExecutor}; @@ -323,20 +322,22 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< Runtime, AllPalletsWithoutSystem, XcmConfig, - LocationToAccountId, ForeignAssetsPalletInstance, >( collator_session_keys: CollatorSessionKeys, existential_deposit: BalanceOf, target_account: AccountIdOf, block_author_account: AccountIdOf, - ( - foreign_asset_id_location, - transfered_foreign_asset_id_amount, - foreign_asset_id_minimum_balance, - ): (xcm::v3::Location, u128, u128), - prepare_configuration: fn() -> TestBridgingConfig, + (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance): ( + AccountIdOf, + xcm::v3::Location, + u128, + ), + foreign_asset_id_amount_to_transfer: u128, + prepare_configuration: impl FnOnce() -> TestBridgingConfig, (bridge_instance, universal_origin, descend_origin): (Junctions, Junction, Junctions), /* bridge adds origin manipulation on the way */ + additional_checks_before: impl FnOnce(), + additional_checks_after: impl FnOnce(), ) where Runtime: frame_system::Config + pallet_balances::Config @@ -346,15 +347,13 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config + cumulus_pallet_xcmp_queue::Config - + pallet_assets::Config - + pallet_asset_conversion::Config, + + pallet_assets::Config, AllPalletsWithoutSystem: OnInitialize> + OnFinalize>, AccountIdOf: Into<[u8; 32]> + From<[u8; 32]>, ValidatorIdOf: From>, BalanceOf: From + Into, XcmConfig: xcm_executor::Config, - LocationToAccountId: ConvertLocation>, >::AssetId: From + Into, >::AssetIdParameter: @@ -365,9 +364,6 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< + Into, <::Lookup as StaticLookup>::Source: From<::AccountId>, - ::AssetKind: - From + Into, - ::Balance: From, ForeignAssetsPalletInstance: 'static, { ExtBuilder::::default() @@ -382,88 +378,31 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< block_author_account.clone().into(), ); - // prepare bridge config - let TestBridgingConfig { local_bridge_hub_location, .. } = prepare_configuration(); - // drip 'ED' user target account let _ = >::deposit_creating( &target_account, existential_deposit, ); - // sovereign account as foreign asset owner (can be whoever for this scenario, doesnt - // matter) - let sovereign_account_as_owner_of_foreign_asset = - LocationToAccountId::convert_location(&Location::parent()).unwrap(); - - // staking pot account for collecting local native fees from `BuyExecution` - let staking_pot = >::account_id(); - let _ = >::deposit_creating( - &staking_pot, - existential_deposit, - ); - // create foreign asset for wrapped/derivated representation assert_ok!( >::force_create( RuntimeHelper::::root_origin(), foreign_asset_id_location.into(), - sovereign_account_as_owner_of_foreign_asset.clone().into(), + foreign_asset_owner.into(), true, // is_sufficient=true foreign_asset_id_minimum_balance.into() ) ); - // setup a pool to pay fees with `foreign_asset_id_location` tokens - let pool_owner: AccountIdOf = [1u8; 32].into(); - let native_asset = xcm::v3::Location::parent(); - let pool_liquidity: u128 = - existential_deposit.into().max(foreign_asset_id_minimum_balance).mul(100_000); - - let _ = >::deposit_creating( - &pool_owner, - (existential_deposit.into() + pool_liquidity).mul(2).into(), - ); - - assert_ok!(>::mint( - RuntimeHelper::::origin_of( - sovereign_account_as_owner_of_foreign_asset - ), - foreign_asset_id_location.into(), - pool_owner.clone().into(), - (foreign_asset_id_minimum_balance + pool_liquidity).mul(2).into(), - )); - - assert_ok!(>::create_pool( - RuntimeHelper::::origin_of(pool_owner.clone()), - Box::new(native_asset.into()), - Box::new(foreign_asset_id_location.into()) - )); - - assert_ok!(>::add_liquidity( - RuntimeHelper::::origin_of(pool_owner.clone()), - Box::new(native_asset.into()), - Box::new(foreign_asset_id_location.into()), - pool_liquidity.into(), - pool_liquidity.into(), - 1.into(), - 1.into(), - pool_owner, - )); + // prepare bridge config + let TestBridgingConfig { local_bridge_hub_location, .. } = prepare_configuration(); // Balances before assert_eq!( >::free_balance(&target_account), existential_deposit.clone() ); - assert_eq!( - >::free_balance(&block_author_account), - 0.into() - ); - assert_eq!( - >::free_balance(&staking_pot), - existential_deposit.clone() - ); // ForeignAssets balances before assert_eq!( @@ -473,27 +412,16 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< ), 0.into() ); - assert_eq!( - >::balance( - foreign_asset_id_location.into(), - &block_author_account - ), - 0.into() - ); - assert_eq!( - >::balance( - foreign_asset_id_location.into(), - &staking_pot - ), - 0.into() - ); + + // additional check before + additional_checks_before(); let foreign_asset_id_location_latest: Location = foreign_asset_id_location.try_into().unwrap(); let expected_assets = Assets::from(vec![Asset { id: AssetId(foreign_asset_id_location_latest.clone()), - fun: Fungible(transfered_foreign_asset_id_amount), + fun: Fungible(foreign_asset_id_amount_to_transfer), }]); let expected_beneficiary = Location::new( 0, @@ -510,7 +438,7 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< BuyExecution { fees: Asset { id: AssetId(foreign_asset_id_location_latest.clone()), - fun: Fungible(transfered_foreign_asset_id_amount), + fun: Fungible(foreign_asset_id_amount_to_transfer), }, weight_limit: Unlimited, }, @@ -544,19 +472,10 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< assert_ok!(outcome.ensure_complete()); // Balances after - // staking pot receives xcm fees in dot - assert!( - >::free_balance(&staking_pot) != - existential_deposit - ); assert_eq!( >::free_balance(&target_account), existential_deposit.clone() ); - assert_eq!( - >::free_balance(&block_author_account), - 0.into() - ); // ForeignAssets balances after assert!( @@ -565,20 +484,9 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< &target_account ) > 0.into() ); - assert_eq!( - >::balance( - foreign_asset_id_location.into(), - &staking_pot - ), - 0.into() - ); - assert_eq!( - >::balance( - foreign_asset_id_location.into(), - &block_author_account - ), - 0.into() - ); + + // additional check after + additional_checks_after(); }) } -- GitLab From fee810a5ea177f95ddf13ea51270b84f1e117bae Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 20 Feb 2024 17:16:21 +0100 Subject: [PATCH 096/133] rpc server: make possible to disable/enable batch requests (#3364) The rationale behind this, is that it may be useful for some users actually disable RPC batch requests or limit them by length instead of the total size bytes of the batch. This PR adds two new CLI options: ``` --rpc-disable-batch-requests - disable batch requests on the server --rpc-max-batch-request-len - limit batches to LEN on the server. ``` --- .gitlab/pipeline/check.yml | 1 + cumulus/client/cli/src/lib.rs | 10 ++++++- cumulus/test/service/src/lib.rs | 3 ++- polkadot/node/test/service/src/lib.rs | 5 ++-- prdoc/pr_3364.prdoc | 12 +++++++++ .../bin/node/cli/benches/block_production.rs | 3 ++- .../bin/node/cli/benches/transaction_pool.rs | 3 ++- substrate/client/cli/src/commands/run_cmd.rs | 26 ++++++++++++++++++- substrate/client/cli/src/config.rs | 11 ++++++-- substrate/client/cli/src/runner.rs | 1 + substrate/client/rpc-servers/src/lib.rs | 6 ++++- substrate/client/service/src/config.rs | 3 +++ substrate/client/service/src/lib.rs | 1 + substrate/client/service/test/src/lib.rs | 3 ++- 14 files changed, 77 insertions(+), 11 deletions(-) create mode 100644 prdoc/pr_3364.prdoc diff --git a/.gitlab/pipeline/check.yml b/.gitlab/pipeline/check.yml index 1ed12e68c2c..cdb5d1b05d0 100644 --- a/.gitlab/pipeline/check.yml +++ b/.gitlab/pipeline/check.yml @@ -133,6 +133,7 @@ check-runtime-migration-westend: WASM: "westend_runtime.compact.compressed.wasm" URI: "wss://westend-try-runtime-node.parity-chains.parity.io:443" SUBCOMMAND_EXTRA_ARGS: "--no-weight-warnings" + allow_failure: true check-runtime-migration-rococo: stage: check diff --git a/cumulus/client/cli/src/lib.rs b/cumulus/client/cli/src/lib.rs index 1807b8a1718..a7b2eb19de8 100644 --- a/cumulus/client/cli/src/lib.rs +++ b/cumulus/client/cli/src/lib.rs @@ -30,7 +30,7 @@ use codec::Encode; use sc_chain_spec::ChainSpec; use sc_client_api::HeaderBackend; use sc_service::{ - config::{PrometheusConfig, TelemetryEndpoints}, + config::{PrometheusConfig, RpcBatchRequestConfig, TelemetryEndpoints}, BasePath, TransactionPoolOptions, }; use sp_core::hexdisplay::HexDisplay; @@ -443,6 +443,14 @@ impl sc_cli::CliConfiguration for NormalizedRunCmd { Ok(self.base.rpc_max_subscriptions_per_connection) } + fn rpc_buffer_capacity_per_connection(&self) -> sc_cli::Result { + Ok(self.base.rpc_message_buffer_capacity_per_connection) + } + + fn rpc_batch_config(&self) -> sc_cli::Result { + self.base.rpc_batch_config() + } + fn transaction_pool(&self, is_dev: bool) -> sc_cli::Result { self.base.transaction_pool(is_dev) } diff --git a/cumulus/test/service/src/lib.rs b/cumulus/test/service/src/lib.rs index 229bde4d19c..1c2e1db9741 100644 --- a/cumulus/test/service/src/lib.rs +++ b/cumulus/test/service/src/lib.rs @@ -67,7 +67,7 @@ use sc_network::{ use sc_service::{ config::{ BlocksPruning, DatabaseSource, KeystoreConfig, MultiaddrWithPeerId, NetworkConfiguration, - OffchainWorkerConfig, PruningMode, WasmExecutionMethod, + OffchainWorkerConfig, PruningMode, RpcBatchRequestConfig, WasmExecutionMethod, }, BasePath, ChainSpec as ChainSpecService, Configuration, Error as ServiceError, PartialComponents, Role, RpcHandlers, TFullBackend, TFullClient, TaskManager, @@ -801,6 +801,7 @@ pub fn node_config( rpc_max_subs_per_conn: Default::default(), rpc_port: 9945, rpc_message_buffer_capacity: Default::default(), + rpc_batch_config: RpcBatchRequestConfig::Unlimited, rpc_rate_limit: None, prometheus_config: None, telemetry_endpoints: None, diff --git a/polkadot/node/test/service/src/lib.rs b/polkadot/node/test/service/src/lib.rs index a3af977ab3e..ca904bae28d 100644 --- a/polkadot/node/test/service/src/lib.rs +++ b/polkadot/node/test/service/src/lib.rs @@ -44,8 +44,8 @@ use sc_network::{ }; use sc_service::{ config::{ - DatabaseSource, KeystoreConfig, MultiaddrWithPeerId, WasmExecutionMethod, - WasmtimeInstantiationStrategy, + DatabaseSource, KeystoreConfig, MultiaddrWithPeerId, RpcBatchRequestConfig, + WasmExecutionMethod, WasmtimeInstantiationStrategy, }, BasePath, BlocksPruning, Configuration, Role, RpcHandlers, TaskManager, }; @@ -186,6 +186,7 @@ pub fn node_config( rpc_max_subs_per_conn: Default::default(), rpc_port: 9944, rpc_message_buffer_capacity: Default::default(), + rpc_batch_config: RpcBatchRequestConfig::Unlimited, rpc_rate_limit: None, prometheus_config: None, telemetry_endpoints: None, diff --git a/prdoc/pr_3364.prdoc b/prdoc/pr_3364.prdoc new file mode 100644 index 00000000000..1e7a6a5278d --- /dev/null +++ b/prdoc/pr_3364.prdoc @@ -0,0 +1,12 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: rpc server expose batch request configuration + +doc: + - audience: Node Operator + description: | + Add functionality to limit RPC batch requests by two new CLI options: + --rpc-disable-batch-request - disable batch requests on the server + --rpc-max-batch-request-len - limit batches to LEN on the server +crates: [ ] diff --git a/substrate/bin/node/cli/benches/block_production.rs b/substrate/bin/node/cli/benches/block_production.rs index d28cfbddfd2..23a62cc0bd2 100644 --- a/substrate/bin/node/cli/benches/block_production.rs +++ b/substrate/bin/node/cli/benches/block_production.rs @@ -28,7 +28,7 @@ use sc_consensus::{ use sc_service::{ config::{ BlocksPruning, DatabaseSource, KeystoreConfig, NetworkConfiguration, OffchainWorkerConfig, - PruningMode, WasmExecutionMethod, WasmtimeInstantiationStrategy, + PruningMode, RpcBatchRequestConfig, WasmExecutionMethod, WasmtimeInstantiationStrategy, }, BasePath, Configuration, Role, }; @@ -84,6 +84,7 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { rpc_max_subs_per_conn: Default::default(), rpc_port: 9944, rpc_message_buffer_capacity: Default::default(), + rpc_batch_config: RpcBatchRequestConfig::Unlimited, rpc_rate_limit: None, prometheus_config: None, telemetry_endpoints: None, diff --git a/substrate/bin/node/cli/benches/transaction_pool.rs b/substrate/bin/node/cli/benches/transaction_pool.rs index 1e25b7ce6fd..de4eef1944d 100644 --- a/substrate/bin/node/cli/benches/transaction_pool.rs +++ b/substrate/bin/node/cli/benches/transaction_pool.rs @@ -26,7 +26,7 @@ use node_primitives::AccountId; use sc_service::{ config::{ BlocksPruning, DatabaseSource, KeystoreConfig, NetworkConfiguration, OffchainWorkerConfig, - PruningMode, TransactionPoolOptions, + PruningMode, RpcBatchRequestConfig, TransactionPoolOptions, }, BasePath, Configuration, Role, }; @@ -80,6 +80,7 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { rpc_max_subs_per_conn: Default::default(), rpc_port: 9944, rpc_message_buffer_capacity: Default::default(), + rpc_batch_config: RpcBatchRequestConfig::Unlimited, rpc_rate_limit: None, prometheus_config: None, telemetry_endpoints: None, diff --git a/substrate/client/cli/src/commands/run_cmd.rs b/substrate/client/cli/src/commands/run_cmd.rs index c50198cf3cb..221c32affd5 100644 --- a/substrate/client/cli/src/commands/run_cmd.rs +++ b/substrate/client/cli/src/commands/run_cmd.rs @@ -30,7 +30,7 @@ use crate::{ use clap::Parser; use regex::Regex; use sc_service::{ - config::{BasePath, PrometheusConfig, TransactionPoolOptions}, + config::{BasePath, PrometheusConfig, RpcBatchRequestConfig, TransactionPoolOptions}, ChainSpec, Role, }; use sc_telemetry::TelemetryEndpoints; @@ -125,6 +125,14 @@ pub struct RunCmd { #[arg(long, default_value_t = RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN)] pub rpc_message_buffer_capacity_per_connection: u32, + /// Disable RPC batch requests + #[arg(long, alias = "rpc_no_batch_requests", conflicts_with_all = &["rpc_max_batch_request_len"])] + pub rpc_disable_batch_requests: bool, + + /// Limit the max length per RPC batch request + #[arg(long, conflicts_with_all = &["rpc_disable_batch_requests"], value_name = "LEN")] + pub rpc_max_batch_request_len: Option, + /// Specify browser *origins* allowed to access the HTTP & WS RPC servers. /// /// A comma-separated list of origins (protocol://domain or special `null` @@ -411,6 +419,22 @@ impl CliConfiguration for RunCmd { Ok(self.rpc_max_subscriptions_per_connection) } + fn rpc_buffer_capacity_per_connection(&self) -> Result { + Ok(self.rpc_message_buffer_capacity_per_connection) + } + + fn rpc_batch_config(&self) -> Result { + let cfg = if self.rpc_disable_batch_requests { + RpcBatchRequestConfig::Disabled + } else if let Some(l) = self.rpc_max_batch_request_len { + RpcBatchRequestConfig::Limit(l) + } else { + RpcBatchRequestConfig::Unlimited + }; + + Ok(cfg) + } + fn rpc_rate_limit(&self) -> Result> { Ok(self.rpc_rate_limit) } diff --git a/substrate/client/cli/src/config.rs b/substrate/client/cli/src/config.rs index 78015cf8373..5def9ce9b72 100644 --- a/substrate/client/cli/src/config.rs +++ b/substrate/client/cli/src/config.rs @@ -28,7 +28,8 @@ use sc_service::{ config::{ BasePath, Configuration, DatabaseSource, KeystoreConfig, NetworkConfiguration, NodeKeyConfig, OffchainWorkerConfig, OutputFormat, PrometheusConfig, PruningMode, Role, - RpcMethods, TelemetryEndpoints, TransactionPoolOptions, WasmExecutionMethod, + RpcBatchRequestConfig, RpcMethods, TelemetryEndpoints, TransactionPoolOptions, + WasmExecutionMethod, }, BlocksPruning, ChainSpec, TracingReceiver, }; @@ -338,7 +339,12 @@ pub trait CliConfiguration: Sized { Ok(RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN) } - /// Rate limit calls per minute. + /// RPC server batch request configuration. + fn rpc_batch_config(&self) -> Result { + Ok(RpcBatchRequestConfig::Unlimited) + } + + /// RPC rate limit configuration. fn rpc_rate_limit(&self) -> Result> { Ok(None) } @@ -515,6 +521,7 @@ pub trait CliConfiguration: Sized { rpc_max_subs_per_conn: self.rpc_max_subscriptions_per_connection()?, rpc_port: DCV::rpc_listen_port(), rpc_message_buffer_capacity: self.rpc_buffer_capacity_per_connection()?, + rpc_batch_config: self.rpc_batch_config()?, rpc_rate_limit: self.rpc_rate_limit()?, prometheus_config: self .prometheus_config(DCV::prometheus_listen_port(), &chain_spec)?, diff --git a/substrate/client/cli/src/runner.rs b/substrate/client/cli/src/runner.rs index b4937db71e6..4201a0f4062 100644 --- a/substrate/client/cli/src/runner.rs +++ b/substrate/client/cli/src/runner.rs @@ -271,6 +271,7 @@ mod tests { rpc_max_subs_per_conn: Default::default(), rpc_message_buffer_capacity: Default::default(), rpc_port: 9944, + rpc_batch_config: sc_service::config::RpcBatchRequestConfig::Unlimited, rpc_rate_limit: None, prometheus_config: None, telemetry_endpoints: None, diff --git a/substrate/client/rpc-servers/src/lib.rs b/substrate/client/rpc-servers/src/lib.rs index a22b7309ac1..e65d954bed5 100644 --- a/substrate/client/rpc-servers/src/lib.rs +++ b/substrate/client/rpc-servers/src/lib.rs @@ -47,7 +47,7 @@ pub use jsonrpsee::{ id_providers::{RandomIntegerIdProvider, RandomStringIdProvider}, traits::IdProvider, }, - server::middleware::rpc::RpcServiceBuilder, + server::{middleware::rpc::RpcServiceBuilder, BatchRequestConfig}, }; pub use middleware::{MetricsLayer, RateLimitLayer, RpcMetrics}; @@ -81,6 +81,8 @@ pub struct Config<'a, M: Send + Sync + 'static> { pub id_provider: Option>, /// Tokio runtime handle. pub tokio_handle: tokio::runtime::Handle, + /// Batch request config. + pub batch_config: BatchRequestConfig, /// Rate limit calls per minute. pub rate_limit: Option, } @@ -103,6 +105,7 @@ where { let Config { addrs, + batch_config, cors, max_payload_in_mb, max_payload_out_mb, @@ -139,6 +142,7 @@ where ) .set_http_middleware(http_middleware) .set_message_buffer_capacity(message_buffer_capacity) + .set_batch_request_config(batch_config) .custom_tokio_runtime(tokio_handle.clone()); if let Some(provider) = id_provider { diff --git a/substrate/client/service/src/config.rs b/substrate/client/service/src/config.rs index 74c5dd775b0..35262ff493b 100644 --- a/substrate/client/service/src/config.rs +++ b/substrate/client/service/src/config.rs @@ -18,6 +18,7 @@ //! Service configuration. +pub use jsonrpsee::server::BatchRequestConfig as RpcBatchRequestConfig; use prometheus_endpoint::Registry; use sc_chain_spec::ChainSpec; pub use sc_client_db::{BlocksPruning, Database, DatabaseSource, PruningMode}; @@ -103,6 +104,8 @@ pub struct Configuration { pub rpc_port: u16, /// The number of messages the JSON-RPC server is allowed to keep in memory. pub rpc_message_buffer_capacity: u32, + /// JSON-RPC server batch config. + pub rpc_batch_config: RpcBatchRequestConfig, /// RPC rate limit per minute. pub rpc_rate_limit: Option, /// Prometheus endpoint configuration. `None` if disabled. diff --git a/substrate/client/service/src/lib.rs b/substrate/client/service/src/lib.rs index d0adf4f7d5c..9480d4a0b07 100644 --- a/substrate/client/service/src/lib.rs +++ b/substrate/client/service/src/lib.rs @@ -393,6 +393,7 @@ where let server_config = sc_rpc_server::Config { addrs: [addr, backup_addr], + batch_config: config.rpc_batch_config, max_connections: config.rpc_max_connections, max_payload_in_mb: config.rpc_max_request_size, max_payload_out_mb: config.rpc_max_response_size, diff --git a/substrate/client/service/test/src/lib.rs b/substrate/client/service/test/src/lib.rs index 6148bb05fcf..349538965ee 100644 --- a/substrate/client/service/test/src/lib.rs +++ b/substrate/client/service/test/src/lib.rs @@ -29,7 +29,7 @@ use sc_network::{ use sc_network_sync::SyncingService; use sc_service::{ client::Client, - config::{BasePath, DatabaseSource, KeystoreConfig}, + config::{BasePath, DatabaseSource, KeystoreConfig, RpcBatchRequestConfig}, BlocksPruning, ChainSpecExtension, Configuration, Error, GenericChainSpec, Role, RuntimeGenesis, SpawnTaskHandle, TaskManager, }; @@ -254,6 +254,7 @@ fn node_config< rpc_max_subs_per_conn: Default::default(), rpc_port: 9944, rpc_message_buffer_capacity: Default::default(), + rpc_batch_config: RpcBatchRequestConfig::Unlimited, rpc_rate_limit: None, prometheus_config: None, telemetry_endpoints: None, -- GitLab From b77b3a2de5be4d413dede8262a92f87c0d63959f Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Tue, 20 Feb 2024 22:00:44 +0100 Subject: [PATCH 097/133] Nits found during the bumping fellows repo (#3410) --- .../runtimes/assets/asset-hub-rococo/src/lib.rs | 6 ++++-- .../runtimes/assets/asset-hub-westend/src/lib.rs | 1 + cumulus/parachains/runtimes/assets/common/src/lib.rs | 2 +- .../runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs | 1 + .../runtimes/contracts/contracts-rococo/src/lib.rs | 1 + .../parachains/runtimes/people/people-rococo/src/lib.rs | 2 ++ .../parachains/runtimes/people/people-westend/src/lib.rs | 2 ++ .../parachains/runtimes/testing/penpal/src/xcm_config.rs | 2 +- polkadot/runtime/common/src/impls.rs | 8 ++++---- polkadot/xcm/src/lib.rs | 2 +- substrate/frame/asset-conversion/src/lib.rs | 2 +- 11 files changed, 19 insertions(+), 10 deletions(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index ed893091533..7e550c32917 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -383,8 +383,8 @@ pub type ForeignAssetsInstance = pallet_assets::Instance2; impl pallet_assets::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Balance = Balance; - type AssetId = xcm::v3::MultiLocation; - type AssetIdParameter = xcm::v3::MultiLocation; + type AssetId = xcm::v3::Location; + type AssetIdParameter = xcm::v3::Location; type Currency = Balances; type CreateOrigin = ForeignCreators< ( @@ -1043,6 +1043,7 @@ mod benches { [pallet_assets, Pool] [pallet_asset_conversion, AssetConversion] [pallet_balances, Balances] + [pallet_message_queue, MessageQueue] [pallet_multisig, Multisig] [pallet_nft_fractionalization, NftFractionalization] [pallet_nfts, Nfts] @@ -1052,6 +1053,7 @@ mod benches { [pallet_utility, Utility] [pallet_timestamp, Timestamp] [pallet_collator_selection, CollatorSelection] + [cumulus_pallet_parachain_system, ParachainSystem] [cumulus_pallet_xcmp_queue, XcmpQueue] [pallet_xcm_bridge_hub_router, ToWestend] // XCM diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index bcd3a9aa896..a3166106073 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -1084,6 +1084,7 @@ mod benches { [pallet_utility, Utility] [pallet_timestamp, Timestamp] [pallet_collator_selection, CollatorSelection] + [cumulus_pallet_parachain_system, ParachainSystem] [cumulus_pallet_xcmp_queue, XcmpQueue] [pallet_xcm_bridge_hub_router, ToRococo] // XCM diff --git a/cumulus/parachains/runtimes/assets/common/src/lib.rs b/cumulus/parachains/runtimes/assets/common/src/lib.rs index a0c912b6f0f..fa2752179eb 100644 --- a/cumulus/parachains/runtimes/assets/common/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/common/src/lib.rs @@ -371,7 +371,7 @@ mod tests { for (asset, expected_result) in test_data { assert_eq!( - >::matches_fungibles( + >::matches_fungibles( &asset.clone().try_into().unwrap() ), expected_result, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 8ffea24b3cc..997320ffcf2 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -522,6 +522,7 @@ mod benches { [pallet_utility, Utility] [pallet_timestamp, Timestamp] [pallet_collator_selection, CollatorSelection] + [cumulus_pallet_parachain_system, ParachainSystem] [cumulus_pallet_xcmp_queue, XcmpQueue] // XCM [pallet_xcm, PalletXcmExtrinsicsBenchmark::] diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs index 15a5d9b04a9..94cb0e502e4 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs @@ -427,6 +427,7 @@ mod benches { [pallet_sudo, Sudo] [pallet_timestamp, Timestamp] [pallet_collator_selection, CollatorSelection] + [cumulus_pallet_parachain_system, ParachainSystem] [pallet_contracts, Contracts] [pallet_xcm, PalletXcmExtrinsicsBenchmark::] ); diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs index 9e7dd6c884b..570dc0fa12c 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -448,6 +448,7 @@ mod benches { [frame_system, SystemBench::] [pallet_balances, Balances] [pallet_identity, Identity] + [pallet_message_queue, MessageQueue] [pallet_multisig, Multisig] [pallet_session, SessionBench::] [pallet_utility, Utility] @@ -455,6 +456,7 @@ mod benches { // Polkadot [polkadot_runtime_common::identity_migrator, IdentityMigrator] // Cumulus + [cumulus_pallet_parachain_system, ParachainSystem] [cumulus_pallet_xcmp_queue, XcmpQueue] [pallet_collator_selection, CollatorSelection] // XCM diff --git a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs index 4ff743aefe1..a47df66f503 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs @@ -448,6 +448,7 @@ mod benches { [frame_system, SystemBench::] [pallet_balances, Balances] [pallet_identity, Identity] + [pallet_message_queue, MessageQueue] [pallet_multisig, Multisig] [pallet_session, SessionBench::] [pallet_utility, Utility] @@ -455,6 +456,7 @@ mod benches { // Polkadot [polkadot_runtime_common::identity_migrator, IdentityMigrator] // Cumulus + [cumulus_pallet_parachain_system, ParachainSystem] [cumulus_pallet_xcmp_queue, XcmpQueue] [pallet_collator_selection, CollatorSelection] // XCM diff --git a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs index c0bcd787962..ef4f466d484 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -134,7 +134,7 @@ pub type ForeignFungiblesTransactor = FungiblesAdapter< ForeignAssets, // Use this currency when it is a fungible asset matching the given location or name: ForeignAssetsConvertedConcreteId, - // Convert an XCM MultiLocation into a local account id: + // Convert an XCM Location into a local account id: LocationToAccountId, // Our chain's account ID type (we can't get away without mentioning it explicitly): AccountId, diff --git a/polkadot/runtime/common/src/impls.rs b/polkadot/runtime/common/src/impls.rs index 1ddad37831b..4ba85a3c835 100644 --- a/polkadot/runtime/common/src/impls.rs +++ b/polkadot/runtime/common/src/impls.rs @@ -107,7 +107,7 @@ pub fn era_payout( )] pub enum VersionedLocatableAsset { #[codec(index = 3)] - V3 { location: xcm::v3::MultiLocation, asset_id: xcm::v3::AssetId }, + V3 { location: xcm::v3::Location, asset_id: xcm::v3::AssetId }, #[codec(index = 4)] V4 { location: xcm::v4::Location, asset_id: xcm::v4::AssetId }, } @@ -140,7 +140,7 @@ impl TryConvert<&VersionedLocation, xcm::latest::Location> for VersionedLocation ) -> Result { let latest = match location.clone() { VersionedLocation::V2(l) => { - let v3: xcm::v3::MultiLocation = l.try_into().map_err(|_| location)?; + let v3: xcm::v3::Location = l.try_into().map_err(|_| location)?; v3.try_into().map_err(|_| location)? }, VersionedLocation::V3(l) => l.try_into().map_err(|_| location)?, @@ -191,11 +191,11 @@ pub mod benchmarks { { fn create_asset_kind(seed: u32) -> VersionedLocatableAsset { VersionedLocatableAsset::V3 { - location: xcm::v3::MultiLocation::new( + location: xcm::v3::Location::new( Parents::get(), [xcm::v3::Junction::Parachain(ParaId::get())], ), - asset_id: xcm::v3::MultiLocation::new( + asset_id: xcm::v3::Location::new( 0, [ xcm::v3::Junction::PalletInstance(seed.try_into().unwrap()), diff --git a/polkadot/xcm/src/lib.rs b/polkadot/xcm/src/lib.rs index 561d5175b41..c90ad25c0d8 100644 --- a/polkadot/xcm/src/lib.rs +++ b/polkadot/xcm/src/lib.rs @@ -494,7 +494,7 @@ pub trait WrapVersion { } /// Check and return the `Version` that should be used for the `Xcm` datum for the destination -/// `MultiLocation`, which will interpret it. +/// `Location`, which will interpret it. pub trait GetVersion { fn get_version_for(dest: &latest::Location) -> Option; } diff --git a/substrate/frame/asset-conversion/src/lib.rs b/substrate/frame/asset-conversion/src/lib.rs index f0695678fbd..f13d40d3e7e 100644 --- a/substrate/frame/asset-conversion/src/lib.rs +++ b/substrate/frame/asset-conversion/src/lib.rs @@ -40,7 +40,7 @@ //! non-native asset 1, you would pass in a path of `[DOT, 1]` or `[1, DOT]`. If you want to swap //! from non-native asset 1 to non-native asset 2, you would pass in a path of `[1, DOT, 2]`. //! -//! (For an example of configuring this pallet to use `MultiLocation` as an asset id, see the +//! (For an example of configuring this pallet to use `Location` as an asset id, see the //! cumulus repo). //! //! Here is an example `state_call` that asks for a quote of a pool of native versus asset 1: -- GitLab From d50cf1402d55c1a031283645ef16cc4a74163959 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 23:07:48 +0000 Subject: [PATCH 098/133] Bump the known_good_semver group with 5 updates (#3397) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the known_good_semver group with 5 updates: | Package | From | To | | --- | --- | --- | | [serde](https://github.com/serde-rs/serde) | `1.0.196` | `1.0.197` | | [serde_json](https://github.com/serde-rs/json) | `1.0.113` | `1.0.114` | | [syn](https://github.com/dtolnay/syn) | `2.0.49` | `2.0.50` | | [serde_yaml](https://github.com/dtolnay/serde-yaml) | `0.9.31` | `0.9.32` | | [serde_derive](https://github.com/serde-rs/serde) | `1.0.196` | `1.0.197` | Updates `serde` from 1.0.196 to 1.0.197
Release notes

Sourced from serde's releases.

v1.0.197

  • Fix unused_imports warnings when compiled by rustc 1.78
  • Optimize code size of some Display impls (#2697, thanks @​nyurik)
Commits
  • 5fa711d Release 1.0.197
  • f5d8ae4 Resolve prelude redundant import warnings
  • 1d54973 Merge pull request #2697 from nyurik/format-str
  • b8fafef A few minor write_str optimizations and inlining
  • c42ebb8 Update ui test suite to nightly-2024-02-12
  • 9e68062 Ignore incompatible_msrv clippy lint for conditionally compiled code
  • 846f865 Ignore dead_code warnings in test
  • See full diff in compare view

Updates `serde_json` from 1.0.113 to 1.0.114
Release notes

Sourced from serde_json's releases.

v1.0.114

  • Fix unused_imports warnings when compiled by rustc 1.78
Commits
  • e1b3a6d Release 1.0.114
  • 6fb7026 Work around prelude redundant import warnings
  • 34a04c5 Ignore incompatible_msrv clippy false positives in test
  • ca05f69 Remove unused Float::is_sign_negative trait method
  • See full diff in compare view

Updates `syn` from 2.0.49 to 2.0.50
Release notes

Sourced from syn's releases.

2.0.50

  • Fix unused_imports warnings when compiled by rustc 1.78
Commits
  • 0dae015 Release 2.0.50
  • 2b493c3 Update location of Pat variant aliases
  • 07a435d Fix doc links now that crate::* is no longer imported
  • 713f932 Eliminate glob imports
  • 6eb82a9 Eliminate glob imports from codegen
  • 896a730 Eliminate glob imports from examples
  • 866c80c Update test suite to nightly-2024-02-18
  • d4b499e Update test suite to nightly-2024-02-17
  • 38956e6 Update test suite to nightly-2024-02-16
  • See full diff in compare view

Updates `serde_yaml` from 0.9.31 to 0.9.32
Release notes

Sourced from serde_yaml's releases.

0.9.32

  • Fix unused_imports warnings when compiled by rustc 1.78
Commits

Updates `serde_derive` from 1.0.196 to 1.0.197
Release notes

Sourced from serde_derive's releases.

v1.0.197

  • Fix unused_imports warnings when compiled by rustc 1.78
  • Optimize code size of some Display impls (#2697, thanks @​nyurik)
Commits
  • 5fa711d Release 1.0.197
  • f5d8ae4 Resolve prelude redundant import warnings
  • 1d54973 Merge pull request #2697 from nyurik/format-str
  • b8fafef A few minor write_str optimizations and inlining
  • c42ebb8 Update ui test suite to nightly-2024-02-12
  • 9e68062 Ignore incompatible_msrv clippy lint for conditionally compiled code
  • 846f865 Ignore dead_code warnings in test
  • See full diff in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 140 ++++++++++++++++++++++++++--------------------------- Cargo.toml | 6 +-- 2 files changed, 73 insertions(+), 73 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f8adf0e5f4..9f2a1a06891 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -191,7 +191,7 @@ checksum = "c0391754c09fab4eae3404d19d0d297aa1c670c1775ab51d8a5312afeca23157" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -206,7 +206,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", "syn-solidity", "tiny-keccak", ] @@ -333,7 +333,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -1217,7 +1217,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -1234,7 +1234,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -1416,7 +1416,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -2669,7 +2669,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -3900,7 +3900,7 @@ dependencies = [ "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -4406,7 +4406,7 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -4446,7 +4446,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -4463,7 +4463,7 @@ checksum = "50c49547d73ba8dcfd4ad7325d64c6d5391ff4224d498fc39a6f3f49825a530d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -4671,7 +4671,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -4732,7 +4732,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.49", + "syn 2.0.50", "termcolor", "toml 0.8.8", "walkdir", @@ -4957,7 +4957,7 @@ checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -4968,7 +4968,7 @@ checksum = "c2ad8cef1d801a4686bfd8919f0b30eac4c8e48968c437a6405ded4fb5272d2b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -5158,7 +5158,7 @@ dependencies = [ "fs-err", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -5551,7 +5551,7 @@ dependencies = [ "quote", "scale-info", "sp-arithmetic", - "syn 2.0.49", + "syn 2.0.50", "trybuild", ] @@ -5706,7 +5706,7 @@ dependencies = [ "quote", "regex", "sp-crypto-hashing", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -5717,7 +5717,7 @@ dependencies = [ "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -5726,7 +5726,7 @@ version = "11.0.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -5959,7 +5959,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -7900,7 +7900,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -7914,7 +7914,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -7925,7 +7925,7 @@ checksum = "9ea73aa640dc01d62a590d48c0c3521ed739d53b27f919b25c3551e233481654" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -7936,7 +7936,7 @@ checksum = "ef9d79ae96aaba821963320eb2b6e34d17df1e5a83d8a1985c29cc5be59577b3" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -9693,7 +9693,7 @@ version = "18.0.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -10868,7 +10868,7 @@ dependencies = [ "proc-macro2", "quote", "sp-runtime", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -11948,7 +11948,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -11989,7 +11989,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -13829,7 +13829,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6380dbe1fb03ecc74ad55d841cfc75480222d153ba69ddcb00977866cbdabdb8" dependencies = [ "polkavm-derive-impl 0.5.0", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -13850,7 +13850,7 @@ dependencies = [ "polkavm-common 0.5.0", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -13862,7 +13862,7 @@ dependencies = [ "polkavm-common 0.8.0", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -13872,7 +13872,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15e85319a0d5129dc9f021c62607e0804f5fb777a05cdda44d750ac0732def66" dependencies = [ "polkavm-derive-impl 0.8.0", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -14077,7 +14077,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62" dependencies = [ "proc-macro2", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -14168,7 +14168,7 @@ checksum = "9b698b0b09d40e9b7c1a47b132d66a8b54bcd20583d9b6d06e4535e383b4405c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -14240,7 +14240,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -14340,7 +14340,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -14683,7 +14683,7 @@ checksum = "7f7473c2cfcf90008193dd0e3e16599455cb601a9fce322b5bb55de799664925" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -15593,7 +15593,7 @@ dependencies = [ "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -16863,7 +16863,7 @@ dependencies = [ "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -17247,9 +17247,9 @@ checksum = "f97841a747eef040fcd2e7b3b9a220a7205926e60488e673d9e4926d27772ce5" [[package]] name = "serde" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] @@ -17274,13 +17274,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -17305,9 +17305,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.113" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ "itoa", "ryu", @@ -17337,9 +17337,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.31" +version = "0.9.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adf8a49373e98a4c5f0ceb5d05aa7c648d75f63774981ed95b7c7443bbd50c6e" +checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f" dependencies = [ "indexmap 2.2.3", "itoa", @@ -17370,7 +17370,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -18209,7 +18209,7 @@ dependencies = [ "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -18602,7 +18602,7 @@ version = "0.0.0" dependencies = [ "quote", "sp-crypto-hashing", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -18620,7 +18620,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf5 dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -18629,7 +18629,7 @@ version = "14.0.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -18903,7 +18903,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -18915,7 +18915,7 @@ dependencies = [ "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -19187,7 +19187,7 @@ dependencies = [ "proc-macro2", "quote", "sp-version", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -19632,7 +19632,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -20030,9 +20030,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.49" +version = "2.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496" +checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" dependencies = [ "proc-macro2", "quote", @@ -20048,7 +20048,7 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -20313,7 +20313,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -20474,7 +20474,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -20681,7 +20681,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -20723,7 +20723,7 @@ dependencies = [ "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -21288,7 +21288,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", "wasm-bindgen-shared", ] @@ -21322,7 +21322,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -22337,7 +22337,7 @@ dependencies = [ "proc-macro2", "quote", "staging-xcm", - "syn 2.0.49", + "syn 2.0.50", "trybuild", ] @@ -22459,7 +22459,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -22479,7 +22479,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 7e666755922..774ce1b52a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -538,12 +538,12 @@ polkavm-linker = "0.8.2" polkavm-derive = "0.8.0" log = { version = "0.4.20", default-features = false } quote = { version = "1.0.33" } -serde = { version = "1.0.196", default-features = false } +serde = { version = "1.0.197", default-features = false } serde-big-array = { version = "0.3.2" } serde_derive = { version = "1.0.117" } -serde_json = { version = "1.0.113", default-features = false } +serde_json = { version = "1.0.114", default-features = false } serde_yaml = { version = "0.9" } -syn = { version = "2.0.49" } +syn = { version = "2.0.50" } thiserror = { version = "1.0.48" } [profile.release] -- GitLab From 60563b893a448c34474c6ab3de8141b836e17f1b Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 21 Feb 2024 00:32:56 +0100 Subject: [PATCH 099/133] nit: add prdoc for 3370 (#3401) Did not realize that https://github.com/paritytech/polkadot-sdk/pull/3370 was indeed breaking. cc @muraca --------- Signed-off-by: Oliver Tale-Yazdi --- prdoc/pr_3370.prdoc | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 prdoc/pr_3370.prdoc diff --git a/prdoc/pr_3370.prdoc b/prdoc/pr_3370.prdoc new file mode 100644 index 00000000000..f40d3821b9f --- /dev/null +++ b/prdoc/pr_3370.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Remove `key` getter from pallet-sudo" + +doc: + - audience: Runtime Dev + description: | + Removed the `key` getter function from the sudo pallet. There is no replacement for getting + the key currently. + +crates: + - name: pallet-sudo -- GitLab From 579ef32ddd4ec55314d6f2ee427afbd300541e27 Mon Sep 17 00:00:00 2001 From: girazoki Date: Wed, 21 Feb 2024 00:34:09 +0100 Subject: [PATCH 100/133] remove bound on accountID in AssetFeeAsExistentialDepositMultiplier (#3303) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I dont think this bound is required by anything else and it allows now to use this struct with runtimes that have 20 bytes accounts --------- Co-authored-by: Bastian Köcher --- cumulus/parachains/common/src/xcm_config.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/cumulus/parachains/common/src/xcm_config.rs b/cumulus/parachains/common/src/xcm_config.rs index 15b090923d5..a9756af7aed 100644 --- a/cumulus/parachains/common/src/xcm_config.rs +++ b/cumulus/parachains/common/src/xcm_config.rs @@ -45,8 +45,6 @@ where >::AssetId, >::Balance, >, - AccountIdOf: - From + Into, { fn charge_weight_in_fungibles( asset_id: as Inspect< -- GitLab From f3a6b6dceabccbc4b92471cf12340b8c390ff14a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Wed, 21 Feb 2024 18:14:48 +0800 Subject: [PATCH 101/133] contracts: Fix double charge of gas for host functions (#3361) This PR is fixing a bug in the sync mechanism between wasmi and pallet-contracts. This bug leads to essentially double charging all the gas that was used during the execution of the host function. When the `call` host function is used for recursion this will lead to a quadratic amount of gas consumption with regard to the nesting depth.We also took the chance to refactor the code in question and improve the rust docs. The bug was caused by not updating `GasMeter::executor_consumed` (previously `engine_consumed`) when leaving the host function. This lead to the value being stale (too low) when entering another host function. --------- Co-authored-by: PG Herveou --- prdoc/pr_3361.prdoc | 10 +++ .../contracts/fixtures/contracts/recurse.rs | 53 ++++++++++++ .../frame/contracts/proc-macro/src/lib.rs | 37 ++++---- substrate/frame/contracts/src/gas.rs | 86 +++++++++++++------ substrate/frame/contracts/src/tests.rs | 54 +++++++++++- substrate/frame/contracts/src/wasm/mod.rs | 2 +- 6 files changed, 192 insertions(+), 50 deletions(-) create mode 100644 prdoc/pr_3361.prdoc create mode 100644 substrate/frame/contracts/fixtures/contracts/recurse.rs diff --git a/prdoc/pr_3361.prdoc b/prdoc/pr_3361.prdoc new file mode 100644 index 00000000000..65baa9e94a0 --- /dev/null +++ b/prdoc/pr_3361.prdoc @@ -0,0 +1,10 @@ +title: Fix double charge of host function weight + +doc: + - audience: Runtime Dev + description: | + Fixed a double charge which can lead to quadratic gas consumption of + the `call` and `instantiate` host functions. + +crates: + - name: pallet-contracts diff --git a/substrate/frame/contracts/fixtures/contracts/recurse.rs b/substrate/frame/contracts/fixtures/contracts/recurse.rs new file mode 100644 index 00000000000..b1ded608c2f --- /dev/null +++ b/substrate/frame/contracts/fixtures/contracts/recurse.rs @@ -0,0 +1,53 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This fixture calls itself as many times as passed as argument. + +#![no_std] +#![no_main] + +use common::{input, output}; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(calls_left: u32, ); + + // own address + output!(addr, [0u8; 32], api::address,); + + if calls_left == 0 { + return + } + + api::call_v2( + uapi::CallFlags::ALLOW_REENTRY, + addr, + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much deposit_limit to devote for the execution. 0 = all. + None, // No deposit limit. + &0u64.to_le_bytes(), // Value transferred to the contract. + &(calls_left - 1).to_le_bytes(), + None, + ) + .unwrap(); +} diff --git a/substrate/frame/contracts/proc-macro/src/lib.rs b/substrate/frame/contracts/proc-macro/src/lib.rs index 403db15ac2c..de961776c32 100644 --- a/substrate/frame/contracts/proc-macro/src/lib.rs +++ b/substrate/frame/contracts/proc-macro/src/lib.rs @@ -638,37 +638,34 @@ fn expand_functions(def: &EnvDef, expand_blocks: bool, host_state: TokenStream2) }; let sync_gas_before = if expand_blocks { quote! { - // Gas left in the gas meter right before switching to engine execution. - let __gas_before__ = { - let engine_consumed_total = + // Write gas from wasmi into pallet-contracts before entering the host function. + let __gas_left_before__ = { + let executor_total = __caller__.fuel_consumed().expect("Fuel metering is enabled; qed"); - let gas_meter = __caller__.data_mut().ext().gas_meter_mut(); - gas_meter - .charge_fuel(engine_consumed_total) + __caller__ + .data_mut() + .ext() + .gas_meter_mut() + .sync_from_executor(executor_total) .map_err(TrapReason::from) .map_err(#into_host)? - .ref_time() }; } } else { quote! { } }; - // Gas left in the gas meter right after returning from engine execution. + // Write gas from pallet-contracts into wasmi after leaving the host function. let sync_gas_after = if expand_blocks { quote! { - let mut gas_after = __caller__.data_mut().ext().gas_meter().gas_left().ref_time(); - let mut host_consumed = __gas_before__.saturating_sub(gas_after); - // Possible undercharge of at max 1 fuel here, if host consumed less than `instruction_weights.base` - // Not a problem though, as soon as host accounts its spent gas properly. - let fuel_consumed = host_consumed - .checked_div(__caller__.data_mut().ext().schedule().instruction_weights.base as u64) - .ok_or(Error::::InvalidSchedule) - .map_err(TrapReason::from) - .map_err(#into_host)?; + let fuel_consumed = __caller__ + .data_mut() + .ext() + .gas_meter_mut() + .sync_to_executor(__gas_left_before__) + .map_err(TrapReason::from)?; __caller__ - .consume_fuel(fuel_consumed) - .map_err(|_| TrapReason::from(Error::::OutOfGas)) - .map_err(#into_host)?; + .consume_fuel(fuel_consumed.into()) + .map_err(|_| TrapReason::from(Error::::OutOfGas))?; } } else { quote! { } diff --git a/substrate/frame/contracts/src/gas.rs b/substrate/frame/contracts/src/gas.rs index 9271b615d00..b9d91f38f16 100644 --- a/substrate/frame/contracts/src/gas.rs +++ b/substrate/frame/contracts/src/gas.rs @@ -23,7 +23,7 @@ use frame_support::{ DefaultNoBound, }; use sp_core::Get; -use sp_runtime::{traits::Zero, DispatchError}; +use sp_runtime::{traits::Zero, DispatchError, Saturating}; #[cfg(test)] use std::{any::Any, fmt::Debug}; @@ -37,6 +37,24 @@ impl ChargedAmount { } } +/// Used to capture the gas left before entering a host function. +/// +/// Has to be consumed in order to sync back the gas after leaving the host function. +#[must_use] +pub struct RefTimeLeft(u64); + +/// Resource that needs to be synced to the executor. +/// +/// Wrapped to make sure that the resource will be synced back the the executor. +#[must_use] +pub struct Syncable(u64); + +impl From for u64 { + fn from(from: Syncable) -> u64 { + from.0 + } +} + #[cfg(not(test))] pub trait TestAuxiliaries {} #[cfg(not(test))] @@ -84,8 +102,13 @@ pub struct GasMeter { gas_left: Weight, /// Due to `adjust_gas` and `nested` the `gas_left` can temporarily dip below its final value. gas_left_lowest: Weight, - /// Amount of fuel consumed by the engine from the last host function call. - engine_consumed: u64, + /// The amount of resources that was consumed by the execution engine. + /// + /// This should be equivalent to `self.gas_consumed().ref_time()` but expressed in whatever + /// unit the execution engine uses to track resource consumption. We have to track it + /// separately in order to avoid the loss of precision that happens when converting from + /// ref_time to the execution engine unit. + executor_consumed: u64, _phantom: PhantomData, #[cfg(test)] tokens: Vec, @@ -97,7 +120,7 @@ impl GasMeter { gas_limit, gas_left: gas_limit, gas_left_lowest: gas_limit, - engine_consumed: Default::default(), + executor_consumed: 0, _phantom: PhantomData, #[cfg(test)] tokens: Vec::new(), @@ -172,32 +195,41 @@ impl GasMeter { self.gas_left = self.gas_left.saturating_add(adjustment).min(self.gas_limit); } - /// This method is used for gas syncs with the engine. + /// Hand over the gas metering responsibility from the executor to this meter. /// - /// Updates internal `engine_comsumed` tracker of engine fuel consumption. + /// Needs to be called when entering a host function to update this meter with the + /// gas that was tracked by the executor. It tracks the latest seen total value + /// in order to compute the delta that needs to be charged. + pub fn sync_from_executor( + &mut self, + executor_total: u64, + ) -> Result { + let chargable_reftime = executor_total + .saturating_sub(self.executor_consumed) + .saturating_mul(u64::from(T::Schedule::get().instruction_weights.base)); + self.executor_consumed = executor_total; + self.gas_left + .checked_reduce(Weight::from_parts(chargable_reftime, 0)) + .ok_or_else(|| Error::::OutOfGas)?; + Ok(RefTimeLeft(self.gas_left.ref_time())) + } + + /// Hand over the gas metering responsibility from this meter to the executor. /// - /// Charges self with the `ref_time` Weight corresponding to wasmi fuel consumed on the engine - /// side since last sync. Passed value is scaled by multiplying it by the weight of a basic - /// operation, as such an operation in wasmi engine costs 1. + /// Needs to be called when leaving a host function in order to calculate how much + /// gas needs to be charged from the **executor**. It updates the last seen executor + /// total value so that it is correct when `sync_from_executor` is called the next time. /// - /// Returns the updated `gas_left` `Weight` value from the meter. - /// Normally this would never fail, as engine should fail first when out of gas. - pub fn charge_fuel(&mut self, wasmi_fuel_total: u64) -> Result { - // Take the part consumed since the last update. - let wasmi_fuel = wasmi_fuel_total.saturating_sub(self.engine_consumed); - if !wasmi_fuel.is_zero() { - self.engine_consumed = wasmi_fuel_total; - let reftime_consumed = - wasmi_fuel.saturating_mul(T::Schedule::get().instruction_weights.base as u64); - let ref_time_left = self - .gas_left - .ref_time() - .checked_sub(reftime_consumed) - .ok_or_else(|| Error::::OutOfGas)?; - - *(self.gas_left.ref_time_mut()) = ref_time_left; - } - Ok(self.gas_left) + /// It is important that this does **not** actually sync with the executor. That has + /// to be done by the caller. + pub fn sync_to_executor(&mut self, before: RefTimeLeft) -> Result { + let chargable_executor_resource = before + .0 + .saturating_sub(self.gas_left().ref_time()) + .checked_div(u64::from(T::Schedule::get().instruction_weights.base)) + .ok_or(Error::::InvalidSchedule)?; + self.executor_consumed.saturating_accrue(chargable_executor_resource); + Ok(Syncable(chargable_executor_resource)) } /// Returns the amount of gas that is required to run the same call. diff --git a/substrate/frame/contracts/src/tests.rs b/substrate/frame/contracts/src/tests.rs index cd4e43ed4c2..b39fc79b62a 100644 --- a/substrate/frame/contracts/src/tests.rs +++ b/substrate/frame/contracts/src/tests.rs @@ -35,8 +35,8 @@ use crate::{ tests::test_utils::{get_contract, get_contract_checked}, wasm::{Determinism, ReturnErrorCode as RuntimeReturnCode}, weights::WeightInfo, - BalanceOf, Code, CodeHash, CodeInfoOf, CollectEvents, Config, ContractInfo, ContractInfoOf, - DebugInfo, DefaultAddressGenerator, DeletionQueueCounter, Error, HoldReason, + Array, BalanceOf, Code, CodeHash, CodeInfoOf, CollectEvents, Config, ContractInfo, + ContractInfoOf, DebugInfo, DefaultAddressGenerator, DeletionQueueCounter, Error, HoldReason, MigrationInProgress, Origin, Pallet, PristineCode, Schedule, }; use assert_matches::assert_matches; @@ -5977,3 +5977,53 @@ fn balance_api_returns_free_balance() { ); }); } + +#[test] +fn gas_consumed_is_linear_for_nested_calls() { + let (code, _code_hash) = compile_module::("recurse").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let addr = Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Upload(code), + vec![], + vec![], + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result + .unwrap() + .account_id; + + let max_call_depth = ::CallStack::size() as u32; + let [gas_0, gas_1, gas_2, gas_max] = { + [0u32, 1u32, 2u32, max_call_depth] + .iter() + .map(|i| { + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + i.encode(), + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, + ); + assert_ok!(result.result); + result.gas_consumed + }) + .collect::>() + .try_into() + .unwrap() + }; + + let gas_per_recursion = gas_2.checked_sub(&gas_1).unwrap(); + assert_eq!(gas_max, gas_0 + gas_per_recursion * max_call_depth as u64); + }); +} diff --git a/substrate/frame/contracts/src/wasm/mod.rs b/substrate/frame/contracts/src/wasm/mod.rs index 5386f4d0ffd..c96c2856509 100644 --- a/substrate/frame/contracts/src/wasm/mod.rs +++ b/substrate/frame/contracts/src/wasm/mod.rs @@ -386,7 +386,7 @@ impl Executable for WasmBlob { let engine_consumed_total = store.fuel_consumed().expect("Fuel metering is enabled; qed"); let gas_meter = store.data_mut().ext().gas_meter_mut(); - gas_meter.charge_fuel(engine_consumed_total)?; + let _ = gas_meter.sync_from_executor(engine_consumed_total)?; store.into_data().to_execution_result(result) }; -- GitLab From cbeccad65fa3e6f9fb795fe996d522365a72c69b Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Wed, 21 Feb 2024 12:24:14 +0200 Subject: [PATCH 102/133] sc-consensus-beefy: reduce log levels (#3418) fixes https://github.com/paritytech/polkadot-sdk/issues/3407 --- .../client/consensus/beefy/src/aux_schema.rs | 4 ++-- substrate/client/consensus/beefy/src/lib.rs | 8 +++++-- .../client/consensus/beefy/src/worker.rs | 21 ++++++++++--------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/substrate/client/consensus/beefy/src/aux_schema.rs b/substrate/client/consensus/beefy/src/aux_schema.rs index 944a00f8372..534f668ae69 100644 --- a/substrate/client/consensus/beefy/src/aux_schema.rs +++ b/substrate/client/consensus/beefy/src/aux_schema.rs @@ -20,7 +20,7 @@ use crate::{error::Error, worker::PersistedState, LOG_TARGET}; use codec::{Decode, Encode}; -use log::{info, trace}; +use log::{debug, trace}; use sc_client_api::{backend::AuxStore, Backend}; use sp_runtime::traits::Block as BlockT; @@ -30,7 +30,7 @@ const WORKER_STATE_KEY: &[u8] = b"beefy_voter_state"; const CURRENT_VERSION: u32 = 4; pub(crate) fn write_current_version(backend: &BE) -> Result<(), Error> { - info!(target: LOG_TARGET, "🥩 write aux schema version {:?}", CURRENT_VERSION); + debug!(target: LOG_TARGET, "🥩 write aux schema version {:?}", CURRENT_VERSION); AuxStore::insert_aux(backend, &[(VERSION_KEY, CURRENT_VERSION.encode().as_slice())], &[]) .map_err(|e| Error::Backend(e.to_string())) } diff --git a/substrate/client/consensus/beefy/src/lib.rs b/substrate/client/consensus/beefy/src/lib.rs index 1f10d8099d8..11a105561e4 100644 --- a/substrate/client/consensus/beefy/src/lib.rs +++ b/substrate/client/consensus/beefy/src/lib.rs @@ -460,10 +460,14 @@ where R::Api: BeefyApi, { let blockchain = backend.blockchain(); - // Walk up the chain looking for the validator set active at 'at_header'. Process both state and // header digests. - debug!(target: LOG_TARGET, "🥩 Trying to find validator set active at header: {:?}", at_header); + debug!( + target: LOG_TARGET, + "🥩 Trying to find validator set active at header(number {:?}, hash {:?})", + at_header.number(), + at_header.hash() + ); let mut header = at_header.clone(); loop { debug!(target: LOG_TARGET, "🥩 Looking for auth set change at block number: {:?}", *header.number()); diff --git a/substrate/client/consensus/beefy/src/worker.rs b/substrate/client/consensus/beefy/src/worker.rs index 19a24b9bc1a..d7a229003cc 100644 --- a/substrate/client/consensus/beefy/src/worker.rs +++ b/substrate/client/consensus/beefy/src/worker.rs @@ -387,7 +387,7 @@ where .flatten() .map(|justifs| justifs.get(BEEFY_ENGINE_ID).is_some()) { - info!( + debug!( target: LOG_TARGET, "🥩 Initialize BEEFY voter at last BEEFY finalized block: {:?}.", *header.number() @@ -439,7 +439,7 @@ where } if let Some(active) = find_authorities_change::(&header) { - info!( + debug!( target: LOG_TARGET, "🥩 Marking block {:?} as BEEFY Mandatory.", *header.number() @@ -471,7 +471,7 @@ where state.set_best_grandpa(best_grandpa.clone()); // Overwrite persisted data with newly provided `min_block_delta`. state.set_min_block_delta(min_block_delta); - info!(target: LOG_TARGET, "🥩 Loading BEEFY voter state from db: {:?}.", state); + debug!(target: LOG_TARGET, "🥩 Loading BEEFY voter state from db: {:?}.", state); // Make sure that all the headers that we need have been synced. let mut new_sessions = vec![]; @@ -489,7 +489,7 @@ where // Make sure we didn't miss any sessions during node restart. for (validator_set, new_session_start) in new_sessions.drain(..).rev() { - info!( + debug!( target: LOG_TARGET, "🥩 Handling missed BEEFY session after node restart: {:?}.", new_session_start @@ -630,13 +630,14 @@ where &mut self, notification: &FinalityNotification, ) -> Result<(), Error> { + let header = ¬ification.header; debug!( target: LOG_TARGET, - "🥩 Finality notification: header {:?} tree_route {:?}", - notification.header, + "🥩 Finality notification: header(number {:?}, hash {:?}) tree_route {:?}", + header.number(), + header.hash(), notification.tree_route, ); - let header = ¬ification.header; self.base .runtime @@ -757,7 +758,7 @@ where match rounds.add_vote(vote) { VoteImportResult::RoundConcluded(signed_commitment) => { let finality_proof = VersionedFinalityProof::V1(signed_commitment); - info!( + debug!( target: LOG_TARGET, "🥩 Round #{} concluded, finality_proof: {:?}.", block_number, finality_proof ); @@ -1165,7 +1166,7 @@ where return Ok(()) } else if let Some(local_id) = self.base.key_store.authority_id(validators) { if offender_id == local_id { - debug!(target: LOG_TARGET, "🥩 Skip equivocation report for own equivocation"); + warn!(target: LOG_TARGET, "🥩 Skip equivocation report for own equivocation"); return Ok(()) } } @@ -1234,7 +1235,7 @@ where // if the mandatory block (session_start) does not have a beefy justification yet, // we vote on it let target = if best_beefy < session_start { - debug!(target: LOG_TARGET, "🥩 vote target - mandatory block: #{:?}", session_start,); + debug!(target: LOG_TARGET, "🥩 vote target - mandatory block: #{:?}", session_start); session_start } else { let diff = best_grandpa.saturating_sub(best_beefy) + 1u32.into(); -- GitLab From bf7c49b33cee9a00d7e94d0f64b1fdb4bf3f9241 Mon Sep 17 00:00:00 2001 From: Egor_P Date: Wed, 21 Feb 2024 20:48:18 +0700 Subject: [PATCH 103/133] Fix failing release notifications GHA (#3416) This PR should fix the issue with the failing GHA which sends notifications to the different matrix channels, when the new release is published --- .../workflows/release-99_notif-published.yml | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/.github/workflows/release-99_notif-published.yml b/.github/workflows/release-99_notif-published.yml index b35120ca4e1..397795fafa4 100644 --- a/.github/workflows/release-99_notif-published.yml +++ b/.github/workflows/release-99_notif-published.yml @@ -8,13 +8,11 @@ on: jobs: ping_matrix: runs-on: ubuntu-latest + environment: release strategy: matrix: channel: # Internal - - name: 'RelEng: Cumulus Release Coordination' - room: '!NAEMyPAHWOiOQHsvus:parity.io' - pre-releases: true - name: "RelEng: Polkadot Release Coordination" room: '!cqAmzdIcbOFwrdrubV:parity.io' pre-release: true @@ -31,18 +29,9 @@ jobs: pre-release: true # Public - # - name: '#KusamaValidatorLounge:polkadot.builders' - # room: '!LhjZccBOqFNYKLdmbb:polkadot.builders' - # pre-releases: false - # - name: '#kusama-announcements:matrix.parity.io' - # room: '!FMwxpQnYhRCNDRsYGI:matrix.parity.io' - # pre-release: false - # - name: '#polkadotvalidatorlounge:web3.foundation' - # room: '!NZrbtteFeqYKCUGQtr:matrix.parity.io' - # pre-release: false - # - name: '#polkadot-announcements:matrix.parity.io' - # room: '!UqHPWiCBGZWxrmYBkF:matrix.parity.io' - # pre-release: false + - name: '#polkadotvalidatorlounge:web3.foundation' + room: '!NZrbtteFeqYKCUGQtr:matrix.parity.io' + pre-releases: false steps: - name: Matrix notification to ${{ matrix.channel.name }} -- GitLab From 5a06771eccb27e58f09189ea34ce20d947b1db4f Mon Sep 17 00:00:00 2001 From: Clara van Staden Date: Wed, 21 Feb 2024 16:48:40 +0200 Subject: [PATCH 104/133] Snowbridge - Test pallet order (#3381) - Adds a test to check the correct digest for Snowbridge outbound messages. For the correct digest to be in the block, the the MessageQueue pallet should be configured after the EthereumOutbound queue pallet. The added test fails if the EthereumOutbound is configured after the MessageQueue pallet. - Adds a helper method `run_to_block_with_finalize` to simulate the block finalizing. The existing `run_to_block` method does not finalize and so it cannot successfully test this condition. Closes: https://github.com/paritytech/polkadot-sdk/issues/3208 --------- Co-authored-by: claravanstaden --- Cargo.lock | 3 + bridges/snowbridge/README.md | 24 ++-- bridges/snowbridge/pallets/system/src/lib.rs | 4 +- .../snowbridge/runtime/test-common/src/lib.rs | 112 +++++++++++++++-- .../snowbridge/scripts/contribute-upstream.sh | 80 ++++++++++++ .../scripts/verify-pallets-build.sh | 116 ------------------ .../runtimes/assets/test-utils/Cargo.toml | 2 + .../assets/test-utils/src/test_cases.rs | 18 ++- .../test-utils/src/test_cases_over_bridge.rs | 9 +- .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 2 +- .../bridge-hub-rococo/tests/snowbridge.rs | 32 ++++- .../bridge-hubs/test-utils/Cargo.toml | 2 + .../test-utils/src/test_cases/helpers.rs | 4 +- .../parachains/runtimes/test-utils/Cargo.toml | 2 + .../parachains/runtimes/test-utils/src/lib.rs | 71 ++++++++++- .../runtimes/test-utils/src/test_cases.rs | 6 +- 16 files changed, 332 insertions(+), 155 deletions(-) create mode 100755 bridges/snowbridge/scripts/contribute-upstream.sh delete mode 100755 bridges/snowbridge/scripts/verify-pallets-build.sh diff --git a/Cargo.lock b/Cargo.lock index 9f2a1a06891..93ee9cc99c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1054,6 +1054,7 @@ dependencies = [ "pallet-balances", "pallet-collator-selection", "pallet-session", + "pallet-timestamp", "pallet-xcm", "pallet-xcm-bridge-hub-router", "parachains-common", @@ -2099,6 +2100,7 @@ dependencies = [ "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", + "pallet-timestamp", "pallet-utility", "parachains-common", "parachains-runtimes-test-utils", @@ -11430,6 +11432,7 @@ dependencies = [ "pallet-balances", "pallet-collator-selection", "pallet-session", + "pallet-timestamp", "pallet-xcm", "parity-scale-codec", "polkadot-parachain-primitives", diff --git a/bridges/snowbridge/README.md b/bridges/snowbridge/README.md index 49b9c2eaf55..6561df40112 100644 --- a/bridges/snowbridge/README.md +++ b/bridges/snowbridge/README.md @@ -1,32 +1,40 @@ -# Snowbridge -[![codecov](https://codecov.io/gh/Snowfork/snowbridge/branch/main/graph/badge.svg?token=9hvgSws4rN)](https://codecov.io/gh/Snowfork/snowbridge) +# Snowbridge · +[![codecov](https://codecov.io/gh/Snowfork/polkadot-sdk/branch/snowbridge/graph/badge.svg?token=9hvgSws4rN)](https://codecov.io/gh/Snowfork/polkadot-sdk) ![GitHub](https://img.shields.io/github/license/Snowfork/snowbridge) Snowbridge is a trustless bridge between Polkadot and Ethereum. For documentation, visit https://docs.snowbridge.network. ## Components +The Snowbridge project lives in two repositories: + +- [Snowfork/Polkadot-sdk](https://github.com/Snowfork/polkadot-sdk): The Snowbridge parachain and pallets live in +a fork of the Polkadot SDK. Changes are eventually contributed back to +[paritytech/Polkadot-sdk](https://github.com/paritytech/polkadot-sdk) +- [Snowfork/snowbridge](https://github.com/Snowfork/snowbridge): The rest of the Snowbridge components, like contracts, +off-chain relayer, end-to-end tests and test-net setup code. + ### Parachain -Polkadot parachain and our pallets. See [parachain/README.md](https://github.com/Snowfork/snowbridge/blob/main/parachain/README.md). +Polkadot parachain and our pallets. See [README.md](https://github.com/Snowfork/polkadot-sdk/blob/snowbridge/bridges/snowbridge/README.md). ### Contracts -Ethereum contracts and unit tests. See [contracts/README.md](https://github.com/Snowfork/snowbridge/blob/main/contracts/README.md) +Ethereum contracts and unit tests. See [Snowfork/snowbridge/contracts/README.md](https://github.com/Snowfork/snowbridge/blob/main/contracts/README.md) ### Relayer Off-chain relayer services for relaying messages between Polkadot and Ethereum. See -[relayer/README.md](https://github.com/Snowfork/snowbridge/blob/main/relayer/README.md) +[Snowfork/snowbridge/relayer/README.md](https://github.com/Snowfork/snowbridge/blob/main/relayer/README.md) ### Local Testnet Scripts to provision a local testnet, running the above services to bridge between local deployments of Polkadot and -Ethereum. See [web/packages/test/README.md](https://github.com/Snowfork/snowbridge/blob/main/web/packages/test/README.md). +Ethereum. See [Snowfork/snowbridge/web/packages/test/README.md](https://github.com/Snowfork/snowbridge/blob/main/web/packages/test/README.md). ### Smoke Tests -Integration tests for our local testnet. See [smoketest/README.md](https://github.com/Snowfork/snowbridge/blob/main/smoketest/README.md). +Integration tests for our local testnet. See [Snowfork/snowbridge/smoketest/README.md](https://github.com/Snowfork/snowbridge/blob/main/smoketest/README.md). ## Development @@ -83,7 +91,7 @@ direnv allow ### Upgrading the Rust toolchain -Sometimes we would like to upgrade rust toolchain. First update `parachain/rust-toolchain.toml` as required and then +Sometimes we would like to upgrade rust toolchain. First update `rust-toolchain.toml` as required and then update `flake.lock` running ```sh nix flake lock --update-input rust-overlay diff --git a/bridges/snowbridge/pallets/system/src/lib.rs b/bridges/snowbridge/pallets/system/src/lib.rs index b7f38fb753d..6e5ceb5e9b1 100644 --- a/bridges/snowbridge/pallets/system/src/lib.rs +++ b/bridges/snowbridge/pallets/system/src/lib.rs @@ -37,8 +37,6 @@ //! `force_update_channel` and extrinsics to manage agents and channels for system parachains. #![cfg_attr(not(feature = "std"), no_std)] -pub use pallet::*; - #[cfg(test)] mod mock; @@ -79,6 +77,8 @@ use xcm_executor::traits::ConvertLocation; #[cfg(feature = "runtime-benchmarks")] use frame_support::traits::OriginTrait; +pub use pallet::*; + pub type BalanceOf = <::Token as Inspect<::AccountId>>::Balance; pub type AccountIdOf = ::AccountId; diff --git a/bridges/snowbridge/runtime/test-common/src/lib.rs b/bridges/snowbridge/runtime/test-common/src/lib.rs index c9bbce98e57..29b0e738c18 100644 --- a/bridges/snowbridge/runtime/test-common/src/lib.rs +++ b/bridges/snowbridge/runtime/test-common/src/lib.rs @@ -13,9 +13,9 @@ use parachains_runtimes_test_utils::{ }; use snowbridge_core::{ChannelId, ParaId}; use snowbridge_pallet_ethereum_client_fixtures::*; -use sp_core::H160; +use sp_core::{H160, U256}; use sp_keyring::AccountKeyring::*; -use sp_runtime::{traits::Header, AccountId32, SaturatedConversion, Saturating}; +use sp_runtime::{traits::Header, AccountId32, DigestItem, SaturatedConversion, Saturating}; use xcm::{ latest::prelude::*, v3::Error::{self, Barrier}, @@ -53,7 +53,8 @@ where + parachain_info::Config + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config - + snowbridge_pallet_outbound_queue::Config, + + snowbridge_pallet_outbound_queue::Config + + pallet_timestamp::Config, XcmConfig: xcm_executor::Config, { let assethub_parachain_location = Location::new(1, Parachain(assethub_parachain_id)); @@ -125,7 +126,8 @@ pub fn send_transfer_token_message_success( + pallet_message_queue::Config + cumulus_pallet_parachain_system::Config + snowbridge_pallet_outbound_queue::Config - + snowbridge_pallet_system::Config, + + snowbridge_pallet_system::Config + + pallet_timestamp::Config, XcmConfig: xcm_executor::Config, ValidatorIdOf: From>, ::AccountId: From + AsRef<[u8]>, @@ -193,12 +195,100 @@ pub fn send_transfer_token_message_success( let digest = included_head.digest(); - //let digest = frame_system::Pallet::::digest(); let digest_items = digest.logs(); assert!(digest_items.len() == 1 && digest_items[0].as_other().is_some()); }); } +pub fn ethereum_outbound_queue_processes_messages_before_message_queue_works< + Runtime, + XcmConfig, + AllPalletsWithoutSystem, +>( + collator_session_key: CollatorSessionKeys, + runtime_para_id: u32, + assethub_parachain_id: u32, + weth_contract_address: H160, + destination_address: H160, + fee_amount: u128, + snowbridge_pallet_outbound_queue: Box< + dyn Fn(Vec) -> Option>, + >, +) where + Runtime: frame_system::Config + + pallet_balances::Config + + pallet_session::Config + + pallet_xcm::Config + + parachain_info::Config + + pallet_collator_selection::Config + + pallet_message_queue::Config + + cumulus_pallet_parachain_system::Config + + snowbridge_pallet_outbound_queue::Config + + snowbridge_pallet_system::Config + + pallet_timestamp::Config, + XcmConfig: xcm_executor::Config, + AllPalletsWithoutSystem: + OnInitialize> + OnFinalize>, + ValidatorIdOf: From>, + ::AccountId: From + AsRef<[u8]>, +{ + ExtBuilder::::default() + .with_collators(collator_session_key.collators()) + .with_session_keys(collator_session_key.session_keys()) + .with_para_id(runtime_para_id.into()) + .with_tracing() + .build() + .execute_with(|| { + >::initialize( + runtime_para_id.into(), + assethub_parachain_id.into(), + ) + .unwrap(); + + // fund asset hub sovereign account enough so it can pay fees + initial_fund::(assethub_parachain_id, 5_000_000_000_000); + + let outcome = send_transfer_token_message::( + assethub_parachain_id, + weth_contract_address, + destination_address, + fee_amount, + ); + + assert_ok!(outcome.ensure_complete()); + + // check events + let mut events = >::events() + .into_iter() + .filter_map(|e| snowbridge_pallet_outbound_queue(e.event.encode())); + assert!(events.any(|e| matches!( + e, + snowbridge_pallet_outbound_queue::Event::MessageQueued { .. } + ))); + + let next_block_number: U256 = >::block_number() + .saturating_add(BlockNumberFor::::from(1u32)) + .into(); + + let included_head = + RuntimeHelper::::run_to_block_with_finalize( + next_block_number.as_u32(), + ); + let digest = included_head.digest(); + let digest_items = digest.logs(); + + let mut found_outbound_digest = false; + for digest_item in digest_items { + match digest_item { + DigestItem::Other(_) => found_outbound_digest = true, + _ => {}, + } + } + + assert_eq!(found_outbound_digest, true); + }); +} + pub fn send_unpaid_transfer_token_message( collator_session_key: CollatorSessionKeys, runtime_para_id: u32, @@ -213,7 +303,8 @@ pub fn send_unpaid_transfer_token_message( + parachain_info::Config + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config - + snowbridge_pallet_outbound_queue::Config, + + snowbridge_pallet_outbound_queue::Config + + pallet_timestamp::Config, XcmConfig: xcm_executor::Config, ValidatorIdOf: From>, { @@ -301,7 +392,8 @@ pub fn send_transfer_token_message_failure( + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config + snowbridge_pallet_outbound_queue::Config - + snowbridge_pallet_system::Config, + + snowbridge_pallet_system::Config + + pallet_timestamp::Config, XcmConfig: xcm_executor::Config, ValidatorIdOf: From>, { @@ -349,7 +441,8 @@ pub fn ethereum_extrinsic( + cumulus_pallet_parachain_system::Config + snowbridge_pallet_outbound_queue::Config + snowbridge_pallet_system::Config - + snowbridge_pallet_ethereum_client::Config, + + snowbridge_pallet_ethereum_client::Config + + pallet_timestamp::Config, ValidatorIdOf: From>, ::RuntimeCall: From>, @@ -430,7 +523,8 @@ pub fn ethereum_to_polkadot_message_extrinsics_work( + cumulus_pallet_parachain_system::Config + snowbridge_pallet_outbound_queue::Config + snowbridge_pallet_system::Config - + snowbridge_pallet_ethereum_client::Config, + + snowbridge_pallet_ethereum_client::Config + + pallet_timestamp::Config, ValidatorIdOf: From>, ::RuntimeCall: From>, diff --git a/bridges/snowbridge/scripts/contribute-upstream.sh b/bridges/snowbridge/scripts/contribute-upstream.sh new file mode 100755 index 00000000000..8aa2d2a7035 --- /dev/null +++ b/bridges/snowbridge/scripts/contribute-upstream.sh @@ -0,0 +1,80 @@ +#!/bin/bash + +# A script to cleanup the Snowfork fork of the polkadot-sdk to contribute it upstream back to parity/polkadot-sdk +# ./bridges/snowbridge/scripts/contribute-upstream.sh + +# show CLI help +function show_help() { + set +x + echo " " + echo Error: $1 + echo "Usage:" + echo " ./bridges/snowbridge/scripts/contribute-upstream.sh Exit with code 0 if pallets code is well decoupled from the other code in the repo" + exit 1 +} + +if [[ -z "$1" ]]; then + echo "Please provide a branch name you would like your upstream branch to be named" + exit 1 +fi + +branch_name=$1 + +set -eux + +# let's avoid any restrictions on where this script can be called for - snowbridge repo may be +# plugged into any other repo folder. So the script (and other stuff that needs to be removed) +# may be located either in call dir, or one of it subdirs. +SNOWBRIDGE_FOLDER="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )/../" + +# Get the current Git branch name +current_branch=$(git rev-parse --abbrev-ref HEAD) + +if [ "$current_branch" = "$branch_name" ] || git branch | grep -q "$branch_name"; then + echo "Already on requested branch or branch exists, not creating." +else + git branch "$branch_name" +fi + +git checkout "$branch_name" + +# remove everything we think is not required for our needs +rm -rf rust-toolchain.toml +rm -rf $SNOWBRIDGE_FOLDER/.cargo +rm -rf $SNOWBRIDGE_FOLDER/.github +rm -rf $SNOWBRIDGE_FOLDER/SECURITY.md +rm -rf $SNOWBRIDGE_FOLDER/.gitignore +rm -rf $SNOWBRIDGE_FOLDER/templates +rm -rf $SNOWBRIDGE_FOLDER/pallets/ethereum-client/fuzz + +pushd $SNOWBRIDGE_FOLDER + +# let's test if everything we need compiles +cargo check -p snowbridge-pallet-ethereum-client +cargo check -p snowbridge-pallet-ethereum-client --features runtime-benchmarks +cargo check -p snowbridge-pallet-ethereum-client --features try-runtime +cargo check -p snowbridge-pallet-inbound-queue +cargo check -p snowbridge-pallet-inbound-queue --features runtime-benchmarks +cargo check -p snowbridge-pallet-inbound-queue --features try-runtime +cargo check -p snowbridge-pallet-outbound-queue +cargo check -p snowbridge-pallet-outbound-queue --features runtime-benchmarks +cargo check -p snowbridge-pallet-outbound-queue --features try-runtime +cargo check -p snowbridge-pallet-system +cargo check -p snowbridge-pallet-system --features runtime-benchmarks +cargo check -p snowbridge-pallet-system --features try-runtime + +# we're removing lock file after all checks are done. Otherwise we may use different +# Substrate/Polkadot/Cumulus commits and our checks will fail +rm -f $SNOWBRIDGE_FOLDER/Cargo.toml +rm -f $SNOWBRIDGE_FOLDER/Cargo.lock + +popd + +# Replace Parity's CI files, that we have overwritten in our fork, to run our own CI +rm -rf .github +git remote -v | grep -w parity || git remote add parity https://github.com/paritytech/polkadot-sdk +git fetch parity master +git checkout parity/master -- .github +git add -- .github + +echo "OK" diff --git a/bridges/snowbridge/scripts/verify-pallets-build.sh b/bridges/snowbridge/scripts/verify-pallets-build.sh deleted file mode 100755 index a62f48c84d4..00000000000 --- a/bridges/snowbridge/scripts/verify-pallets-build.sh +++ /dev/null @@ -1,116 +0,0 @@ -#!/bin/bash - -# A script to remove everything from snowbridge repository/subtree, except: -# -# - parachain -# - readme -# - license - -set -eu - -# show CLI help -function show_help() { - set +x - echo " " - echo Error: $1 - echo "Usage:" - echo " ./scripts/verify-pallets-build.sh Exit with code 0 if pallets code is well decoupled from the other code in the repo" - echo "Options:" - echo " --no-revert Leaves only runtime code on exit" - echo " --ignore-git-state Ignores git actual state" - exit 1 -} - -# parse CLI args -NO_REVERT= -IGNORE_GIT_STATE= -for i in "$@" -do - case $i in - --no-revert) - NO_REVERT=true - shift - ;; - --ignore-git-state) - IGNORE_GIT_STATE=true - shift - ;; - *) - show_help "Unknown option: $i" - ;; - esac -done - -# the script is able to work only on clean git copy, unless we want to ignore this check -[[ ! -z "${IGNORE_GIT_STATE}" ]] || [[ -z "$(git status --porcelain)" ]] || { echo >&2 "The git copy must be clean"; exit 1; } - -# let's avoid any restrictions on where this script can be called for - snowbridge repo may be -# plugged into any other repo folder. So the script (and other stuff that needs to be removed) -# may be located either in call dir, or one of it subdirs. -SNOWBRIDGE_FOLDER="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )/../.." - -# remove everything we think is not required for our needs -rm -rf $SNOWBRIDGE_FOLDER/.cargo -rm -rf $SNOWBRIDGE_FOLDER/.github -rm -rf $SNOWBRIDGE_FOLDER/contracts -rm -rf $SNOWBRIDGE_FOLDER/codecov.yml -rm -rf $SNOWBRIDGE_FOLDER/docs -rm -rf $SNOWBRIDGE_FOLDER/hooks -rm -rf $SNOWBRIDGE_FOLDER/relayer -rm -rf $SNOWBRIDGE_FOLDER/scripts -rm -rf $SNOWBRIDGE_FOLDER/SECURITY.md -rm -rf $SNOWBRIDGE_FOLDER/smoketest -rm -rf $SNOWBRIDGE_FOLDER/web -rm -rf $SNOWBRIDGE_FOLDER/.envrc-example -rm -rf $SNOWBRIDGE_FOLDER/.gitbook.yaml -rm -rf $SNOWBRIDGE_FOLDER/.gitignore -rm -rf $SNOWBRIDGE_FOLDER/.gitmodules -rm -rf $SNOWBRIDGE_FOLDER/_typos.toml -rm -rf $SNOWBRIDGE_FOLDER/_codecov.yml -rm -rf $SNOWBRIDGE_FOLDER/flake.lock -rm -rf $SNOWBRIDGE_FOLDER/flake.nix -rm -rf $SNOWBRIDGE_FOLDER/go.work -rm -rf $SNOWBRIDGE_FOLDER/go.work.sum -rm -rf $SNOWBRIDGE_FOLDER/polkadot-sdk -rm -rf $SNOWBRIDGE_FOLDER/rust-toolchain.toml -rm -rf $SNOWBRIDGE_FOLDER/parachain/rustfmt.toml -rm -rf $SNOWBRIDGE_FOLDER/parachain/.gitignore -rm -rf $SNOWBRIDGE_FOLDER/parachain/templates -rm -rf $SNOWBRIDGE_FOLDER/parachain/.cargo -rm -rf $SNOWBRIDGE_FOLDER/parachain/.config -rm -rf $SNOWBRIDGE_FOLDER/parachain/pallets/ethereum-client/fuzz - -cd bridges/snowbridge/parachain - -# fix polkadot-sdk paths in Cargo.toml files -find "." -name 'Cargo.toml' | while read -r file; do - replace=$(printf '../../' ) - if [[ "$(uname)" = "Darwin" ]] || [[ "$(uname)" = *BSD ]]; then - sed -i '' "s|polkadot-sdk/|$replace|g" "$file" - else - sed -i "s|polkadot-sdk/|$replace|g" "$file" - fi -done - -# let's test if everything we need compiles -cargo check -p snowbridge-pallet-ethereum-client -cargo check -p snowbridge-pallet-ethereum-client --features runtime-benchmarks -cargo check -p snowbridge-pallet-ethereum-client --features try-runtime -cargo check -p snowbridge-pallet-inbound-queue -cargo check -p snowbridge-pallet-inbound-queue --features runtime-benchmarks -cargo check -p snowbridge-pallet-inbound-queue --features try-runtime -cargo check -p snowbridge-pallet-outbound-queue -cargo check -p snowbridge-pallet-outbound-queue --features runtime-benchmarks -cargo check -p snowbridge-pallet-outbound-queue --features try-runtime -cargo check -p snowbridge-pallet-system -cargo check -p snowbridge-pallet-system --features runtime-benchmarks -cargo check -p snowbridge-pallet-system --features try-runtime - -cd - - -# we're removing lock file after all checks are done. Otherwise we may use different -# Substrate/Polkadot/Cumulus commits and our checks will fail -rm -f $SNOWBRIDGE_FOLDER/parachain/Cargo.toml -rm -f $SNOWBRIDGE_FOLDER/parachain/Cargo.lock - -echo "OK" diff --git a/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml b/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml index 89c4925cb1a..883c93c97b4 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml @@ -17,6 +17,7 @@ frame-support = { path = "../../../../../substrate/frame/support", default-featu frame-system = { path = "../../../../../substrate/frame/system", default-features = false } pallet-assets = { path = "../../../../../substrate/frame/assets", default-features = false } pallet-balances = { path = "../../../../../substrate/frame/balances", default-features = false } +pallet-timestamp = { path = "../../../../../substrate/frame/timestamp", default-features = false } pallet-session = { path = "../../../../../substrate/frame/session", default-features = false } sp-io = { path = "../../../../../substrate/primitives/io", default-features = false } sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false } @@ -59,6 +60,7 @@ std = [ "pallet-balances/std", "pallet-collator-selection/std", "pallet-session/std", + "pallet-timestamp/std", "pallet-xcm-bridge-hub-router/std", "pallet-xcm/std", "parachain-info/std", diff --git a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs index 4007d926983..53e10956bd0 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs +++ b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs @@ -70,7 +70,8 @@ pub fn teleports_for_native_asset_works< + parachain_info::Config + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config - + cumulus_pallet_xcmp_queue::Config, + + cumulus_pallet_xcmp_queue::Config + + pallet_timestamp::Config, AllPalletsWithoutSystem: OnInitialize> + OnFinalize>, AccountIdOf: Into<[u8; 32]>, @@ -350,7 +351,8 @@ pub fn teleports_for_foreign_assets_works< + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config + cumulus_pallet_xcmp_queue::Config - + pallet_assets::Config, + + pallet_assets::Config + + pallet_timestamp::Config, AllPalletsWithoutSystem: OnInitialize> + OnFinalize>, AccountIdOf: Into<[u8; 32]>, @@ -701,7 +703,8 @@ pub fn asset_transactor_transfer_with_local_consensus_currency_works: Into<[u8; 32]>, ValidatorIdOf: From>, BalanceOf: From, @@ -826,7 +829,8 @@ pub fn asset_transactor_transfer_with_pallet_assets_instance_works< + parachain_info::Config + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config - + pallet_assets::Config, + + pallet_assets::Config + + pallet_timestamp::Config, AccountIdOf: Into<[u8; 32]>, ValidatorIdOf: From>, BalanceOf: From, @@ -1093,7 +1097,8 @@ pub fn create_and_manage_foreign_assets_for_local_consensus_parachain_assets_wor + parachain_info::Config + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config - + pallet_assets::Config, + + pallet_assets::Config + + pallet_timestamp::Config, AccountIdOf: Into<[u8; 32]>, ValidatorIdOf: From>, BalanceOf: From, @@ -1422,7 +1427,8 @@ pub fn reserve_transfer_native_asset_to_non_teleport_para_works< + parachain_info::Config + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config - + cumulus_pallet_xcmp_queue::Config, + + cumulus_pallet_xcmp_queue::Config + + pallet_timestamp::Config, AllPalletsWithoutSystem: OnInitialize> + OnFinalize>, AccountIdOf: Into<[u8; 32]>, diff --git a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs index 66ed3417951..1cce3b647cf 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs +++ b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs @@ -70,7 +70,8 @@ pub fn limited_reserve_transfer_assets_for_native_asset_works< + parachain_info::Config + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config - + cumulus_pallet_xcmp_queue::Config, + + cumulus_pallet_xcmp_queue::Config + + pallet_timestamp::Config, AllPalletsWithoutSystem: OnInitialize> + OnFinalize>, AccountIdOf: Into<[u8; 32]>, @@ -347,7 +348,8 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config + cumulus_pallet_xcmp_queue::Config - + pallet_assets::Config, + + pallet_assets::Config + + pallet_timestamp::Config, AllPalletsWithoutSystem: OnInitialize> + OnFinalize>, AccountIdOf: Into<[u8; 32]> + From<[u8; 32]>, @@ -510,7 +512,8 @@ pub fn report_bridge_status_from_xcm_bridge_router_works< + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config + cumulus_pallet_xcmp_queue::Config - + pallet_xcm_bridge_hub_router::Config, + + pallet_xcm_bridge_hub_router::Config + + pallet_timestamp::Config, AllPalletsWithoutSystem: OnInitialize> + OnFinalize>, AccountIdOf: Into<[u8; 32]>, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index d1eac089f67..ae50d2a93cb 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -730,7 +730,7 @@ construct_runtime!( // Message Queue. Importantly, is registered last so that messages are processed after // the `on_initialize` hooks of bridging pallets. - MessageQueue: pallet_message_queue = 250, + MessageQueue: pallet_message_queue = 175, } ); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs index b2e5cc9ef06..b9f43624b65 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs @@ -20,9 +20,9 @@ use bp_polkadot_core::Signature; use bridge_hub_rococo_runtime::{ bridge_to_bulletin_config::OnBridgeHubRococoRefundRococoBulletinMessages, bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages, - xcm_config::XcmConfig, BridgeRejectObsoleteHeadersAndMessages, Executive, - MessageQueueServiceWeight, Runtime, RuntimeCall, RuntimeEvent, SessionKeys, SignedExtra, - UncheckedExtrinsic, + xcm_config::XcmConfig, AllPalletsWithoutSystem, BridgeRejectObsoleteHeadersAndMessages, + Executive, MessageQueueServiceWeight, Runtime, RuntimeCall, RuntimeEvent, SessionKeys, + SignedExtra, UncheckedExtrinsic, }; use codec::{Decode, Encode}; use cumulus_primitives_core::XcmError::{FailedToTransactAsset, NotHoldingFees}; @@ -135,6 +135,32 @@ fn ethereum_to_polkadot_message_extrinsics_work() { ); } +/// Tests that the digest items are as expected when a Ethereum Outbound message is received. +/// If the MessageQueue pallet is configured before (i.e. the MessageQueue pallet is listed before +/// the EthereumOutboundQueue in the construct_runtime macro) the EthereumOutboundQueue, this test +/// will fail. +#[test] +pub fn ethereum_outbound_queue_processes_messages_before_message_queue_works() { + snowbridge_runtime_test_common::ethereum_outbound_queue_processes_messages_before_message_queue_works::< + Runtime, + XcmConfig, + AllPalletsWithoutSystem, + >( + collator_session_keys(), + 1013, + 1000, + H160::random(), + H160::random(), + DefaultBridgeHubEthereumBaseFee::get(), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::EthereumOutboundQueue(event)) => Some(event), + _ => None, + } + }), + ) +} + fn construct_extrinsic( sender: sp_keyring::AccountKeyring, call: RuntimeCall, diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml index d34b5cd0eed..5f2a6e050d8 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml @@ -25,6 +25,7 @@ sp-std = { path = "../../../../../substrate/primitives/std", default-features = sp-tracing = { path = "../../../../../substrate/primitives/tracing" } pallet-balances = { path = "../../../../../substrate/frame/balances", default-features = false } pallet-utility = { path = "../../../../../substrate/frame/utility", default-features = false } +pallet-timestamp = { path = "../../../../../substrate/frame/timestamp", default-features = false } # Cumulus asset-test-utils = { path = "../../assets/test-utils" } @@ -73,6 +74,7 @@ std = [ "pallet-bridge-messages/std", "pallet-bridge-parachains/std", "pallet-bridge-relayers/std", + "pallet-timestamp/std", "pallet-utility/std", "parachains-common/std", "parachains-runtimes-test-utils/std", diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs index 4f634c184aa..2b48f2e3d51 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs @@ -197,7 +197,9 @@ where pub(crate) fn initialize_bridge_grandpa_pallet( init_data: bp_header_chain::InitializationData>, ) where - Runtime: BridgeGrandpaConfig, + Runtime: BridgeGrandpaConfig + + cumulus_pallet_parachain_system::Config + + pallet_timestamp::Config, { pallet_bridge_grandpa::Pallet::::initialize( RuntimeHelper::::root_origin(), diff --git a/cumulus/parachains/runtimes/test-utils/Cargo.toml b/cumulus/parachains/runtimes/test-utils/Cargo.toml index a61e05de13f..eda88beb7da 100644 --- a/cumulus/parachains/runtimes/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/test-utils/Cargo.toml @@ -17,6 +17,7 @@ frame-support = { path = "../../../../substrate/frame/support", default-features frame-system = { path = "../../../../substrate/frame/system", default-features = false } pallet-balances = { path = "../../../../substrate/frame/balances", default-features = false } pallet-session = { path = "../../../../substrate/frame/session", default-features = false } +pallet-timestamp = { path = "../../../../substrate/frame/timestamp", default-features = false } sp-consensus-aura = { path = "../../../../substrate/primitives/consensus/aura", default-features = false } sp-io = { path = "../../../../substrate/primitives/io", default-features = false } sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } @@ -59,6 +60,7 @@ std = [ "pallet-balances/std", "pallet-collator-selection/std", "pallet-session/std", + "pallet-timestamp/std", "pallet-xcm/std", "parachain-info/std", "polkadot-parachain-primitives/std", diff --git a/cumulus/parachains/runtimes/test-utils/src/lib.rs b/cumulus/parachains/runtimes/test-utils/src/lib.rs index b4eb57fcb66..e62daa16a12 100644 --- a/cumulus/parachains/runtimes/test-utils/src/lib.rs +++ b/cumulus/parachains/runtimes/test-utils/src/lib.rs @@ -34,7 +34,7 @@ use polkadot_parachain_primitives::primitives::{ }; use sp_consensus_aura::{SlotDuration, AURA_ENGINE_ID}; use sp_core::{Encode, U256}; -use sp_runtime::{traits::Header, BuildStorage, Digest, DigestItem}; +use sp_runtime::{traits::Header, BuildStorage, Digest, DigestItem, SaturatedConversion}; use xcm::{ latest::{Asset, Location, XcmContext, XcmHash}, prelude::*, @@ -129,6 +129,7 @@ pub trait BasicParachainRuntime: + parachain_info::Config + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config + + pallet_timestamp::Config { } @@ -140,7 +141,8 @@ where + pallet_xcm::Config + parachain_info::Config + pallet_collator_selection::Config - + cumulus_pallet_parachain_system::Config, + + cumulus_pallet_parachain_system::Config + + pallet_timestamp::Config, ValidatorIdOf: From>, { } @@ -259,8 +261,10 @@ pub struct RuntimeHelper( ); /// Utility function that advances the chain to the desired block number. /// If an author is provided, that author information is injected to all the blocks in the meantime. -impl - RuntimeHelper +impl< + Runtime: frame_system::Config + cumulus_pallet_parachain_system::Config + pallet_timestamp::Config, + AllPalletsWithoutSystem, + > RuntimeHelper where AccountIdOf: Into<<::RuntimeOrigin as OriginTrait>::AccountId>, @@ -296,6 +300,65 @@ where last_header.expect("run_to_block empty block range") } + pub fn run_to_block_with_finalize(n: u32) -> HeaderFor { + let mut last_header = None; + loop { + let block_number = frame_system::Pallet::::block_number(); + if block_number >= n.into() { + break + } + // Set the new block number and author + let header = frame_system::Pallet::::finalize(); + + let pre_digest = Digest { + logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, block_number.encode())], + }; + frame_system::Pallet::::reset_events(); + + let next_block_number = block_number + 1u32.into(); + frame_system::Pallet::::initialize( + &next_block_number, + &header.hash(), + &pre_digest, + ); + AllPalletsWithoutSystem::on_initialize(next_block_number); + + let parent_head = HeadData(header.encode()); + let sproof_builder = RelayStateSproofBuilder { + para_id: ::SelfParaId::get(), + included_para_head: parent_head.clone().into(), + ..Default::default() + }; + + let (relay_parent_storage_root, relay_chain_state) = + sproof_builder.into_state_root_and_proof(); + let inherent_data = ParachainInherentData { + validation_data: PersistedValidationData { + parent_head, + relay_parent_number: (block_number.saturated_into::() * 2 + 1).into(), + relay_parent_storage_root, + max_pov_size: 100_000_000, + }, + relay_chain_state, + downward_messages: Default::default(), + horizontal_messages: Default::default(), + }; + + let _ = cumulus_pallet_parachain_system::Pallet::::set_validation_data( + Runtime::RuntimeOrigin::none(), + inherent_data, + ); + let _ = pallet_timestamp::Pallet::::set( + Runtime::RuntimeOrigin::none(), + 300_u32.into(), + ); + AllPalletsWithoutSystem::on_finalize(next_block_number); + let header = frame_system::Pallet::::finalize(); + last_header = Some(header); + } + last_header.expect("run_to_block empty block range") + } + pub fn root_origin() -> ::RuntimeOrigin { ::RuntimeOrigin::root() } diff --git a/cumulus/parachains/runtimes/test-utils/src/test_cases.rs b/cumulus/parachains/runtimes/test-utils/src/test_cases.rs index f78bf9877ec..1c58df189b6 100644 --- a/cumulus/parachains/runtimes/test-utils/src/test_cases.rs +++ b/cumulus/parachains/runtimes/test-utils/src/test_cases.rs @@ -37,7 +37,8 @@ pub fn change_storage_constant_by_governance_works: From>, StorageConstant: Get, StorageConstantType: Encode + PartialEq + std::fmt::Debug, @@ -107,7 +108,8 @@ pub fn set_storage_keys_by_governance_works( + pallet_xcm::Config + parachain_info::Config + pallet_collator_selection::Config - + cumulus_pallet_parachain_system::Config, + + cumulus_pallet_parachain_system::Config + + pallet_timestamp::Config, ValidatorIdOf: From>, { let mut runtime = ExtBuilder::::default() -- GitLab From 165d075a5f680791c8e0b128ea60edd590a44f1f Mon Sep 17 00:00:00 2001 From: Dastan <88332432+dastansam@users.noreply.github.com> Date: Wed, 21 Feb 2024 15:56:09 +0100 Subject: [PATCH 105/133] benchmarking-cli: add --list-pallets and --all options (#3395) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes #2844 - adds `list-pallets` option which prints all unique available pallets for benchmarking ```bash ./target/release/node benchmark pallet --list=pallets ``` - adds `all` option which runs benchmarks for all available pallets and extrinsics (equivalent to `--pallet * --extrinsic *`) ```bash ./target/release/node benchmark pallet --all ``` - use the `list=pallets` syntax in `run_all_benchmarks.sh` script cc ggwpez --------- Co-authored-by: Bastian Köcher --- prdoc/pr_3395.prdoc | 14 +++++ substrate/scripts/run_all_benchmarks.sh | 11 ++-- .../benchmarking-cli/src/pallet/command.rs | 54 ++++++++++++++----- .../frame/benchmarking-cli/src/pallet/mod.rs | 32 ++++++++--- 4 files changed, 86 insertions(+), 25 deletions(-) create mode 100644 prdoc/pr_3395.prdoc diff --git a/prdoc/pr_3395.prdoc b/prdoc/pr_3395.prdoc new file mode 100644 index 00000000000..a10fb84fd7f --- /dev/null +++ b/prdoc/pr_3395.prdoc @@ -0,0 +1,14 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "`benchmarking-cli` `pallet` subcommand: refactor `--list` and add `--all` option" + +doc: + - audience: Node Dev + description: | + `pallet` subcommand's `--list` now accepts two values: "all" and "pallets". The former will list all available benchmarks, the latter will list only pallets. + Also adds `--all` to run all the available benchmarks and `--no-csv-header` to omit the csv-style header in the output. + NOTE: changes are backward compatible. + +crates: + - name: frame-benchmarking-cli diff --git a/substrate/scripts/run_all_benchmarks.sh b/substrate/scripts/run_all_benchmarks.sh index 83848100a7e..6dd7cede319 100755 --- a/substrate/scripts/run_all_benchmarks.sh +++ b/substrate/scripts/run_all_benchmarks.sh @@ -59,11 +59,12 @@ done if [ "$skip_build" != true ] then echo "[+] Compiling Substrate benchmarks..." - cargo build --profile=production --locked --features=runtime-benchmarks --bin substrate + cargo build --profile=production --locked --features=runtime-benchmarks --bin substrate-node fi # The executable to use. -SUBSTRATE=./target/production/substrate +# Parent directory because of the monorepo structure. +SUBSTRATE=../target/production/substrate-node # Manually exclude some pallets. EXCLUDED_PALLETS=( @@ -80,11 +81,7 @@ EXCLUDED_PALLETS=( # Load all pallet names in an array. ALL_PALLETS=($( - $SUBSTRATE benchmark pallet --list --chain=dev |\ - tail -n+2 |\ - cut -d',' -f1 |\ - sort |\ - uniq + $SUBSTRATE benchmark pallet --list=pallets --no-csv-header --chain=dev )) # Filter out the excluded pallets by concatenating the arrays and discarding duplicates. diff --git a/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs b/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs index dce49db15f7..d5dc6226ab9 100644 --- a/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs +++ b/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::{writer, PalletCmd}; +use super::{writer, ListOutput, PalletCmd}; use codec::{Decode, Encode}; use frame_benchmarking::{ Analysis, BenchmarkBatch, BenchmarkBatchSplitResults, BenchmarkList, BenchmarkParameter, @@ -39,7 +39,13 @@ use sp_externalities::Extensions; use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; use sp_runtime::traits::Hash; use sp_state_machine::StateMachine; -use std::{collections::HashMap, fmt::Debug, fs, str::FromStr, time}; +use std::{ + collections::{BTreeMap, BTreeSet, HashMap}, + fmt::Debug, + fs, + str::FromStr, + time, +}; /// Logging target const LOG_TARGET: &'static str = "frame::benchmark::pallet"; @@ -191,6 +197,7 @@ impl PalletCmd { let spec = config.chain_spec; let pallet = self.pallet.clone().unwrap_or_default(); let pallet = pallet.as_bytes(); + let extrinsic = self.extrinsic.clone().unwrap_or_default(); let extrinsic_split: Vec<&str> = extrinsic.split(',').collect(); let extrinsics: Vec<_> = extrinsic_split.iter().map(|x| x.trim().as_bytes()).collect(); @@ -309,9 +316,8 @@ impl PalletCmd { return Err("No benchmarks found which match your input.".into()) } - if self.list { - // List benchmarks instead of running them - list_benchmark(benchmarks_to_run); + if let Some(list_output) = self.list { + list_benchmark(benchmarks_to_run, list_output, self.no_csv_header); return Ok(()) } @@ -755,19 +761,43 @@ impl CliConfiguration for PalletCmd { /// List the benchmarks available in the runtime, in a CSV friendly format. fn list_benchmark( - mut benchmarks_to_run: Vec<( + benchmarks_to_run: Vec<( Vec, Vec, Vec<(BenchmarkParameter, u32, u32)>, Vec<(String, String)>, )>, + list_output: ListOutput, + no_csv_header: bool, ) { - // Sort and de-dub by pallet and function name. - benchmarks_to_run.sort_by(|(pa, sa, _, _), (pb, sb, _, _)| (pa, sa).cmp(&(pb, sb))); - benchmarks_to_run.dedup_by(|(pa, sa, _, _), (pb, sb, _, _)| (pa, sa) == (pb, sb)); + let mut benchmarks = BTreeMap::new(); - println!("pallet, benchmark"); - for (pallet, extrinsic, _, _) in benchmarks_to_run { - println!("{}, {}", String::from_utf8_lossy(&pallet), String::from_utf8_lossy(&extrinsic)); + // Sort and de-dub by pallet and function name. + benchmarks_to_run.iter().for_each(|(pallet, extrinsic, _, _)| { + benchmarks + .entry(String::from_utf8_lossy(pallet).to_string()) + .or_insert_with(BTreeSet::new) + .insert(String::from_utf8_lossy(extrinsic).to_string()); + }); + + match list_output { + ListOutput::All => { + if !no_csv_header { + println!("pallet,extrinsic"); + } + for (pallet, extrinsics) in benchmarks { + for extrinsic in extrinsics { + println!("{pallet},{extrinsic}"); + } + } + }, + ListOutput::Pallets => { + if !no_csv_header { + println!("pallet"); + }; + for pallet in benchmarks.keys() { + println!("{pallet}"); + } + }, } } diff --git a/substrate/utils/frame/benchmarking-cli/src/pallet/mod.rs b/substrate/utils/frame/benchmarking-cli/src/pallet/mod.rs index c69ce1765fc..6dc56c0724e 100644 --- a/substrate/utils/frame/benchmarking-cli/src/pallet/mod.rs +++ b/substrate/utils/frame/benchmarking-cli/src/pallet/mod.rs @@ -19,6 +19,7 @@ mod command; mod writer; use crate::shared::HostInfoParams; +use clap::ValueEnum; use sc_cli::{ WasmExecutionMethod, WasmtimeInstantiationStrategy, DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, DEFAULT_WASM_EXECUTION_METHOD, @@ -31,17 +32,32 @@ fn parse_pallet_name(pallet: &str) -> std::result::Result { Ok(pallet.replace("-", "_")) } +/// List options for available benchmarks. +#[derive(Debug, Clone, Copy, ValueEnum)] +pub enum ListOutput { + /// List all available pallets and extrinsics. + All, + /// List all available pallets only. + Pallets, +} + /// Benchmark the extrinsic weight of FRAME Pallets. #[derive(Debug, clap::Parser)] pub struct PalletCmd { /// Select a FRAME Pallet to benchmark, or `*` for all (in which case `extrinsic` must be `*`). - #[arg(short, long, value_parser = parse_pallet_name, required_unless_present_any = ["list", "json_input"])] + #[arg(short, long, value_parser = parse_pallet_name, required_unless_present_any = ["list", "json_input", "all"], default_value_if("all", "true", Some("*".into())))] pub pallet: Option, /// Select an extrinsic inside the pallet to benchmark, or `*` for all. - #[arg(short, long, required_unless_present_any = ["list", "json_input"])] + #[arg(short, long, required_unless_present_any = ["list", "json_input", "all"], default_value_if("all", "true", Some("*".into())))] pub extrinsic: Option, + /// Run benchmarks for all pallets and extrinsics. + /// + /// This is equivalent to running `--pallet * --extrinsic *`. + #[arg(long)] + pub all: bool, + /// Select how many samples we should take across the variable components. #[arg(short, long, default_value_t = 50)] pub steps: u32, @@ -158,11 +174,15 @@ pub struct PalletCmd { #[arg(long = "db-cache", value_name = "MiB", default_value_t = 1024)] pub database_cache_size: u32, - /// List the benchmarks that match your query rather than running them. + /// List and print available benchmarks in a csv-friendly format. /// - /// When nothing is provided, we list all benchmarks. - #[arg(long)] - pub list: bool, + /// NOTE: `num_args` and `require_equals` are required to allow `--list` + #[arg(long, value_enum, ignore_case = true, num_args = 0..=1, require_equals = true, default_missing_value("All"))] + pub list: Option, + + /// Don't include csv header when listing benchmarks. + #[arg(long, requires("list"))] + pub no_csv_header: bool, /// If enabled, the storage info is not displayed in the output next to the analysis. /// -- GitLab From 1b624c50728b7f38b31075c3dd8f9a55f0ceee55 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Wed, 21 Feb 2024 18:31:49 +0200 Subject: [PATCH 106/133] rpc-v2/tx: Remove the broadcast event from `transaction_submitAndWatch` (#3321) This PR backports the changes from the rpc-v2 spec: https://github.com/paritytech/json-rpc-interface-spec/pull/134 The `Broadcasted` event has been removed: - it is hard to enforce a `Dropped { broadcasted: bool }` event in cases of a load-balancer being placed in front of an RPC server - when the server exists, it is impossible to guarantee this field if the server did not previously send a `Broadcasted` event - the number of peers reported by this event does not guarantee that peers are unique - the same peer can disconnect and reconnect, increasing this metric number - the number of peers that receive this transaction offers no guarantee about the transaction being included in the chain at a later time cc @paritytech/subxt-team --------- Signed-off-by: Alexandru Vasile Co-authored-by: James Wilson --- .../rpc-spec-v2/src/transaction/event.rs | 59 ++--------- .../client/rpc-spec-v2/src/transaction/mod.rs | 5 +- .../src/transaction/transaction.rs | 97 ++++++------------- 3 files changed, 38 insertions(+), 123 deletions(-) diff --git a/substrate/client/rpc-spec-v2/src/transaction/event.rs b/substrate/client/rpc-spec-v2/src/transaction/event.rs index 8b80fcda17b..882ac8490b0 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/event.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/event.rs @@ -20,23 +20,6 @@ use serde::{Deserialize, Serialize}; -/// The transaction was broadcasted to a number of peers. -/// -/// # Note -/// -/// The RPC does not guarantee that the peers have received the -/// transaction. -/// -/// When the number of peers is zero, the event guarantees that -/// shutting down the local node will lead to the transaction -/// not being included in the chain. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TransactionBroadcasted { - /// The number of peers the transaction was broadcasted to. - pub num_peers: usize, -} - /// The transaction was included in a block of the chain. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -59,9 +42,6 @@ pub struct TransactionError { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TransactionDropped { - /// True if the transaction was broadcasted to other peers and - /// may still be included in the block. - pub broadcasted: bool, /// Reason of the event. pub error: String, } @@ -70,20 +50,17 @@ pub struct TransactionDropped { /// /// The status events can be grouped based on their kinds as: /// -/// 1. Runtime validated the transaction: +/// 1. Runtime validated the transaction and it entered the pool: /// - `Validated` /// -/// 2. Inside the `Ready` queue: -/// - `Broadcast` -/// -/// 3. Leaving the pool: +/// 2. Leaving the pool: /// - `BestChainBlockIncluded` /// - `Invalid` /// -/// 4. Block finalized: +/// 3. Block finalized: /// - `Finalized` /// -/// 5. At any time: +/// 4. At any time: /// - `Dropped` /// - `Error` /// @@ -101,8 +78,6 @@ pub struct TransactionDropped { pub enum TransactionEvent { /// The transaction was validated by the runtime. Validated, - /// The transaction was broadcasted to a number of peers. - Broadcasted(TransactionBroadcasted), /// The transaction was included in a best block of the chain. /// /// # Note @@ -159,7 +134,6 @@ enum TransactionEventBlockIR { #[serde(tag = "event")] enum TransactionEventNonBlockIR { Validated, - Broadcasted(TransactionBroadcasted), Error(TransactionError), Invalid(TransactionError), Dropped(TransactionDropped), @@ -186,8 +160,6 @@ impl From> for TransactionEventIR { match value { TransactionEvent::Validated => TransactionEventIR::NonBlock(TransactionEventNonBlockIR::Validated), - TransactionEvent::Broadcasted(event) => - TransactionEventIR::NonBlock(TransactionEventNonBlockIR::Broadcasted(event)), TransactionEvent::BestChainBlockIncluded(event) => TransactionEventIR::Block(TransactionEventBlockIR::BestChainBlockIncluded(event)), TransactionEvent::Finalized(event) => @@ -207,8 +179,6 @@ impl From> for TransactionEvent { match value { TransactionEventIR::NonBlock(status) => match status { TransactionEventNonBlockIR::Validated => TransactionEvent::Validated, - TransactionEventNonBlockIR::Broadcasted(event) => - TransactionEvent::Broadcasted(event), TransactionEventNonBlockIR::Error(event) => TransactionEvent::Error(event), TransactionEventNonBlockIR::Invalid(event) => TransactionEvent::Invalid(event), TransactionEventNonBlockIR::Dropped(event) => TransactionEvent::Dropped(event), @@ -239,19 +209,6 @@ mod tests { assert_eq!(event_dec, event); } - #[test] - fn broadcasted_event() { - let event: TransactionEvent<()> = - TransactionEvent::Broadcasted(TransactionBroadcasted { num_peers: 2 }); - let ser = serde_json::to_string(&event).unwrap(); - - let exp = r#"{"event":"broadcasted","numPeers":2}"#; - assert_eq!(ser, exp); - - let event_dec: TransactionEvent<()> = serde_json::from_str(exp).unwrap(); - assert_eq!(event_dec, event); - } - #[test] fn best_chain_event() { let event: TransactionEvent<()> = TransactionEvent::BestChainBlockIncluded(None); @@ -320,13 +277,11 @@ mod tests { #[test] fn dropped_event() { - let event: TransactionEvent<()> = TransactionEvent::Dropped(TransactionDropped { - broadcasted: true, - error: "abc".to_string(), - }); + let event: TransactionEvent<()> = + TransactionEvent::Dropped(TransactionDropped { error: "abc".to_string() }); let ser = serde_json::to_string(&event).unwrap(); - let exp = r#"{"event":"dropped","broadcasted":true,"error":"abc"}"#; + let exp = r#"{"event":"dropped","error":"abc"}"#; assert_eq!(ser, exp); let event_dec: TransactionEvent<()> = serde_json::from_str(exp).unwrap(); diff --git a/substrate/client/rpc-spec-v2/src/transaction/mod.rs b/substrate/client/rpc-spec-v2/src/transaction/mod.rs index 74268a5372a..514ccf047dc 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/mod.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/mod.rs @@ -35,9 +35,6 @@ pub mod transaction; pub mod transaction_broadcast; pub use api::{TransactionApiServer, TransactionBroadcastApiServer}; -pub use event::{ - TransactionBlock, TransactionBroadcasted, TransactionDropped, TransactionError, - TransactionEvent, -}; +pub use event::{TransactionBlock, TransactionDropped, TransactionError, TransactionEvent}; pub use transaction::Transaction; pub use transaction_broadcast::TransactionBroadcast; diff --git a/substrate/client/rpc-spec-v2/src/transaction/transaction.rs b/substrate/client/rpc-spec-v2/src/transaction/transaction.rs index 17889b3bad2..d44006392dc 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/transaction.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/transaction.rs @@ -22,10 +22,7 @@ use crate::{ transaction::{ api::TransactionApiServer, error::Error, - event::{ - TransactionBlock, TransactionBroadcasted, TransactionDropped, TransactionError, - TransactionEvent, - }, + event::{TransactionBlock, TransactionDropped, TransactionError, TransactionEvent}, }, SubscriptionTaskExecutor, }; @@ -113,9 +110,7 @@ where match submit.await { Ok(stream) => { - let mut state = TransactionState::new(); - let stream = - stream.filter_map(move |event| async move { state.handle_event(event) }); + let stream = stream.filter_map(move |event| async move { handle_event(event) }); pipe_from_stream(pending, stream.boxed()).await; }, Err(err) => { @@ -131,66 +126,34 @@ where } } -/// The transaction's state that needs to be preserved between -/// multiple events generated by the transaction-pool. -/// -/// # Note -/// -/// In the future, the RPC server can submit only the last event when multiple -/// identical events happen in a row. -#[derive(Clone, Copy)] -struct TransactionState { - /// True if the transaction was previously broadcasted. - broadcasted: bool, -} - -impl TransactionState { - /// Construct a new [`TransactionState`]. - pub fn new() -> Self { - TransactionState { broadcasted: false } - } - - /// Handle events generated by the transaction-pool and convert them - /// to the new API expected state. - #[inline] - pub fn handle_event( - &mut self, - event: TransactionStatus, - ) -> Option> { - match event { - TransactionStatus::Ready | TransactionStatus::Future => - Some(TransactionEvent::::Validated), - TransactionStatus::Broadcast(peers) => { - // Set the broadcasted flag once if we submitted the transaction to - // at least one peer. - self.broadcasted = self.broadcasted || !peers.is_empty(); - - Some(TransactionEvent::Broadcasted(TransactionBroadcasted { - num_peers: peers.len(), - })) - }, - TransactionStatus::InBlock((hash, index)) => - Some(TransactionEvent::BestChainBlockIncluded(Some(TransactionBlock { - hash, - index, - }))), - TransactionStatus::Retracted(_) => Some(TransactionEvent::BestChainBlockIncluded(None)), - TransactionStatus::FinalityTimeout(_) => - Some(TransactionEvent::Dropped(TransactionDropped { - broadcasted: self.broadcasted, - error: "Maximum number of finality watchers has been reached".into(), - })), - TransactionStatus::Finalized((hash, index)) => - Some(TransactionEvent::Finalized(TransactionBlock { hash, index })), - TransactionStatus::Usurped(_) => Some(TransactionEvent::Invalid(TransactionError { - error: "Extrinsic was rendered invalid by another extrinsic".into(), - })), - TransactionStatus::Dropped => Some(TransactionEvent::Invalid(TransactionError { - error: "Extrinsic dropped from the pool due to exceeding limits".into(), - })), - TransactionStatus::Invalid => Some(TransactionEvent::Invalid(TransactionError { - error: "Extrinsic marked as invalid".into(), +/// Handle events generated by the transaction-pool and convert them +/// to the new API expected state. +#[inline] +pub fn handle_event( + event: TransactionStatus, +) -> Option> { + match event { + TransactionStatus::Ready | TransactionStatus::Future => + Some(TransactionEvent::::Validated), + TransactionStatus::InBlock((hash, index)) => + Some(TransactionEvent::BestChainBlockIncluded(Some(TransactionBlock { hash, index }))), + TransactionStatus::Retracted(_) => Some(TransactionEvent::BestChainBlockIncluded(None)), + TransactionStatus::FinalityTimeout(_) => + Some(TransactionEvent::Dropped(TransactionDropped { + error: "Maximum number of finality watchers has been reached".into(), })), - } + TransactionStatus::Finalized((hash, index)) => + Some(TransactionEvent::Finalized(TransactionBlock { hash, index })), + TransactionStatus::Usurped(_) => Some(TransactionEvent::Invalid(TransactionError { + error: "Extrinsic was rendered invalid by another extrinsic".into(), + })), + TransactionStatus::Dropped => Some(TransactionEvent::Invalid(TransactionError { + error: "Extrinsic dropped from the pool due to exceeding limits".into(), + })), + TransactionStatus::Invalid => Some(TransactionEvent::Invalid(TransactionError { + error: "Extrinsic marked as invalid".into(), + })), + // These are the events that are not supported by the new API. + TransactionStatus::Broadcast(_) => None, } } -- GitLab From 318fed32f8ff42e451ac9f750c25c0f2f85320ad Mon Sep 17 00:00:00 2001 From: tmpolaczyk <44604217+tmpolaczyk@users.noreply.github.com> Date: Wed, 21 Feb 2024 17:38:06 +0100 Subject: [PATCH 107/133] ProposerFactory impl Clone (#3389) In Tanssi, we need a way to stop the collator code and then start it again. This is to support rotating the same collator between different runtimes. Currently, this works very well, except for the proposer metrics, because they only get registered the first time they are started. Afterwards, we see this warning log: > Failed to register proposer prometheus metrics: Duplicate metrics collector registration attempted ~~So this PR adds a method to set metrics, to allow us to register metrics manually before creating the `ProposerFactory`, and then clone the same metrics every time we need to start the collator.~~ Implemented Clone instead --- .../basic-authorship/src/basic_authorship.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/substrate/client/basic-authorship/src/basic_authorship.rs b/substrate/client/basic-authorship/src/basic_authorship.rs index c07f3e639c3..fdc3d4918de 100644 --- a/substrate/client/basic-authorship/src/basic_authorship.rs +++ b/substrate/client/basic-authorship/src/basic_authorship.rs @@ -87,6 +87,22 @@ pub struct ProposerFactory { _phantom: PhantomData, } +impl Clone for ProposerFactory { + fn clone(&self) -> Self { + Self { + spawn_handle: self.spawn_handle.clone(), + client: self.client.clone(), + transaction_pool: self.transaction_pool.clone(), + metrics: self.metrics.clone(), + default_block_size_limit: self.default_block_size_limit, + soft_deadline_percent: self.soft_deadline_percent, + telemetry: self.telemetry.clone(), + include_proof_in_block_size_estimation: self.include_proof_in_block_size_estimation, + _phantom: self._phantom, + } + } +} + impl ProposerFactory { /// Create a new proposer factory. /// -- GitLab From cd91c6b782b60906597ed28c4e4a4fdddd8428c9 Mon Sep 17 00:00:00 2001 From: Matteo Muraca <56828990+muraca@users.noreply.github.com> Date: Thu, 22 Feb 2024 00:02:31 +0100 Subject: [PATCH 108/133] removed `pallet::getter` from example pallets (#3371) part of #3326 @ggwpez @kianenigma @shawntabrizi --------- Signed-off-by: Matteo Muraca Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- .../pallets/template/src/lib.rs | 1 - .../pallets/template/src/tests.rs | 4 ++-- prdoc/pr_3371.prdoc | 19 +++++++++++++++++++ .../node-template/pallets/template/src/lib.rs | 4 +--- .../pallets/template/src/tests.rs | 4 ++-- .../frame/examples/basic/src/benchmarking.rs | 2 +- substrate/frame/examples/basic/src/lib.rs | 14 +++----------- substrate/frame/examples/basic/src/tests.rs | 12 ++++++------ .../examples/kitchensink/src/benchmarking.rs | 2 +- .../frame/examples/kitchensink/src/lib.rs | 1 - .../frame/examples/offchain-worker/src/lib.rs | 12 +++++------- substrate/frame/examples/split/src/lib.rs | 2 +- 12 files changed, 41 insertions(+), 36 deletions(-) create mode 100644 prdoc/pr_3371.prdoc diff --git a/cumulus/parachain-template/pallets/template/src/lib.rs b/cumulus/parachain-template/pallets/template/src/lib.rs index 5f3252bfc3a..24226d6cf40 100644 --- a/cumulus/parachain-template/pallets/template/src/lib.rs +++ b/cumulus/parachain-template/pallets/template/src/lib.rs @@ -32,7 +32,6 @@ pub mod pallet { // The pallet's runtime storage items. // https://docs.substrate.io/v3/runtime/storage #[pallet::storage] - #[pallet::getter(fn something)] // Learn more about declaring storage items: // https://docs.substrate.io/v3/runtime/storage#declaring-storage-items pub type Something = StorageValue<_, u32>; diff --git a/cumulus/parachain-template/pallets/template/src/tests.rs b/cumulus/parachain-template/pallets/template/src/tests.rs index 527aec8ed00..9ad3076be2c 100644 --- a/cumulus/parachain-template/pallets/template/src/tests.rs +++ b/cumulus/parachain-template/pallets/template/src/tests.rs @@ -1,4 +1,4 @@ -use crate::{mock::*, Error}; +use crate::{mock::*, Error, Something}; use frame_support::{assert_noop, assert_ok}; #[test] @@ -7,7 +7,7 @@ fn it_works_for_default_value() { // Dispatch a signed extrinsic. assert_ok!(TemplateModule::do_something(RuntimeOrigin::signed(1), 42)); // Read pallet storage and assert an expected result. - assert_eq!(TemplateModule::something(), Some(42)); + assert_eq!(Something::::get(), Some(42)); }); } diff --git a/prdoc/pr_3371.prdoc b/prdoc/pr_3371.prdoc new file mode 100644 index 00000000000..605d540772f --- /dev/null +++ b/prdoc/pr_3371.prdoc @@ -0,0 +1,19 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: removed `pallet::getter` from example pallets + +doc: + - audience: Runtime Dev + description: | + This PR removes all the `pallet::getter` usages from the template pallets found in the Substrate and Cumulus template nodes, and from the Substrate example pallets. + The purpose is to discourage developers to use this macro, that is currently being removed and soon will be deprecated. + +crates: + - name: pallet-template + - name: pallet-parachain-template + - name: pallet-example-basic + - name: pallet-example-kitchensink + - name: pallet-example-offchain-worker + - name: pallet-example-split + diff --git a/substrate/bin/node-template/pallets/template/src/lib.rs b/substrate/bin/node-template/pallets/template/src/lib.rs index 4a2e53baa77..90dfe370145 100644 --- a/substrate/bin/node-template/pallets/template/src/lib.rs +++ b/substrate/bin/node-template/pallets/template/src/lib.rs @@ -90,9 +90,7 @@ pub mod pallet { /// /// In this template, we are declaring a storage item called `Something` that stores a single /// `u32` value. Learn more about runtime storage here: - /// The [`getter`] macro generates a function to conveniently retrieve the value from storage. #[pallet::storage] - #[pallet::getter(fn something)] pub type Something = StorageValue<_, u32>; /// Events that functions in this pallet can emit. @@ -187,7 +185,7 @@ pub mod pallet { let _who = ensure_signed(origin)?; // Read a value from storage. - match Pallet::::something() { + match Something::::get() { // Return an error if the value has not been set. None => Err(Error::::NoneValue.into()), Some(old) => { diff --git a/substrate/bin/node-template/pallets/template/src/tests.rs b/substrate/bin/node-template/pallets/template/src/tests.rs index 7c2b853ee4d..83e4bea7377 100644 --- a/substrate/bin/node-template/pallets/template/src/tests.rs +++ b/substrate/bin/node-template/pallets/template/src/tests.rs @@ -1,4 +1,4 @@ -use crate::{mock::*, Error, Event}; +use crate::{mock::*, Error, Event, Something}; use frame_support::{assert_noop, assert_ok}; #[test] @@ -9,7 +9,7 @@ fn it_works_for_default_value() { // Dispatch a signed extrinsic. assert_ok!(TemplateModule::do_something(RuntimeOrigin::signed(1), 42)); // Read pallet storage and assert an expected result. - assert_eq!(TemplateModule::something(), Some(42)); + assert_eq!(Something::::get(), Some(42)); // Assert that the correct event was deposited System::assert_last_event(Event::SomethingStored { something: 42, who: 1 }.into()); }); diff --git a/substrate/frame/examples/basic/src/benchmarking.rs b/substrate/frame/examples/basic/src/benchmarking.rs index 4b2ebb41fbd..65ca3089aba 100644 --- a/substrate/frame/examples/basic/src/benchmarking.rs +++ b/substrate/frame/examples/basic/src/benchmarking.rs @@ -48,7 +48,7 @@ mod benchmarks { set_dummy(RawOrigin::Root, value); // The execution phase is just running `set_dummy` extrinsic call // This is the optional benchmark verification phase, asserting certain states. - assert_eq!(Pallet::::dummy(), Some(value)) + assert_eq!(Dummy::::get(), Some(value)) } // An example method that returns a Result that can be called within a benchmark diff --git a/substrate/frame/examples/basic/src/lib.rs b/substrate/frame/examples/basic/src/lib.rs index dad4d01978f..12cadc969fd 100644 --- a/substrate/frame/examples/basic/src/lib.rs +++ b/substrate/frame/examples/basic/src/lib.rs @@ -286,9 +286,7 @@ pub mod pallet { let _sender = ensure_signed(origin)?; // Read the value of dummy from storage. - // let dummy = Self::dummy(); - // Will also work using the `::get` on the storage item type itself: - // let dummy = >::get(); + // let dummy = Dummy::::get(); // Calculate the new value. // let new_dummy = dummy.map_or(increase_by, |dummy| dummy + increase_by); @@ -381,20 +379,14 @@ pub mod pallet { // - `Foo::put(1); Foo::get()` returns `1`; // - `Foo::kill(); Foo::get()` returns `0` (u32::default()). #[pallet::storage] - // The getter attribute generate a function on `Pallet` placeholder: - // `fn getter_name() -> Type` for basic value items or - // `fn getter_name(key: KeyType) -> ValueType` for map items. - #[pallet::getter(fn dummy)] pub(super) type Dummy = StorageValue<_, T::Balance>; // A map that has enumerable entries. #[pallet::storage] - #[pallet::getter(fn bar)] pub(super) type Bar = StorageMap<_, Blake2_128Concat, T::AccountId, T::Balance>; // this one uses the query kind: `ValueQuery`, we'll demonstrate the usage of 'mutate' API. #[pallet::storage] - #[pallet::getter(fn foo)] pub(super) type Foo = StorageValue<_, T::Balance, ValueQuery>; #[pallet::storage] @@ -433,10 +425,10 @@ impl Pallet { fn accumulate_foo(origin: T::RuntimeOrigin, increase_by: T::Balance) -> DispatchResult { let _sender = ensure_signed(origin)?; - let prev = >::get(); + let prev = Foo::::get(); // Because Foo has 'default', the type of 'foo' in closure is the raw type instead of an // Option<> type. - let result = >::mutate(|foo| { + let result = Foo::::mutate(|foo| { *foo = foo.saturating_add(increase_by); *foo }); diff --git a/substrate/frame/examples/basic/src/tests.rs b/substrate/frame/examples/basic/src/tests.rs index 9434ace35ff..207e46e428d 100644 --- a/substrate/frame/examples/basic/src/tests.rs +++ b/substrate/frame/examples/basic/src/tests.rs @@ -119,25 +119,25 @@ fn it_works_for_optional_value() { // Check that GenesisBuilder works properly. let val1 = 42; let val2 = 27; - assert_eq!(Example::dummy(), Some(val1)); + assert_eq!(Dummy::::get(), Some(val1)); // Check that accumulate works when we have Some value in Dummy already. assert_ok!(Example::accumulate_dummy(RuntimeOrigin::signed(1), val2)); - assert_eq!(Example::dummy(), Some(val1 + val2)); + assert_eq!(Dummy::::get(), Some(val1 + val2)); // Check that accumulate works when we Dummy has None in it. >::on_initialize(2); assert_ok!(Example::accumulate_dummy(RuntimeOrigin::signed(1), val1)); - assert_eq!(Example::dummy(), Some(val1 + val2 + val1)); + assert_eq!(Dummy::::get(), Some(val1 + val2 + val1)); }); } #[test] fn it_works_for_default_value() { new_test_ext().execute_with(|| { - assert_eq!(Example::foo(), 24); + assert_eq!(Foo::::get(), 24); assert_ok!(Example::accumulate_foo(RuntimeOrigin::signed(1), 1)); - assert_eq!(Example::foo(), 25); + assert_eq!(Foo::::get(), 25); }); } @@ -146,7 +146,7 @@ fn set_dummy_works() { new_test_ext().execute_with(|| { let test_val = 133; assert_ok!(Example::set_dummy(RuntimeOrigin::root(), test_val.into())); - assert_eq!(Example::dummy(), Some(test_val)); + assert_eq!(Dummy::::get(), Some(test_val)); }); } diff --git a/substrate/frame/examples/kitchensink/src/benchmarking.rs b/substrate/frame/examples/kitchensink/src/benchmarking.rs index 24da581fc96..5f1d378e06f 100644 --- a/substrate/frame/examples/kitchensink/src/benchmarking.rs +++ b/substrate/frame/examples/kitchensink/src/benchmarking.rs @@ -51,7 +51,7 @@ mod benchmarks { set_foo(RawOrigin::Root, value, 10u128); // The execution phase is just running `set_foo` extrinsic call // This is the optional benchmark verification phase, asserting certain states. - assert_eq!(Pallet::::foo(), Some(value)) + assert_eq!(Foo::::get(), Some(value)) } // This line generates test cases for benchmarking, and could be run by: diff --git a/substrate/frame/examples/kitchensink/src/lib.rs b/substrate/frame/examples/kitchensink/src/lib.rs index 18429bc967d..b7425b0c084 100644 --- a/substrate/frame/examples/kitchensink/src/lib.rs +++ b/substrate/frame/examples/kitchensink/src/lib.rs @@ -125,7 +125,6 @@ pub mod pallet { #[pallet::storage] #[pallet::unbounded] // optional #[pallet::storage_prefix = "OtherFoo"] // optional - #[pallet::getter(fn foo)] // optional pub type Foo = StorageValue; #[pallet::type_value] diff --git a/substrate/frame/examples/offchain-worker/src/lib.rs b/substrate/frame/examples/offchain-worker/src/lib.rs index 6c1fa6ea8ec..d21c8b4cfd2 100644 --- a/substrate/frame/examples/offchain-worker/src/lib.rs +++ b/substrate/frame/examples/offchain-worker/src/lib.rs @@ -332,7 +332,6 @@ pub mod pallet { /// /// This is used to calculate average price, should have bounded size. #[pallet::storage] - #[pallet::getter(fn prices)] pub(super) type Prices = StorageValue<_, BoundedVec, ValueQuery>; /// Defines the block when next unsigned transaction will be accepted. @@ -341,7 +340,6 @@ pub mod pallet { /// we only allow one transaction every `T::UnsignedInterval` blocks. /// This storage entry defines when new transaction is going to be accepted. #[pallet::storage] - #[pallet::getter(fn next_unsigned_at)] pub(super) type NextUnsignedAt = StorageValue<_, BlockNumberFor, ValueQuery>; } @@ -479,7 +477,7 @@ impl Pallet { ) -> Result<(), &'static str> { // Make sure we don't fetch the price if unsigned transaction is going to be rejected // anyway. - let next_unsigned_at = >::get(); + let next_unsigned_at = NextUnsignedAt::::get(); if next_unsigned_at > block_number { return Err("Too early to send unsigned transaction") } @@ -513,7 +511,7 @@ impl Pallet { ) -> Result<(), &'static str> { // Make sure we don't fetch the price if unsigned transaction is going to be rejected // anyway. - let next_unsigned_at = >::get(); + let next_unsigned_at = NextUnsignedAt::::get(); if next_unsigned_at > block_number { return Err("Too early to send unsigned transaction") } @@ -543,7 +541,7 @@ impl Pallet { ) -> Result<(), &'static str> { // Make sure we don't fetch the price if unsigned transaction is going to be rejected // anyway. - let next_unsigned_at = >::get(); + let next_unsigned_at = NextUnsignedAt::::get(); if next_unsigned_at > block_number { return Err("Too early to send unsigned transaction") } @@ -664,7 +662,7 @@ impl Pallet { /// Calculate current average price. fn average_price() -> Option { - let prices = >::get(); + let prices = Prices::::get(); if prices.is_empty() { None } else { @@ -677,7 +675,7 @@ impl Pallet { new_price: &u32, ) -> TransactionValidity { // Now let's check if the transaction has any chance to succeed. - let next_unsigned_at = >::get(); + let next_unsigned_at = NextUnsignedAt::::get(); if &next_unsigned_at > block_number { return InvalidTransaction::Stale.into() } diff --git a/substrate/frame/examples/split/src/lib.rs b/substrate/frame/examples/split/src/lib.rs index 74d2e0cc24b..5245d90e390 100644 --- a/substrate/frame/examples/split/src/lib.rs +++ b/substrate/frame/examples/split/src/lib.rs @@ -107,7 +107,7 @@ pub mod pallet { let _who = ensure_signed(origin)?; // Read a value from storage. - match >::get() { + match Something::::get() { // Return an error if the value has not been set. None => return Err(Error::::NoneValue.into()), Some(old) => { -- GitLab From e76b244853c52d523e1d5c7a28948573df60df46 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 22 Feb 2024 01:35:01 +0100 Subject: [PATCH 109/133] [FRAME] Test for sane genesis default (#3412) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes https://github.com/paritytech/polkadot-sdk/issues/2713 --------- Signed-off-by: Oliver Tale-Yazdi Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> --- cumulus/pallets/aura-ext/src/lib.rs | 6 --- .../chains/relays/rococo/src/genesis.rs | 2 +- .../chains/relays/westend/src/genesis.rs | 2 +- prdoc/pr_3412.prdoc | 17 ++++++++ .../cli/tests/res/default_genesis_config.json | 8 +++- substrate/bin/node/testing/src/genesis.rs | 39 ++----------------- substrate/client/chain-spec/src/chain_spec.rs | 10 +---- .../chain-spec/src/genesis_config_builder.rs | 2 +- .../client/consensus/babe/rpc/src/lib.rs | 2 +- substrate/frame/babe/src/lib.rs | 6 +-- substrate/frame/session/src/lib.rs | 18 +-------- .../src/construct_runtime/expand/config.rs | 11 ++++++ substrate/frame/support/src/lib.rs | 2 +- .../primitives/consensus/babe/src/lib.rs | 6 +++ .../test-utils/runtime/src/genesismap.rs | 1 - substrate/test-utils/runtime/src/lib.rs | 2 +- 16 files changed, 55 insertions(+), 79 deletions(-) create mode 100644 prdoc/pr_3412.prdoc diff --git a/cumulus/pallets/aura-ext/src/lib.rs b/cumulus/pallets/aura-ext/src/lib.rs index 34a41557152..31b571816a0 100644 --- a/cumulus/pallets/aura-ext/src/lib.rs +++ b/cumulus/pallets/aura-ext/src/lib.rs @@ -117,12 +117,6 @@ pub mod pallet { impl BuildGenesisConfig for GenesisConfig { fn build(&self) { let authorities = Aura::::authorities(); - - assert!( - !authorities.is_empty(), - "AuRa authorities empty, maybe wrong order in `construct_runtime!`?", - ); - Authorities::::put(authorities); } } diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs index 7db9679f1c3..55437645b05 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs @@ -78,7 +78,7 @@ pub fn genesis() -> Storage { }, babe: rococo_runtime::BabeConfig { authorities: Default::default(), - epoch_config: Some(rococo_runtime::BABE_GENESIS_EPOCH_CONFIG), + epoch_config: rococo_runtime::BABE_GENESIS_EPOCH_CONFIG, ..Default::default() }, sudo: rococo_runtime::SudoConfig { diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs index 578b307dd93..700b80e63f6 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs @@ -94,7 +94,7 @@ pub fn genesis() -> Storage { }, babe: westend_runtime::BabeConfig { authorities: Default::default(), - epoch_config: Some(westend_runtime::BABE_GENESIS_EPOCH_CONFIG), + epoch_config: westend_runtime::BABE_GENESIS_EPOCH_CONFIG, ..Default::default() }, configuration: westend_runtime::ConfigurationConfig { config: get_host_config() }, diff --git a/prdoc/pr_3412.prdoc b/prdoc/pr_3412.prdoc new file mode 100644 index 00000000000..d68f05e45dc --- /dev/null +++ b/prdoc/pr_3412.prdoc @@ -0,0 +1,17 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "[FRAME] Add genesis test and remove some checks" + +doc: + - audience: Runtime Dev + description: | + The construct_runtime macro now generates a test to assert that all `GenesisConfig`s of all + pallets can be build within the runtime. This ensures that the `BuildGenesisConfig` runtime + API works. + Further, some checks from a few pallets were removed to make this pass. + +crates: + - name: pallet-babe + - name: pallet-aura-ext + - name: pallet-session diff --git a/substrate/bin/node/cli/tests/res/default_genesis_config.json b/substrate/bin/node/cli/tests/res/default_genesis_config.json index 1465a6497ca..e21fbb47da8 100644 --- a/substrate/bin/node/cli/tests/res/default_genesis_config.json +++ b/substrate/bin/node/cli/tests/res/default_genesis_config.json @@ -2,7 +2,13 @@ "system": {}, "babe": { "authorities": [], - "epochConfig": null + "epochConfig": { + "allowed_slots": "PrimaryAndSecondaryVRFSlots", + "c": [ + 1, + 4 + ] + } }, "indices": { "indices": [] diff --git a/substrate/bin/node/testing/src/genesis.rs b/substrate/bin/node/testing/src/genesis.rs index 6ec21fbe093..c79612d6844 100644 --- a/substrate/bin/node/testing/src/genesis.rs +++ b/substrate/bin/node/testing/src/genesis.rs @@ -20,9 +20,8 @@ use crate::keyring::*; use kitchensink_runtime::{ - constants::currency::*, AccountId, AssetsConfig, BabeConfig, BalancesConfig, GluttonConfig, - GrandpaConfig, IndicesConfig, RuntimeGenesisConfig, SessionConfig, SocietyConfig, StakerStatus, - StakingConfig, BABE_GENESIS_EPOCH_CONFIG, + constants::currency::*, AccountId, AssetsConfig, BalancesConfig, IndicesConfig, + RuntimeGenesisConfig, SessionConfig, SocietyConfig, StakerStatus, StakingConfig, }; use sp_keyring::Ed25519Keyring; use sp_runtime::Perbill; @@ -47,7 +46,6 @@ pub fn config_endowed(extra_endowed: Vec) -> RuntimeGenesisConfig { endowed.extend(extra_endowed.into_iter().map(|endowed| (endowed, 100 * DOLLARS))); RuntimeGenesisConfig { - system: Default::default(), indices: IndicesConfig { indices: vec![] }, balances: BalancesConfig { balances: endowed }, session: SessionConfig { @@ -69,39 +67,8 @@ pub fn config_endowed(extra_endowed: Vec) -> RuntimeGenesisConfig { invulnerables: vec![alice(), bob(), charlie()], ..Default::default() }, - babe: BabeConfig { - authorities: vec![], - epoch_config: Some(BABE_GENESIS_EPOCH_CONFIG), - ..Default::default() - }, - grandpa: GrandpaConfig { authorities: vec![], _config: Default::default() }, - beefy: Default::default(), - im_online: Default::default(), - authority_discovery: Default::default(), - democracy: Default::default(), - council: Default::default(), - technical_committee: Default::default(), - technical_membership: Default::default(), - elections: Default::default(), - sudo: Default::default(), - treasury: Default::default(), society: SocietyConfig { pot: 0 }, - vesting: Default::default(), assets: AssetsConfig { assets: vec![(9, alice(), true, 1)], ..Default::default() }, - pool_assets: Default::default(), - transaction_storage: Default::default(), - transaction_payment: Default::default(), - alliance: Default::default(), - alliance_motion: Default::default(), - nomination_pools: Default::default(), - safe_mode: Default::default(), - tx_pause: Default::default(), - glutton: GluttonConfig { - compute: Default::default(), - storage: Default::default(), - trash_data_count: Default::default(), - ..Default::default() - }, - mixnet: Default::default(), + ..Default::default() } } diff --git a/substrate/client/chain-spec/src/chain_spec.rs b/substrate/client/chain-spec/src/chain_spec.rs index fe8fcfda216..78e81e10d2b 100644 --- a/substrate/client/chain-spec/src/chain_spec.rs +++ b/substrate/client/chain-spec/src/chain_spec.rs @@ -1237,15 +1237,7 @@ mod tests { "TestName", "test", ChainType::Local, - move || substrate_test_runtime::RuntimeGenesisConfig { - babe: substrate_test_runtime::BabeConfig { - epoch_config: Some( - substrate_test_runtime::TEST_RUNTIME_BABE_EPOCH_CONFIGURATION, - ), - ..Default::default() - }, - ..Default::default() - }, + || Default::default(), Vec::new(), None, None, diff --git a/substrate/client/chain-spec/src/genesis_config_builder.rs b/substrate/client/chain-spec/src/genesis_config_builder.rs index 8766dd5c5ad..6b956316203 100644 --- a/substrate/client/chain-spec/src/genesis_config_builder.rs +++ b/substrate/client/chain-spec/src/genesis_config_builder.rs @@ -149,7 +149,7 @@ mod tests { ::new(substrate_test_runtime::wasm_binary_unwrap()) .get_default_config() .unwrap(); - let expected = r#"{"system":{},"babe":{"authorities":[],"epochConfig":null},"substrateTest":{"authorities":[]},"balances":{"balances":[]}}"#; + let expected = r#"{"babe": {"authorities": [], "epochConfig": {"allowed_slots": "PrimaryAndSecondaryVRFSlots", "c": [1, 4]}}, "balances": {"balances": []}, "substrateTest": {"authorities": []}, "system": {}}"#; assert_eq!(from_str::(expected).unwrap(), config); } diff --git a/substrate/client/consensus/babe/rpc/src/lib.rs b/substrate/client/consensus/babe/rpc/src/lib.rs index 8b183e7dfdc..a3e811baecf 100644 --- a/substrate/client/consensus/babe/rpc/src/lib.rs +++ b/substrate/client/consensus/babe/rpc/src/lib.rs @@ -258,7 +258,7 @@ mod tests { let request = r#"{"jsonrpc":"2.0","method":"babe_epochAuthorship","params": [],"id":1}"#; let (response, _) = api.raw_json_request(request, 1).await.unwrap(); - let expected = r#"{"jsonrpc":"2.0","result":{"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY":{"primary":[0],"secondary":[1,2,4],"secondary_vrf":[]}},"id":1}"#; + let expected = r#"{"jsonrpc":"2.0","result":{"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY":{"primary":[0],"secondary":[],"secondary_vrf":[1,2,4]}},"id":1}"#; assert_eq!(response, expected); } diff --git a/substrate/frame/babe/src/lib.rs b/substrate/frame/babe/src/lib.rs index a6e44390dbc..5fb107dde3b 100644 --- a/substrate/frame/babe/src/lib.rs +++ b/substrate/frame/babe/src/lib.rs @@ -323,7 +323,7 @@ pub mod pallet { #[pallet::genesis_config] pub struct GenesisConfig { pub authorities: Vec<(AuthorityId, BabeAuthorityWeight)>, - pub epoch_config: Option, + pub epoch_config: BabeEpochConfiguration, #[serde(skip)] pub _config: sp_std::marker::PhantomData, } @@ -333,9 +333,7 @@ pub mod pallet { fn build(&self) { SegmentIndex::::put(0); Pallet::::initialize_genesis_authorities(&self.authorities); - EpochConfig::::put( - self.epoch_config.clone().expect("epoch_config must not be None"), - ); + EpochConfig::::put(&self.epoch_config); } } diff --git a/substrate/frame/session/src/lib.rs b/substrate/frame/session/src/lib.rs index 178d43f596b..fc35cd6ddd8 100644 --- a/substrate/frame/session/src/lib.rs +++ b/substrate/frame/session/src/lib.rs @@ -460,27 +460,13 @@ pub mod pallet { ); self.keys.iter().map(|x| x.1.clone()).collect() }); - assert!( - !initial_validators_0.is_empty(), - "Empty validator set for session 0 in genesis block!" - ); let initial_validators_1 = T::SessionManager::new_session_genesis(1) .unwrap_or_else(|| initial_validators_0.clone()); - assert!( - !initial_validators_1.is_empty(), - "Empty validator set for session 1 in genesis block!" - ); let queued_keys: Vec<_> = initial_validators_1 - .iter() - .cloned() - .map(|v| { - ( - v.clone(), - Pallet::::load_keys(&v).expect("Validator in session 1 missing keys!"), - ) - }) + .into_iter() + .filter_map(|v| Pallet::::load_keys(&v).map(|k| (v, k))) .collect(); // Tell everyone about the genesis session keys diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs index ffe55bceb80..5613047359a 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs @@ -99,6 +99,17 @@ pub fn expand_outer_config( ::on_genesis(); } } + + /// Test the `Default` derive impl of the `RuntimeGenesisConfig`. + #[cfg(test)] + #[test] + fn test_genesis_config_builds() { + #scrate::__private::sp_io::TestExternalities::default().execute_with(|| { + ::build( + &RuntimeGenesisConfig::default() + ); + }); + } } } diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs index 8935acf4e2b..cd12da6de54 100644 --- a/substrate/frame/support/src/lib.rs +++ b/substrate/frame/support/src/lib.rs @@ -780,7 +780,7 @@ macro_rules! assert_err_with_weight { $crate::assert_err!($call.map(|_| ()).map_err(|e| e.error), $err); assert_eq!(dispatch_err_with_post.post_info.actual_weight, $weight); } else { - panic!("expected Err(_), got Ok(_).") + ::core::panic!("expected Err(_), got Ok(_).") } }; } diff --git a/substrate/primitives/consensus/babe/src/lib.rs b/substrate/primitives/consensus/babe/src/lib.rs index d6b2cdd55e0..ff0b4568226 100644 --- a/substrate/primitives/consensus/babe/src/lib.rs +++ b/substrate/primitives/consensus/babe/src/lib.rs @@ -256,6 +256,12 @@ pub struct BabeEpochConfiguration { pub allowed_slots: AllowedSlots, } +impl Default for BabeEpochConfiguration { + fn default() -> Self { + Self { c: (1, 4), allowed_slots: AllowedSlots::PrimaryAndSecondaryVRFSlots } + } +} + /// Verifies the equivocation proof by making sure that: both headers have /// different hashes, are targetting the same slot, and have valid signatures by /// the same authority. diff --git a/substrate/test-utils/runtime/src/genesismap.rs b/substrate/test-utils/runtime/src/genesismap.rs index 5ed9c8a6458..9e972886b37 100644 --- a/substrate/test-utils/runtime/src/genesismap.rs +++ b/substrate/test-utils/runtime/src/genesismap.rs @@ -124,7 +124,6 @@ impl GenesisStorageBuilder { .into_iter() .map(|x| (x.into(), 1)) .collect(), - epoch_config: Some(crate::TEST_RUNTIME_BABE_EPOCH_CONFIGURATION), ..Default::default() }, substrate_test: substrate_test_pallet::GenesisConfig { diff --git a/substrate/test-utils/runtime/src/lib.rs b/substrate/test-utils/runtime/src/lib.rs index 8bc6f72a82e..db9ff187b70 100644 --- a/substrate/test-utils/runtime/src/lib.rs +++ b/substrate/test-utils/runtime/src/lib.rs @@ -1291,7 +1291,7 @@ mod tests { let r = Vec::::decode(&mut &r[..]).unwrap(); let json = String::from_utf8(r.into()).expect("returned value is json. qed."); - let expected = r#"{"system":{},"babe":{"authorities":[],"epochConfig":null},"substrateTest":{"authorities":[]},"balances":{"balances":[]}}"#; + let expected = r#"{"system":{},"babe":{"authorities":[],"epochConfig":{"c":[1,4],"allowed_slots":"PrimaryAndSecondaryVRFSlots"}},"substrateTest":{"authorities":[]},"balances":{"balances":[]}}"#; assert_eq!(expected.to_string(), json); } -- GitLab From 60e537b95f2336dc598cb9ce49823316841ee3dd Mon Sep 17 00:00:00 2001 From: Andrei Sandu <54316454+sandreim@users.noreply.github.com> Date: Thu, 22 Feb 2024 15:22:31 +0700 Subject: [PATCH 110/133] Elastic scaling: use an assumed `CoreIndex` in `candidate-backing` (#3229) First step in implementing https://github.com/paritytech/polkadot-sdk/issues/3144 ### Summary of changes - switch statement `Table` candidate mapping from `ParaId` to `CoreIndex` - introduce experimental `InjectCoreIndex` node feature. - determine and assume a `CoreIndex` for a candidate based on statement validator index. If the signature is valid it means validator controls the validator that index and we can easily map it to a validator group/core. - introduce a temporary provisioner fix until we fully enable elastic scaling in the subystem. The fix ensures we don't fetch the same backable candidate when calling `get_backable_candidate` for each core. TODO: - [x] fix backing tests - [x] fix statement table tests - [x] add new test --------- Signed-off-by: Andrei Sandu Signed-off-by: alindima Co-authored-by: alindima --- Cargo.lock | 3 + polkadot/node/core/backing/Cargo.toml | 1 + polkadot/node/core/backing/src/error.rs | 3 + polkadot/node/core/backing/src/lib.rs | 285 ++++++++++++++---- polkadot/node/core/backing/src/tests/mod.rs | 128 +++++++- .../src/tests/prospective_parachains.rs | 34 ++- polkadot/node/core/provisioner/src/lib.rs | 12 +- polkadot/primitives/Cargo.toml | 2 + polkadot/primitives/src/v6/mod.rs | 27 +- polkadot/primitives/src/vstaging/mod.rs | 6 +- polkadot/statement-table/Cargo.toml | 1 + polkadot/statement-table/src/generic.rs | 50 ++- polkadot/statement-table/src/lib.rs | 6 +- 13 files changed, 462 insertions(+), 96 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 93ee9cc99c4..950b4e0889a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12490,6 +12490,7 @@ dependencies = [ "polkadot-primitives-test-helpers", "polkadot-statement-table", "sc-keystore", + "schnellru", "sp-application-crypto", "sp-core", "sp-keyring", @@ -13159,6 +13160,7 @@ version = "7.0.0" dependencies = [ "bitvec", "hex-literal", + "log", "parity-scale-codec", "polkadot-core-primitives", "polkadot-parachain-primitives", @@ -13556,6 +13558,7 @@ dependencies = [ "parity-scale-codec", "polkadot-primitives", "sp-core", + "tracing-gum", ] [[package]] diff --git a/polkadot/node/core/backing/Cargo.toml b/polkadot/node/core/backing/Cargo.toml index b0cf041e38d..f71b8df80dd 100644 --- a/polkadot/node/core/backing/Cargo.toml +++ b/polkadot/node/core/backing/Cargo.toml @@ -22,6 +22,7 @@ bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } gum = { package = "tracing-gum", path = "../../gum" } thiserror = { workspace = true } fatality = "0.0.6" +schnellru = "0.2.1" [dev-dependencies] sp-core = { path = "../../../../substrate/primitives/core" } diff --git a/polkadot/node/core/backing/src/error.rs b/polkadot/node/core/backing/src/error.rs index 1b00a62510b..64955a39396 100644 --- a/polkadot/node/core/backing/src/error.rs +++ b/polkadot/node/core/backing/src/error.rs @@ -48,6 +48,9 @@ pub enum Error { #[error("Candidate is not found")] CandidateNotFound, + #[error("CoreIndex cannot be determined for a candidate")] + CoreIndexUnavailable, + #[error("Signature is invalid")] InvalidSignature, diff --git a/polkadot/node/core/backing/src/lib.rs b/polkadot/node/core/backing/src/lib.rs index 98bbd6232ad..cc192607cea 100644 --- a/polkadot/node/core/backing/src/lib.rs +++ b/polkadot/node/core/backing/src/lib.rs @@ -70,13 +70,14 @@ use std::{ sync::Arc, }; -use bitvec::vec::BitVec; +use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec}; use futures::{ channel::{mpsc, oneshot}, future::BoxFuture, stream::FuturesOrdered, FutureExt, SinkExt, StreamExt, TryFutureExt, }; +use schnellru::{ByLength, LruMap}; use error::{Error, FatalResult}; use polkadot_node_primitives::{ @@ -104,10 +105,12 @@ use polkadot_node_subsystem_util::{ Validator, }; use polkadot_primitives::{ + vstaging::{node_features::FeatureIndex, NodeFeatures}, BackedCandidate, CandidateCommitments, CandidateHash, CandidateReceipt, - CommittedCandidateReceipt, CoreIndex, CoreState, ExecutorParams, Hash, Id as ParaId, - PersistedValidationData, PvfExecKind, SigningContext, ValidationCode, ValidatorId, - ValidatorIndex, ValidatorSignature, ValidityAttestation, + CommittedCandidateReceipt, CoreIndex, CoreState, ExecutorParams, GroupIndex, GroupRotationInfo, + Hash, Id as ParaId, IndexedVec, PersistedValidationData, PvfExecKind, SessionIndex, + SigningContext, ValidationCode, ValidatorId, ValidatorIndex, ValidatorSignature, + ValidityAttestation, }; use sp_keystore::KeystorePtr; use statement_table::{ @@ -118,7 +121,7 @@ use statement_table::{ }, Config as TableConfig, Context as TableContextTrait, Table, }; -use util::vstaging::get_disabled_validators_with_fallback; +use util::{runtime::request_node_features, vstaging::get_disabled_validators_with_fallback}; mod error; @@ -209,7 +212,9 @@ struct PerRelayParentState { /// The hash of the relay parent on top of which this job is doing it's work. parent: Hash, /// The `ParaId` assigned to the local validator at this relay parent. - assignment: Option, + assigned_para: Option, + /// The `CoreIndex` assigned to the local validator at this relay parent. + assigned_core: Option, /// The candidates that are backed by enough validators in their group, by hash. backed: HashSet, /// The table of candidates and statements under this relay-parent. @@ -224,6 +229,15 @@ struct PerRelayParentState { fallbacks: HashMap, /// The minimum backing votes threshold. minimum_backing_votes: u32, + /// If true, we're appending extra bits in the BackedCandidate validator indices bitfield, + /// which represent the assigned core index. True if ElasticScalingMVP is enabled. + inject_core_index: bool, + /// The core states for all cores. + cores: Vec, + /// The validator index -> group mapping at this relay parent. + validator_to_group: Arc>>, + /// The associated group rotation information. + group_rotation_info: GroupRotationInfo, } struct PerCandidateState { @@ -275,6 +289,9 @@ struct State { /// This is guaranteed to have an entry for each candidate with a relay parent in the implicit /// or explicit view for which a `Seconded` statement has been successfully imported. per_candidate: HashMap, + /// Cache the per-session Validator->Group mapping. + validator_to_group_cache: + LruMap>>>, /// A cloneable sender which is dispatched to background candidate validation tasks to inform /// the main task of the result. background_validation_tx: mpsc::Sender<(Hash, ValidatedCandidateCommand)>, @@ -292,6 +309,7 @@ impl State { per_leaf: HashMap::default(), per_relay_parent: HashMap::default(), per_candidate: HashMap::new(), + validator_to_group_cache: LruMap::new(ByLength::new(2)), background_validation_tx, keystore, } @@ -379,10 +397,10 @@ struct AttestingData { backing: Vec, } -#[derive(Default)] +#[derive(Default, Debug)] struct TableContext { validator: Option, - groups: HashMap>, + groups: HashMap>, validators: Vec, disabled_validators: Vec, } @@ -404,7 +422,7 @@ impl TableContext { impl TableContextTrait for TableContext { type AuthorityId = ValidatorIndex; type Digest = CandidateHash; - type GroupId = ParaId; + type GroupId = CoreIndex; type Signature = ValidatorSignature; type Candidate = CommittedCandidateReceipt; @@ -412,15 +430,11 @@ impl TableContextTrait for TableContext { candidate.hash() } - fn candidate_group(candidate: &CommittedCandidateReceipt) -> ParaId { - candidate.descriptor().para_id + fn is_member_of(&self, authority: &ValidatorIndex, core: &CoreIndex) -> bool { + self.groups.get(core).map_or(false, |g| g.iter().any(|a| a == authority)) } - fn is_member_of(&self, authority: &ValidatorIndex, group: &ParaId) -> bool { - self.groups.get(group).map_or(false, |g| g.iter().any(|a| a == authority)) - } - - fn get_group_size(&self, group: &ParaId) -> Option { + fn get_group_size(&self, group: &CoreIndex) -> Option { self.groups.get(group).map(|g| g.len()) } } @@ -442,19 +456,20 @@ fn primitive_statement_to_table(s: &SignedFullStatementWithPVD) -> TableSignedSt fn table_attested_to_backed( attested: TableAttestedCandidate< - ParaId, + CoreIndex, CommittedCandidateReceipt, ValidatorIndex, ValidatorSignature, >, table_context: &TableContext, + inject_core_index: bool, ) -> Option { - let TableAttestedCandidate { candidate, validity_votes, group_id: para_id } = attested; + let TableAttestedCandidate { candidate, validity_votes, group_id: core_index } = attested; let (ids, validity_votes): (Vec<_>, Vec) = validity_votes.into_iter().map(|(id, vote)| (id, vote.into())).unzip(); - let group = table_context.groups.get(¶_id)?; + let group = table_context.groups.get(&core_index)?; let mut validator_indices = BitVec::with_capacity(group.len()); @@ -479,6 +494,12 @@ fn table_attested_to_backed( } vote_positions.sort_by_key(|(_orig, pos_in_group)| *pos_in_group); + if inject_core_index { + let core_index_to_inject: BitVec = + BitVec::from_vec(vec![core_index.0 as u8]); + validator_indices.extend(core_index_to_inject); + } + Some(BackedCandidate { candidate, validity_votes: vote_positions @@ -971,7 +992,14 @@ async fn handle_active_leaves_update( // construct a `PerRelayParent` from the runtime API // and insert it. - let per = construct_per_relay_parent_state(ctx, maybe_new, &state.keystore, mode).await?; + let per = construct_per_relay_parent_state( + ctx, + maybe_new, + &state.keystore, + &mut state.validator_to_group_cache, + mode, + ) + .await?; if let Some(per) = per { state.per_relay_parent.insert(maybe_new, per); @@ -981,31 +1009,112 @@ async fn handle_active_leaves_update( Ok(()) } +macro_rules! try_runtime_api { + ($x: expr) => { + match $x { + Ok(x) => x, + Err(err) => { + // Only bubble up fatal errors. + error::log_error(Err(Into::::into(err).into()))?; + + // We can't do candidate validation work if we don't have the + // requisite runtime API data. But these errors should not take + // down the node. + return Ok(None) + }, + } + }; +} + +fn core_index_from_statement( + validator_to_group: &IndexedVec>, + group_rotation_info: &GroupRotationInfo, + cores: &[CoreState], + statement: &SignedFullStatementWithPVD, +) -> Option { + let compact_statement = statement.as_unchecked(); + let candidate_hash = CandidateHash(*compact_statement.unchecked_payload().candidate_hash()); + + let n_cores = cores.len(); + + gum::trace!( + target:LOG_TARGET, + ?group_rotation_info, + ?statement, + ?validator_to_group, + n_cores = ?cores.len(), + ?candidate_hash, + "Extracting core index from statement" + ); + + let statement_validator_index = statement.validator_index(); + let Some(Some(group_index)) = validator_to_group.get(statement_validator_index) else { + gum::debug!( + target: LOG_TARGET, + ?group_rotation_info, + ?statement, + ?validator_to_group, + n_cores = ?cores.len() , + ?candidate_hash, + "Invalid validator index: {:?}", + statement_validator_index + ); + return None + }; + + // First check if the statement para id matches the core assignment. + let core_index = group_rotation_info.core_for_group(*group_index, n_cores); + + if core_index.0 as usize > n_cores { + gum::warn!(target: LOG_TARGET, ?candidate_hash, ?core_index, n_cores, "Invalid CoreIndex"); + return None + } + + if let StatementWithPVD::Seconded(candidate, _pvd) = statement.payload() { + let candidate_para_id = candidate.descriptor.para_id; + let assigned_para_id = match &cores[core_index.0 as usize] { + CoreState::Free => { + gum::debug!(target: LOG_TARGET, ?candidate_hash, "Invalid CoreIndex, core is not assigned to any para_id"); + return None + }, + CoreState::Occupied(occupied) => + if let Some(next) = &occupied.next_up_on_available { + next.para_id + } else { + return None + }, + CoreState::Scheduled(scheduled) => scheduled.para_id, + }; + + if assigned_para_id != candidate_para_id { + gum::debug!( + target: LOG_TARGET, + ?candidate_hash, + ?core_index, + ?assigned_para_id, + ?candidate_para_id, + "Invalid CoreIndex, core is assigned to a different para_id" + ); + return None + } + return Some(core_index) + } else { + return Some(core_index) + } +} + /// Load the data necessary to do backing work on top of a relay-parent. #[overseer::contextbounds(CandidateBacking, prefix = self::overseer)] async fn construct_per_relay_parent_state( ctx: &mut Context, relay_parent: Hash, keystore: &KeystorePtr, + validator_to_group_cache: &mut LruMap< + SessionIndex, + Arc>>, + >, mode: ProspectiveParachainsMode, ) -> Result, Error> { - macro_rules! try_runtime_api { - ($x: expr) => { - match $x { - Ok(x) => x, - Err(err) => { - // Only bubble up fatal errors. - error::log_error(Err(Into::::into(err).into()))?; - - // We can't do candidate validation work if we don't have the - // requisite runtime API data. But these errors should not take - // down the node. - return Ok(None) - }, - } - }; - } - let parent = relay_parent; let (session_index, validators, groups, cores) = futures::try_join!( @@ -1020,6 +1129,16 @@ async fn construct_per_relay_parent_state( .map_err(Error::JoinMultiple)?; let session_index = try_runtime_api!(session_index); + + let inject_core_index = request_node_features(parent, session_index, ctx.sender()) + .await? + .unwrap_or(NodeFeatures::EMPTY) + .get(FeatureIndex::ElasticScalingMVP as usize) + .map(|b| *b) + .unwrap_or(false); + + gum::debug!(target: LOG_TARGET, inject_core_index, ?parent, "New state"); + let validators: Vec<_> = try_runtime_api!(validators); let (validator_groups, group_rotation_info) = try_runtime_api!(groups); let cores = try_runtime_api!(cores); @@ -1055,18 +1174,24 @@ async fn construct_per_relay_parent_state( }, }; - let mut groups = HashMap::new(); let n_cores = cores.len(); - let mut assignment = None; - for (idx, core) in cores.into_iter().enumerate() { + let mut groups = HashMap::>::new(); + let mut assigned_core = None; + let mut assigned_para = None; + + for (idx, core) in cores.iter().enumerate() { let core_para_id = match core { CoreState::Scheduled(scheduled) => scheduled.para_id, CoreState::Occupied(occupied) => if mode.is_enabled() { // Async backing makes it legal to build on top of // occupied core. - occupied.candidate_descriptor.para_id + if let Some(next) = &occupied.next_up_on_available { + next.para_id + } else { + continue + } } else { continue }, @@ -1077,11 +1202,27 @@ async fn construct_per_relay_parent_state( let group_index = group_rotation_info.group_for_core(core_index, n_cores); if let Some(g) = validator_groups.get(group_index.0 as usize) { if validator.as_ref().map_or(false, |v| g.contains(&v.index())) { - assignment = Some(core_para_id); + assigned_para = Some(core_para_id); + assigned_core = Some(core_index); } - groups.insert(core_para_id, g.clone()); + groups.insert(core_index, g.clone()); } } + gum::debug!(target: LOG_TARGET, ?groups, "TableContext"); + + let validator_to_group = validator_to_group_cache + .get_or_insert(session_index, || { + let mut vector = vec![None; validators.len()]; + + for (group_idx, validator_group) in validator_groups.iter().enumerate() { + for validator in validator_group { + vector[validator.0 as usize] = Some(GroupIndex(group_idx as u32)); + } + } + + Arc::new(IndexedVec::<_, _>::from(vector)) + }) + .expect("Just inserted"); let table_context = TableContext { validator, groups, validators, disabled_validators }; let table_config = TableConfig { @@ -1094,7 +1235,8 @@ async fn construct_per_relay_parent_state( Ok(Some(PerRelayParentState { prospective_parachains_mode: mode, parent, - assignment, + assigned_core, + assigned_para, backed: HashSet::new(), table: Table::new(table_config), table_context, @@ -1102,6 +1244,10 @@ async fn construct_per_relay_parent_state( awaiting_validation: HashSet::new(), fallbacks: HashMap::new(), minimum_backing_votes, + inject_core_index, + cores, + validator_to_group: validator_to_group.clone(), + group_rotation_info, })) } @@ -1519,15 +1665,16 @@ async fn import_statement( per_candidate: &mut HashMap, statement: &SignedFullStatementWithPVD, ) -> Result, Error> { + let candidate_hash = statement.payload().candidate_hash(); + gum::debug!( target: LOG_TARGET, statement = ?statement.payload().to_compact(), validator_index = statement.validator_index().0, + ?candidate_hash, "Importing statement", ); - let candidate_hash = statement.payload().candidate_hash(); - // If this is a new candidate (statement is 'seconded' and candidate is unknown), // we need to create an entry in the `PerCandidateState` map. // @@ -1593,7 +1740,15 @@ async fn import_statement( let stmt = primitive_statement_to_table(statement); - Ok(rp_state.table.import_statement(&rp_state.table_context, stmt)) + let core = core_index_from_statement( + &rp_state.validator_to_group, + &rp_state.group_rotation_info, + &rp_state.cores, + statement, + ) + .ok_or(Error::CoreIndexUnavailable)?; + + Ok(rp_state.table.import_statement(&rp_state.table_context, core, stmt)) } /// Handles a summary received from [`import_statement`] and dispatches `Backed` notifications and @@ -1615,7 +1770,11 @@ async fn post_import_statement_actions( // `HashSet::insert` returns true if the thing wasn't in there already. if rp_state.backed.insert(candidate_hash) { - if let Some(backed) = table_attested_to_backed(attested, &rp_state.table_context) { + if let Some(backed) = table_attested_to_backed( + attested, + &rp_state.table_context, + rp_state.inject_core_index, + ) { let para_id = backed.candidate.descriptor.para_id; gum::debug!( target: LOG_TARGET, @@ -1654,8 +1813,14 @@ async fn post_import_statement_actions( ); ctx.send_unbounded_message(message); } + } else { + gum::debug!(target: LOG_TARGET, ?candidate_hash, "Cannot get BackedCandidate"); } + } else { + gum::debug!(target: LOG_TARGET, ?candidate_hash, "Candidate already known"); } + } else { + gum::debug!(target: LOG_TARGET, "No attested candidate"); } issue_new_misbehaviors(ctx, rp_state.parent, &mut rp_state.table); @@ -1859,9 +2024,10 @@ async fn maybe_validate_and_import( let candidate_hash = summary.candidate; - if Some(summary.group_id) != rp_state.assignment { + if Some(summary.group_id) != rp_state.assigned_core { return Ok(()) } + let attesting = match statement.payload() { StatementWithPVD::Seconded(receipt, _) => { let attesting = AttestingData { @@ -2004,10 +2170,11 @@ async fn handle_second_message( } // Sanity check that candidate is from our assignment. - if Some(candidate.descriptor().para_id) != rp_state.assignment { + if Some(candidate.descriptor().para_id) != rp_state.assigned_para { gum::debug!( target: LOG_TARGET, - our_assignment = ?rp_state.assignment, + our_assignment_core = ?rp_state.assigned_core, + our_assignment_para = ?rp_state.assigned_para, collation = ?candidate.descriptor().para_id, "Subsystem asked to second for para outside of our assignment", ); @@ -2015,6 +2182,14 @@ async fn handle_second_message( return Ok(()) } + gum::debug!( + target: LOG_TARGET, + our_assignment_core = ?rp_state.assigned_core, + our_assignment_para = ?rp_state.assigned_para, + collation = ?candidate.descriptor().para_id, + "Current assignments vs collation", + ); + // If the message is a `CandidateBackingMessage::Second`, sign and dispatch a // Seconded statement only if we have not signed a Valid statement for the requested candidate. // @@ -2087,7 +2262,13 @@ fn handle_get_backed_candidates_message( &rp_state.table_context, rp_state.minimum_backing_votes, ) - .and_then(|attested| table_attested_to_backed(attested, &rp_state.table_context)) + .and_then(|attested| { + table_attested_to_backed( + attested, + &rp_state.table_context, + rp_state.inject_core_index, + ) + }) }) .collect(); diff --git a/polkadot/node/core/backing/src/tests/mod.rs b/polkadot/node/core/backing/src/tests/mod.rs index 1957f4e19c5..7223f1e1dfb 100644 --- a/polkadot/node/core/backing/src/tests/mod.rs +++ b/polkadot/node/core/backing/src/tests/mod.rs @@ -65,13 +65,14 @@ fn dummy_pvd() -> PersistedValidationData { } } -struct TestState { +pub(crate) struct TestState { chain_ids: Vec, keystore: KeystorePtr, validators: Vec, validator_public: Vec, validation_data: PersistedValidationData, validator_groups: (Vec>, GroupRotationInfo), + validator_to_group: IndexedVec>, availability_cores: Vec, head_data: HashMap, signing_context: SigningContext, @@ -114,6 +115,11 @@ impl Default for TestState { .into_iter() .map(|g| g.into_iter().map(ValidatorIndex).collect()) .collect(); + let validator_to_group: IndexedVec<_, _> = + vec![Some(0), Some(1), Some(0), Some(0), None, Some(0)] + .into_iter() + .map(|x| x.map(|x| GroupIndex(x))) + .collect(); let group_rotation_info = GroupRotationInfo { session_start_block: 0, group_rotation_frequency: 100, now: 1 }; @@ -143,6 +149,7 @@ impl Default for TestState { validators, validator_public, validator_groups: (validator_groups, group_rotation_info), + validator_to_group, availability_cores, head_data, validation_data, @@ -285,6 +292,16 @@ async fn test_startup(virtual_overseer: &mut VirtualOverseer, test_state: &TestS } ); + // Node features request from runtime: all features are disabled. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_parent, RuntimeApiRequest::NodeFeatures(_session_index, tx)) + ) => { + tx.send(Ok(Default::default())).unwrap(); + } + ); + // Check if subsystem job issues a request for the minimum backing votes. assert_matches!( virtual_overseer.recv().await, @@ -639,6 +656,107 @@ fn backing_works() { }); } +#[test] +fn extract_core_index_from_statement_works() { + let test_state = TestState::default(); + + let pov_a = PoV { block_data: BlockData(vec![42, 43, 44]) }; + let pvd_a = dummy_pvd(); + let validation_code_a = ValidationCode(vec![1, 2, 3]); + + let pov_hash = pov_a.hash(); + + let mut candidate = TestCandidateBuilder { + para_id: test_state.chain_ids[0], + relay_parent: test_state.relay_parent, + pov_hash, + erasure_root: make_erasure_root(&test_state, pov_a.clone(), pvd_a.clone()), + persisted_validation_data_hash: pvd_a.hash(), + validation_code: validation_code_a.0.clone(), + ..Default::default() + } + .build(); + + let public2 = Keystore::sr25519_generate_new( + &*test_state.keystore, + ValidatorId::ID, + Some(&test_state.validators[2].to_seed()), + ) + .expect("Insert key into keystore"); + + let signed_statement_1 = SignedFullStatementWithPVD::sign( + &test_state.keystore, + StatementWithPVD::Seconded(candidate.clone(), pvd_a.clone()), + &test_state.signing_context, + ValidatorIndex(2), + &public2.into(), + ) + .ok() + .flatten() + .expect("should be signed"); + + let public1 = Keystore::sr25519_generate_new( + &*test_state.keystore, + ValidatorId::ID, + Some(&test_state.validators[1].to_seed()), + ) + .expect("Insert key into keystore"); + + let signed_statement_2 = SignedFullStatementWithPVD::sign( + &test_state.keystore, + StatementWithPVD::Seconded(candidate.clone(), pvd_a.clone()), + &test_state.signing_context, + ValidatorIndex(1), + &public1.into(), + ) + .ok() + .flatten() + .expect("should be signed"); + + candidate.descriptor.para_id = test_state.chain_ids[1]; + + let signed_statement_3 = SignedFullStatementWithPVD::sign( + &test_state.keystore, + StatementWithPVD::Seconded(candidate, pvd_a.clone()), + &test_state.signing_context, + ValidatorIndex(1), + &public1.into(), + ) + .ok() + .flatten() + .expect("should be signed"); + + let core_index_1 = core_index_from_statement( + &test_state.validator_to_group, + &test_state.validator_groups.1, + &test_state.availability_cores, + &signed_statement_1, + ) + .unwrap(); + + assert_eq!(core_index_1, CoreIndex(0)); + + let core_index_2 = core_index_from_statement( + &test_state.validator_to_group, + &test_state.validator_groups.1, + &test_state.availability_cores, + &signed_statement_2, + ); + + // Must be none, para_id in descriptor is different than para assigned to core + assert_eq!(core_index_2, None); + + let core_index_3 = core_index_from_statement( + &test_state.validator_to_group, + &test_state.validator_groups.1, + &test_state.availability_cores, + &signed_statement_3, + ) + .unwrap(); + + assert_eq!(core_index_3, CoreIndex(1)); +} + #[test] fn backing_works_while_validation_ongoing() { let test_state = TestState::default(); @@ -1422,7 +1540,7 @@ fn backing_works_after_failed_validation() { fn candidate_backing_reorders_votes() { use sp_core::Encode; - let para_id = ParaId::from(10); + let core_idx = CoreIndex(10); let validators = vec![ Sr25519Keyring::Alice, Sr25519Keyring::Bob, @@ -1436,7 +1554,7 @@ fn candidate_backing_reorders_votes() { let validator_groups = { let mut validator_groups = HashMap::new(); validator_groups - .insert(para_id, vec![0, 1, 2, 3, 4, 5].into_iter().map(ValidatorIndex).collect()); + .insert(core_idx, vec![0, 1, 2, 3, 4, 5].into_iter().map(ValidatorIndex).collect()); validator_groups }; @@ -1466,10 +1584,10 @@ fn candidate_backing_reorders_votes() { (ValidatorIndex(3), fake_attestation(3)), (ValidatorIndex(1), fake_attestation(1)), ], - group_id: para_id, + group_id: core_idx, }; - let backed = table_attested_to_backed(attested, &table_context).unwrap(); + let backed = table_attested_to_backed(attested, &table_context, false).unwrap(); let expected_bitvec = { let mut validator_indices = BitVec::::with_capacity(6); diff --git a/polkadot/node/core/backing/src/tests/prospective_parachains.rs b/polkadot/node/core/backing/src/tests/prospective_parachains.rs index 578f21bef66..94310d2aa16 100644 --- a/polkadot/node/core/backing/src/tests/prospective_parachains.rs +++ b/polkadot/node/core/backing/src/tests/prospective_parachains.rs @@ -185,6 +185,16 @@ async fn activate_leaf( } ); + // Node features request from runtime: all features are disabled. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(parent, RuntimeApiRequest::NodeFeatures(_session_index, tx)) + ) if parent == hash => { + tx.send(Ok(Default::default())).unwrap(); + } + ); + // Check if subsystem job issues a request for the minimum backing votes. assert_matches!( virtual_overseer.recv().await, @@ -305,10 +315,11 @@ async fn assert_hypothetical_frontier_requests( ) => { let idx = match expected_requests.iter().position(|r| r.0 == request) { Some(idx) => idx, - None => panic!( + None => + panic!( "unexpected hypothetical frontier request, no match found for {:?}", request - ), + ), }; let resp = std::mem::take(&mut expected_requests[idx].1); tx.send(resp).unwrap(); @@ -1268,6 +1279,7 @@ fn concurrent_dependent_candidates() { let statement_b = CandidateBackingMessage::Statement(leaf_parent, signed_b.clone()); virtual_overseer.send(FromOrchestra::Communication { msg: statement_a }).await; + // At this point the subsystem waits for response, the previous message is received, // send a second one without blocking. let _ = virtual_overseer @@ -1388,7 +1400,19 @@ fn concurrent_dependent_candidates() { assert_eq!(sess_idx, 1); tx.send(Ok(Some(ExecutorParams::default()))).unwrap(); }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _parent, + RuntimeApiRequest::ValidatorGroups(tx), + )) => { + tx.send(Ok(test_state.validator_groups.clone())).unwrap(); + }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _parent, + RuntimeApiRequest::AvailabilityCores(tx), + )) => { + tx.send(Ok(test_state.availability_cores.clone())).unwrap(); + }, _ => panic!("unexpected message received from overseer: {:?}", msg), } } @@ -1419,7 +1443,6 @@ fn seconding_sanity_check_occupy_same_depth() { let leaf_parent = get_parent_hash(leaf_hash); let activated = new_leaf(leaf_hash, LEAF_BLOCK_NUMBER); - let min_block_number = LEAF_BLOCK_NUMBER - LEAF_ANCESTRY_LEN; let min_relay_parents = vec![(para_id_a, min_block_number), (para_id_b, min_block_number)]; let test_leaf_a = TestLeaf { activated, min_relay_parents }; @@ -1555,13 +1578,14 @@ fn occupied_core_assignment() { const LEAF_A_BLOCK_NUMBER: BlockNumber = 100; const LEAF_A_ANCESTRY_LEN: BlockNumber = 3; let para_id = test_state.chain_ids[0]; + let previous_para_id = test_state.chain_ids[1]; // Set the core state to occupied. let mut candidate_descriptor = ::test_helpers::dummy_candidate_descriptor(Hash::zero()); - candidate_descriptor.para_id = para_id; + candidate_descriptor.para_id = previous_para_id; test_state.availability_cores[0] = CoreState::Occupied(OccupiedCore { group_responsible: Default::default(), - next_up_on_available: None, + next_up_on_available: Some(ScheduledCore { para_id, collator: None }), occupied_since: 100_u32, time_out_at: 200_u32, next_up_on_time_out: None, diff --git a/polkadot/node/core/provisioner/src/lib.rs b/polkadot/node/core/provisioner/src/lib.rs index 51f768d782e..d98f6ebfe42 100644 --- a/polkadot/node/core/provisioner/src/lib.rs +++ b/polkadot/node/core/provisioner/src/lib.rs @@ -681,10 +681,17 @@ async fn request_backable_candidates( CoreState::Free => continue, }; + // We should be calling this once per para rather than per core. + // TODO: Will be fixed in https://github.com/paritytech/polkadot-sdk/pull/3233. + // For now, at least make sure we don't supply the same candidate multiple times in case a + // para has multiple cores scheduled. let response = get_backable_candidate(relay_parent, para_id, required_path, sender).await?; - match response { - Some((hash, relay_parent)) => selected_candidates.push((hash, relay_parent)), + Some((hash, relay_parent)) => { + if !selected_candidates.iter().any(|bc| &(hash, relay_parent) == bc) { + selected_candidates.push((hash, relay_parent)) + } + }, None => { gum::debug!( target: LOG_TARGET, @@ -726,6 +733,7 @@ async fn select_candidates( ) .await?, }; + gum::debug!(target: LOG_TARGET, ?selected_candidates, "Got backable candidates"); // now get the backed candidates corresponding to these candidate receipts let (tx, rx) = oneshot::channel(); diff --git a/polkadot/primitives/Cargo.toml b/polkadot/primitives/Cargo.toml index 58d3d100161..e63fb621c78 100644 --- a/polkadot/primitives/Cargo.toml +++ b/polkadot/primitives/Cargo.toml @@ -14,6 +14,7 @@ bitvec = { version = "1.0.0", default-features = false, features = ["alloc", "se hex-literal = "0.4.1" parity-scale-codec = { version = "3.6.1", default-features = false, features = ["bit-vec", "derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["bit-vec", "derive", "serde"] } +log = { workspace = true, default-features = false } serde = { features = ["alloc", "derive"], workspace = true } application-crypto = { package = "sp-application-crypto", path = "../../substrate/primitives/application-crypto", default-features = false, features = ["serde"] } @@ -38,6 +39,7 @@ std = [ "application-crypto/std", "bitvec/std", "inherents/std", + "log/std", "parity-scale-codec/std", "polkadot-core-primitives/std", "polkadot-parachain-primitives/std", diff --git a/polkadot/primitives/src/v6/mod.rs b/polkadot/primitives/src/v6/mod.rs index 4938d20d2d1..538eb385584 100644 --- a/polkadot/primitives/src/v6/mod.rs +++ b/polkadot/primitives/src/v6/mod.rs @@ -72,6 +72,7 @@ pub use metrics::{ /// The key type ID for a collator key. pub const COLLATOR_KEY_TYPE_ID: KeyTypeId = KeyTypeId(*b"coll"); +const LOG_TARGET: &str = "runtime::primitives"; mod collator_app { use application_crypto::{app_crypto, sr25519}; @@ -746,17 +747,29 @@ impl BackedCandidate { /// /// Returns either an error, indicating that one of the signatures was invalid or that the index /// was out-of-bounds, or the number of signatures checked. -pub fn check_candidate_backing + Clone + Encode>( +pub fn check_candidate_backing + Clone + Encode + core::fmt::Debug>( backed: &BackedCandidate, signing_context: &SigningContext, group_len: usize, validator_lookup: impl Fn(usize) -> Option, ) -> Result { if backed.validator_indices.len() != group_len { + log::debug!( + target: LOG_TARGET, + "Check candidate backing: indices mismatch: group_len = {} , indices_len = {}", + group_len, + backed.validator_indices.len(), + ); return Err(()) } if backed.validity_votes.len() > group_len { + log::debug!( + target: LOG_TARGET, + "Check candidate backing: Too many votes, expected: {}, found: {}", + group_len, + backed.validity_votes.len(), + ); return Err(()) } @@ -778,11 +791,23 @@ pub fn check_candidate_backing + Clone + Encode>( if sig.verify(&payload[..], &validator_id) { signed += 1; } else { + log::debug!( + target: LOG_TARGET, + "Check candidate backing: Invalid signature. validator_id = {:?}, validator_index = {} ", + validator_id, + val_in_group_idx, + ); return Err(()) } } if signed != backed.validity_votes.len() { + log::error!( + target: LOG_TARGET, + "Check candidate backing: Too many signatures, expected = {}, found = {}", + backed.validity_votes.len() , + signed, + ); return Err(()) } diff --git a/polkadot/primitives/src/vstaging/mod.rs b/polkadot/primitives/src/vstaging/mod.rs index 630bcf8679a..39d9dfc02c5 100644 --- a/polkadot/primitives/src/vstaging/mod.rs +++ b/polkadot/primitives/src/vstaging/mod.rs @@ -64,9 +64,13 @@ pub mod node_features { /// Tells if tranch0 assignments could be sent in a single certificate. /// Reserved for: `` EnableAssignmentsV2 = 0, + /// This feature enables the extension of `BackedCandidate::validator_indices` by 8 bits. + /// The value stored there represents the assumed core index where the candidates + /// are backed. This is needed for the elastic scaling MVP. + ElasticScalingMVP = 1, /// First unassigned feature bit. /// Every time a new feature flag is assigned it should take this value. /// and this should be incremented. - FirstUnassigned = 1, + FirstUnassigned = 2, } } diff --git a/polkadot/statement-table/Cargo.toml b/polkadot/statement-table/Cargo.toml index 6403b822ed9..37b8a99d640 100644 --- a/polkadot/statement-table/Cargo.toml +++ b/polkadot/statement-table/Cargo.toml @@ -13,3 +13,4 @@ workspace = true parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } sp-core = { path = "../../substrate/primitives/core" } primitives = { package = "polkadot-primitives", path = "../primitives" } +gum = { package = "tracing-gum", path = "../node/gum" } diff --git a/polkadot/statement-table/src/generic.rs b/polkadot/statement-table/src/generic.rs index 22bffde5acc..2ee6f6a4f78 100644 --- a/polkadot/statement-table/src/generic.rs +++ b/polkadot/statement-table/src/generic.rs @@ -36,6 +36,7 @@ use primitives::{ }; use parity_scale_codec::{Decode, Encode}; +const LOG_TARGET: &str = "parachain::statement-table"; /// Context for the statement table. pub trait Context { @@ -53,9 +54,6 @@ pub trait Context { /// get the digest of a candidate. fn candidate_digest(candidate: &Self::Candidate) -> Self::Digest; - /// get the group of a candidate. - fn candidate_group(candidate: &Self::Candidate) -> Self::GroupId; - /// Whether a authority is a member of a group. /// Members are meant to submit candidates and vote on validity. fn is_member_of(&self, authority: &Self::AuthorityId, group: &Self::GroupId) -> bool; @@ -342,13 +340,13 @@ impl Table { pub fn import_statement( &mut self, context: &Ctx, + group_id: Ctx::GroupId, statement: SignedStatement, ) -> Option> { let SignedStatement { statement, signature, sender: signer } = statement; - let res = match statement { Statement::Seconded(candidate) => - self.import_candidate(context, signer.clone(), candidate, signature), + self.import_candidate(context, signer.clone(), candidate, signature, group_id), Statement::Valid(digest) => self.validity_vote(context, signer.clone(), digest, ValidityVote::Valid(signature)), }; @@ -387,9 +385,10 @@ impl Table { authority: Ctx::AuthorityId, candidate: Ctx::Candidate, signature: Ctx::Signature, + group: Ctx::GroupId, ) -> ImportResult { - let group = Ctx::candidate_group(&candidate); if !context.is_member_of(&authority, &group) { + gum::debug!(target: LOG_TARGET, authority = ?authority, group = ?group, "New `Misbehavior::UnauthorizedStatement`, candidate backed by validator that doesn't belong to expected group" ); return Err(Misbehavior::UnauthorizedStatement(UnauthorizedStatement { statement: SignedStatement { signature, @@ -634,10 +633,6 @@ mod tests { Digest(candidate.1) } - fn candidate_group(candidate: &Candidate) -> GroupId { - GroupId(candidate.0) - } - fn is_member_of(&self, authority: &AuthorityId, group: &GroupId) -> bool { self.authorities.get(authority).map(|v| v == group).unwrap_or(false) } @@ -675,10 +670,10 @@ mod tests { sender: AuthorityId(1), }; - table.import_statement(&context, statement_a); + table.import_statement(&context, GroupId(2), statement_a); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); - table.import_statement(&context, statement_b); + table.import_statement(&context, GroupId(2), statement_b); assert_eq!( table.detected_misbehavior[&AuthorityId(1)][0], Misbehavior::MultipleCandidates(MultipleCandidates { @@ -711,10 +706,10 @@ mod tests { sender: AuthorityId(1), }; - table.import_statement(&context, statement_a); + table.import_statement(&context, GroupId(2), statement_a); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); - table.import_statement(&context, statement_b); + table.import_statement(&context, GroupId(2), statement_b); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); } @@ -735,7 +730,7 @@ mod tests { sender: AuthorityId(1), }; - table.import_statement(&context, statement); + table.import_statement(&context, GroupId(2), statement); assert_eq!( table.detected_misbehavior[&AuthorityId(1)][0], @@ -769,7 +764,7 @@ mod tests { }; let candidate_a_digest = Digest(100); - table.import_statement(&context, candidate_a); + table.import_statement(&context, GroupId(2), candidate_a); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(2))); @@ -779,7 +774,7 @@ mod tests { signature: Signature(2), sender: AuthorityId(2), }; - table.import_statement(&context, bad_validity_vote); + table.import_statement(&context, GroupId(3), bad_validity_vote); assert_eq!( table.detected_misbehavior[&AuthorityId(2)][0], @@ -811,7 +806,7 @@ mod tests { sender: AuthorityId(1), }; - table.import_statement(&context, statement); + table.import_statement(&context, GroupId(2), statement); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); let invalid_statement = SignedStatement { @@ -820,7 +815,7 @@ mod tests { sender: AuthorityId(1), }; - table.import_statement(&context, invalid_statement); + table.import_statement(&context, GroupId(2), invalid_statement); assert!(table.detected_misbehavior.contains_key(&AuthorityId(1))); } @@ -842,7 +837,7 @@ mod tests { }; let candidate_digest = Digest(100); - table.import_statement(&context, statement); + table.import_statement(&context, GroupId(2), statement); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); let extra_vote = SignedStatement { @@ -851,7 +846,7 @@ mod tests { sender: AuthorityId(1), }; - table.import_statement(&context, extra_vote); + table.import_statement(&context, GroupId(2), extra_vote); assert_eq!( table.detected_misbehavior[&AuthorityId(1)][0], Misbehavior::ValidityDoubleVote(ValidityDoubleVote::IssuedAndValidity( @@ -910,7 +905,7 @@ mod tests { }; let candidate_digest = Digest(100); - table.import_statement(&context, statement); + table.import_statement(&context, GroupId(2), statement); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); assert!(table.attested_candidate(&candidate_digest, &context, 2).is_none()); @@ -921,7 +916,7 @@ mod tests { sender: AuthorityId(2), }; - table.import_statement(&context, vote); + table.import_statement(&context, GroupId(2), vote); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(2))); assert!(table.attested_candidate(&candidate_digest, &context, 2).is_some()); } @@ -944,7 +939,7 @@ mod tests { }; let summary = table - .import_statement(&context, statement) + .import_statement(&context, GroupId(2), statement) .expect("candidate import to give summary"); assert_eq!(summary.candidate, Digest(100)); @@ -971,7 +966,7 @@ mod tests { }; let candidate_digest = Digest(100); - table.import_statement(&context, statement); + table.import_statement(&context, GroupId(2), statement); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); let vote = SignedStatement { @@ -980,8 +975,9 @@ mod tests { sender: AuthorityId(2), }; - let summary = - table.import_statement(&context, vote).expect("candidate vote to give summary"); + let summary = table + .import_statement(&context, GroupId(2), vote) + .expect("candidate vote to give summary"); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(2))); diff --git a/polkadot/statement-table/src/lib.rs b/polkadot/statement-table/src/lib.rs index d4629330ac0..3740d15cc4f 100644 --- a/polkadot/statement-table/src/lib.rs +++ b/polkadot/statement-table/src/lib.rs @@ -35,8 +35,8 @@ pub use generic::{Config, Context, Table}; pub mod v2 { use crate::generic; use primitives::{ - CandidateHash, CommittedCandidateReceipt, CompactStatement as PrimitiveStatement, Id, - ValidatorIndex, ValidatorSignature, + CandidateHash, CommittedCandidateReceipt, CompactStatement as PrimitiveStatement, + CoreIndex, ValidatorIndex, ValidatorSignature, }; /// Statements about candidates on the network. @@ -59,7 +59,7 @@ pub mod v2 { >; /// A summary of import of a statement. - pub type Summary = generic::Summary; + pub type Summary = generic::Summary; impl<'a> From<&'a Statement> for PrimitiveStatement { fn from(s: &'a Statement) -> PrimitiveStatement { -- GitLab From 61ecef444fbd9e1eb4138123f35ecba32fe77565 Mon Sep 17 00:00:00 2001 From: Egor_P Date: Thu, 22 Feb 2024 16:48:52 +0700 Subject: [PATCH 111/133] Add matrix public channels for release announcements (#3430) This PR adds two more public channels in matrix where should the release announcements go --- .github/workflows/release-99_notif-published.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/release-99_notif-published.yml b/.github/workflows/release-99_notif-published.yml index 397795fafa4..732db15d9c0 100644 --- a/.github/workflows/release-99_notif-published.yml +++ b/.github/workflows/release-99_notif-published.yml @@ -32,6 +32,12 @@ jobs: - name: '#polkadotvalidatorlounge:web3.foundation' room: '!NZrbtteFeqYKCUGQtr:matrix.parity.io' pre-releases: false + - name: '#polkadot-announcements:parity.io' + room: '!UqHPWiCBGZWxrmYBkF:matrix.parity.io' + pre-releases: false + - name: '#kusama-announce:parity.io' + room: '!FMwxpQnYhRCNDRsYGI:matrix.parity.io' + pre-releases: false steps: - name: Matrix notification to ${{ matrix.channel.name }} -- GitLab From 822082807fd6f146cd1c0561dc340dedab463c40 Mon Sep 17 00:00:00 2001 From: Koute Date: Thu, 22 Feb 2024 19:03:12 +0900 Subject: [PATCH 112/133] Fix `wasm-builder` not exiting if compilation fails (#3439) This PR fixes a subtle bug in `wasm-builder` first introduced in https://github.com/paritytech/polkadot-sdk/pull/1851 (sorry, my bad! I should have caught this during review) where the status code of the `cargo` subprocess is not properly checked, which results in builds silently succeeding when they shouldn't (that is: if we successfully build a runtime blob, and then modify the code so that it won't compile, and recompile it again, then the build will succeed and silently use the *old* blob). cc @athei This is the bug you were seeing. [edit]Also fixes a similar PolkaVM-specific bug where I accidentally used the wrong comparison operator.[/edit] --- substrate/utils/wasm-builder/src/wasm_project.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/substrate/utils/wasm-builder/src/wasm_project.rs b/substrate/utils/wasm-builder/src/wasm_project.rs index bc8c9b3b4b3..9ffb5c72fd9 100644 --- a/substrate/utils/wasm-builder/src/wasm_project.rs +++ b/substrate/utils/wasm-builder/src/wasm_project.rs @@ -855,7 +855,7 @@ fn build_bloaty_blob( println!("{} {}", colorize_info_message("Using rustc version:"), cargo_cmd.rustc_version()); // Use `process::exit(1)` to have a clean error output. - if build_cmd.status().map(|s| s.success()).is_err() { + if !matches!(build_cmd.status().map(|s| s.success()), Ok(true)) { process::exit(1); } @@ -877,7 +877,7 @@ fn build_bloaty_blob( if polkavm_path .metadata() .map(|polkavm_metadata| { - polkavm_metadata.modified().unwrap() >= elf_metadata.modified().unwrap() + polkavm_metadata.modified().unwrap() < elf_metadata.modified().unwrap() }) .unwrap_or(true) { -- GitLab From 31546c8d248573e9af33836883d414057a0cf4c9 Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Thu, 22 Feb 2024 13:44:41 +0200 Subject: [PATCH 113/133] sc-consensus-beefy: pump gossip engine while waiting for initialization conditions (#3435) As part of BEEFY worker/voter initialization the task waits for certain chain and backend conditions to be fulfilled: - BEEFY consensus enabled on-chain & GRANDPA best finalized higher than on-chain BEEFY genesis block, - backend has synced headers for BEEFY mandatory blocks between best BEEFY and best GRANDPA. During this waiting time, any messages gossiped on the BEEFY topic for current chain get enqueued in the gossip engine, leading to RAM bloating and output warning/error messages when the wait time is non-negligible (like during a clean sync). This PR adds logic to pump the gossip engine while waiting for other things to make sure gossiped messages get consumed (practically discarded until worker is fully initialized). Also raises the warning threshold for enqueued messages from 10k to 100k. This is in line with the other gossip protocols on the node. Fixes https://github.com/paritytech/polkadot-sdk/issues/3390 --------- Signed-off-by: Adrian Catangiu --- prdoc/pr_3435.prdoc | 16 + .../beefy/src/communication/gossip.rs | 35 +- .../consensus/beefy/src/communication/mod.rs | 2 - .../client/consensus/beefy/src/import.rs | 2 +- substrate/client/consensus/beefy/src/lib.rs | 414 +++++++++++++---- substrate/client/consensus/beefy/src/tests.rs | 116 +---- .../client/consensus/beefy/src/worker.rs | 419 +++++------------- .../consensus/beefy/src/commitment.rs | 21 + 8 files changed, 520 insertions(+), 505 deletions(-) create mode 100644 prdoc/pr_3435.prdoc diff --git a/prdoc/pr_3435.prdoc b/prdoc/pr_3435.prdoc new file mode 100644 index 00000000000..7d7896bff7d --- /dev/null +++ b/prdoc/pr_3435.prdoc @@ -0,0 +1,16 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Fix BEEFY-related gossip messages error logs + +doc: + - audience: Node Operator + description: | + Added logic to pump the gossip engine while waiting for other things + to make sure gossiped messages get consumed (practically discarded + until worker is fully initialized). + This fixes an issue where node operators saw error logs, and fixes + potential RAM bloat when BEEFY initialization takes a long time + (i.e. during clean sync). +crates: + - name: sc-consensus-beefy diff --git a/substrate/client/consensus/beefy/src/communication/gossip.rs b/substrate/client/consensus/beefy/src/communication/gossip.rs index 8a0b0a74308..eb43c9173d7 100644 --- a/substrate/client/consensus/beefy/src/communication/gossip.rs +++ b/substrate/client/consensus/beefy/src/communication/gossip.rs @@ -56,6 +56,8 @@ pub(super) enum Action { Keep(H, ReputationChange), // discard, applying cost/benefit to originator. Discard(ReputationChange), + // ignore, no cost/benefit applied to originator. + DiscardNoReport, } /// An outcome of examining a message. @@ -68,7 +70,7 @@ enum Consider { /// Message is from the future. Reject. RejectFuture, /// Message cannot be evaluated. Reject. - RejectOutOfScope, + CannotEvaluate, } /// BEEFY gossip message type that gets encoded and sent on the network. @@ -168,18 +170,14 @@ impl Filter { .as_ref() .map(|f| // only from current set and only [filter.start, filter.end] - if set_id < f.validator_set.id() { + if set_id < f.validator_set.id() || round < f.start { Consider::RejectPast - } else if set_id > f.validator_set.id() { - Consider::RejectFuture - } else if round < f.start { - Consider::RejectPast - } else if round > f.end { + } else if set_id > f.validator_set.id() || round > f.end { Consider::RejectFuture } else { Consider::Accept }) - .unwrap_or(Consider::RejectOutOfScope) + .unwrap_or(Consider::CannotEvaluate) } /// Return true if `round` is >= than `max(session_start, best_beefy)`, @@ -199,7 +197,7 @@ impl Filter { Consider::Accept } ) - .unwrap_or(Consider::RejectOutOfScope) + .unwrap_or(Consider::CannotEvaluate) } /// Add new _known_ `round` to the set of seen valid justifications. @@ -244,7 +242,7 @@ where pub(crate) fn new( known_peers: Arc>>, ) -> (GossipValidator, TracingUnboundedReceiver) { - let (tx, rx) = tracing_unbounded("mpsc_beefy_gossip_validator", 10_000); + let (tx, rx) = tracing_unbounded("mpsc_beefy_gossip_validator", 100_000); let val = GossipValidator { votes_topic: votes_topic::(), justifs_topic: proofs_topic::(), @@ -289,7 +287,9 @@ where match filter.consider_vote(round, set_id) { Consider::RejectPast => return Action::Discard(cost::OUTDATED_MESSAGE), Consider::RejectFuture => return Action::Discard(cost::FUTURE_MESSAGE), - Consider::RejectOutOfScope => return Action::Discard(cost::OUT_OF_SCOPE_MESSAGE), + // When we can't evaluate, it's our fault (e.g. filter not initialized yet), we + // discard the vote without punishing or rewarding the sending peer. + Consider::CannotEvaluate => return Action::DiscardNoReport, Consider::Accept => {}, } @@ -330,7 +330,9 @@ where match guard.consider_finality_proof(round, set_id) { Consider::RejectPast => return Action::Discard(cost::OUTDATED_MESSAGE), Consider::RejectFuture => return Action::Discard(cost::FUTURE_MESSAGE), - Consider::RejectOutOfScope => return Action::Discard(cost::OUT_OF_SCOPE_MESSAGE), + // When we can't evaluate, it's our fault (e.g. filter not initialized yet), we + // discard the proof without punishing or rewarding the sending peer. + Consider::CannotEvaluate => return Action::DiscardNoReport, Consider::Accept => {}, } @@ -357,7 +359,9 @@ where Action::Keep(self.justifs_topic, benefit::VALIDATED_PROOF) } }) - .unwrap_or(Action::Discard(cost::OUT_OF_SCOPE_MESSAGE)) + // When we can't evaluate, it's our fault (e.g. filter not initialized yet), we + // discard the proof without punishing or rewarding the sending peer. + .unwrap_or(Action::DiscardNoReport) }; if matches!(action, Action::Keep(_, _)) { self.gossip_filter.write().mark_round_as_proven(round); @@ -404,6 +408,7 @@ where self.report(*sender, cb); ValidationResult::Discard }, + Action::DiscardNoReport => ValidationResult::Discard, } } @@ -579,8 +584,8 @@ pub(crate) mod tests { // filter not initialized let res = gv.validate(&mut context, &sender, &encoded); assert!(matches!(res, ValidationResult::Discard)); - expected_report.cost_benefit = cost::OUT_OF_SCOPE_MESSAGE; - assert_eq!(report_stream.try_recv().unwrap(), expected_report); + // nothing reported + assert!(report_stream.try_recv().is_err()); gv.update_filter(GossipFilterCfg { start: 0, end: 10, validator_set: &validator_set }); // nothing in cache first time diff --git a/substrate/client/consensus/beefy/src/communication/mod.rs b/substrate/client/consensus/beefy/src/communication/mod.rs index 3827559057d..6fda63688e6 100644 --- a/substrate/client/consensus/beefy/src/communication/mod.rs +++ b/substrate/client/consensus/beefy/src/communication/mod.rs @@ -90,8 +90,6 @@ mod cost { pub(super) const BAD_SIGNATURE: Rep = Rep::new(-100, "BEEFY: Bad signature"); // Message received with vote from voter not in validator set. pub(super) const UNKNOWN_VOTER: Rep = Rep::new(-150, "BEEFY: Unknown voter"); - // A message received that cannot be evaluated relative to our current state. - pub(super) const OUT_OF_SCOPE_MESSAGE: Rep = Rep::new(-500, "BEEFY: Out-of-scope message"); // Message containing invalid proof. pub(super) const INVALID_PROOF: Rep = Rep::new(-5000, "BEEFY: Invalid commit"); // Reputation cost per signature checked for invalid proof. diff --git a/substrate/client/consensus/beefy/src/import.rs b/substrate/client/consensus/beefy/src/import.rs index fc19ecc3014..ed8ed68c4e8 100644 --- a/substrate/client/consensus/beefy/src/import.rs +++ b/substrate/client/consensus/beefy/src/import.rs @@ -159,7 +159,7 @@ where // The proof is valid and the block is imported and final, we can import. debug!( target: LOG_TARGET, - "🥩 import justif {:?} for block number {:?}.", proof, number + "🥩 import justif {} for block number {:?}.", proof, number ); // Send the justification to the BEEFY voter for processing. self.justification_sender diff --git a/substrate/client/consensus/beefy/src/lib.rs b/substrate/client/consensus/beefy/src/lib.rs index 11a105561e4..323af1bc830 100644 --- a/substrate/client/consensus/beefy/src/lib.rs +++ b/substrate/client/consensus/beefy/src/lib.rs @@ -31,7 +31,7 @@ use crate::{ import::BeefyBlockImport, metrics::register_metrics, }; -use futures::{stream::Fuse, StreamExt}; +use futures::{stream::Fuse, FutureExt, StreamExt}; use log::{debug, error, info, warn}; use parking_lot::Mutex; use prometheus::Registry; @@ -40,17 +40,21 @@ use sc_consensus::BlockImport; use sc_network::{NetworkRequest, NotificationService, ProtocolName}; use sc_network_gossip::{GossipEngine, Network as GossipNetwork, Syncing as GossipSyncing}; use sp_api::ProvideRuntimeApi; -use sp_blockchain::{ - Backend as BlockchainBackend, Error as ClientError, HeaderBackend, Result as ClientResult, -}; +use sp_blockchain::{Backend as BlockchainBackend, HeaderBackend}; use sp_consensus::{Error as ConsensusError, SyncOracle}; use sp_consensus_beefy::{ - ecdsa_crypto::AuthorityId, BeefyApi, MmrRootHash, PayloadProvider, ValidatorSet, + ecdsa_crypto::AuthorityId, BeefyApi, ConsensusLog, MmrRootHash, PayloadProvider, ValidatorSet, + BEEFY_ENGINE_ID, }; use sp_keystore::KeystorePtr; use sp_mmr_primitives::MmrApi; use sp_runtime::traits::{Block, Header as HeaderT, NumberFor, Zero}; -use std::{collections::BTreeMap, marker::PhantomData, sync::Arc, time::Duration}; +use std::{ + collections::{BTreeMap, VecDeque}, + marker::PhantomData, + sync::Arc, + time::Duration, +}; mod aux_schema; mod error; @@ -63,9 +67,19 @@ pub mod communication; pub mod import; pub mod justification; +use crate::{ + communication::{gossip::GossipValidator, peers::PeerReport}, + justification::BeefyVersionedFinalityProof, + keystore::BeefyKeystore, + metrics::VoterMetrics, + round::Rounds, + worker::{BeefyWorker, PersistedState}, +}; pub use communication::beefy_protocol_name::{ gossip_protocol_name, justifications_protocol_name as justifs_protocol_name, }; +use sc_utils::mpsc::TracingUnboundedReceiver; +use sp_runtime::generic::OpaqueDigestItemId; #[cfg(test)] mod tests; @@ -209,6 +223,247 @@ pub struct BeefyParams { /// Handler for incoming BEEFY justifications requests from a remote peer. pub on_demand_justifications_handler: BeefyJustifsRequestHandler, } +/// Helper object holding BEEFY worker communication/gossip components. +/// +/// These are created once, but will be reused if worker is restarted/reinitialized. +pub(crate) struct BeefyComms { + pub gossip_engine: GossipEngine, + pub gossip_validator: Arc>, + pub gossip_report_stream: TracingUnboundedReceiver, + pub on_demand_justifications: OnDemandJustificationsEngine, +} + +/// Helper builder object for building [worker::BeefyWorker]. +/// +/// It has to do it in two steps: initialization and build, because the first step can sleep waiting +/// for certain chain and backend conditions, and while sleeping we still need to pump the +/// GossipEngine. Once initialization is done, the GossipEngine (and other pieces) are added to get +/// the complete [worker::BeefyWorker] object. +pub(crate) struct BeefyWorkerBuilder { + // utilities + backend: Arc, + runtime: Arc, + key_store: BeefyKeystore, + // voter metrics + metrics: Option, + persisted_state: PersistedState, +} + +impl BeefyWorkerBuilder +where + B: Block + codec::Codec, + BE: Backend, + R: ProvideRuntimeApi, + R::Api: BeefyApi, +{ + /// This will wait for the chain to enable BEEFY (if not yet enabled) and also wait for the + /// backend to sync all headers required by the voter to build a contiguous chain of mandatory + /// justifications. Then it builds the initial voter state using a combination of previously + /// persisted state in AUX DB and latest chain information/progress. + /// + /// Returns a sane `BeefyWorkerBuilder` that can build the `BeefyWorker`. + pub async fn async_initialize( + backend: Arc, + runtime: Arc, + key_store: BeefyKeystore, + metrics: Option, + min_block_delta: u32, + gossip_validator: Arc>, + finality_notifications: &mut Fuse>, + ) -> Result { + // Wait for BEEFY pallet to be active before starting voter. + let (beefy_genesis, best_grandpa) = + wait_for_runtime_pallet(&*runtime, finality_notifications).await?; + + let persisted_state = Self::load_or_init_state( + beefy_genesis, + best_grandpa, + min_block_delta, + backend.clone(), + runtime.clone(), + &key_store, + &metrics, + ) + .await?; + // Update the gossip validator with the right starting round and set id. + persisted_state + .gossip_filter_config() + .map(|f| gossip_validator.update_filter(f))?; + + Ok(BeefyWorkerBuilder { backend, runtime, key_store, metrics, persisted_state }) + } + + /// Takes rest of missing pieces as params and builds the `BeefyWorker`. + pub fn build( + self, + payload_provider: P, + sync: Arc, + comms: BeefyComms, + links: BeefyVoterLinks, + pending_justifications: BTreeMap, BeefyVersionedFinalityProof>, + ) -> BeefyWorker { + BeefyWorker { + backend: self.backend, + runtime: self.runtime, + key_store: self.key_store, + metrics: self.metrics, + persisted_state: self.persisted_state, + payload_provider, + sync, + comms, + links, + pending_justifications, + } + } + + // If no persisted state present, walk back the chain from first GRANDPA notification to either: + // - latest BEEFY finalized block, or if none found on the way, + // - BEEFY pallet genesis; + // Enqueue any BEEFY mandatory blocks (session boundaries) found on the way, for voter to + // finalize. + async fn init_state( + beefy_genesis: NumberFor, + best_grandpa: ::Header, + min_block_delta: u32, + backend: Arc, + runtime: Arc, + ) -> Result, Error> { + let blockchain = backend.blockchain(); + + let beefy_genesis = runtime + .runtime_api() + .beefy_genesis(best_grandpa.hash()) + .ok() + .flatten() + .filter(|genesis| *genesis == beefy_genesis) + .ok_or_else(|| Error::Backend("BEEFY pallet expected to be active.".into()))?; + // Walk back the imported blocks and initialize voter either, at the last block with + // a BEEFY justification, or at pallet genesis block; voter will resume from there. + let mut sessions = VecDeque::new(); + let mut header = best_grandpa.clone(); + let state = loop { + if let Some(true) = blockchain + .justifications(header.hash()) + .ok() + .flatten() + .map(|justifs| justifs.get(BEEFY_ENGINE_ID).is_some()) + { + debug!( + target: LOG_TARGET, + "🥩 Initialize BEEFY voter at last BEEFY finalized block: {:?}.", + *header.number() + ); + let best_beefy = *header.number(); + // If no session boundaries detected so far, just initialize new rounds here. + if sessions.is_empty() { + let active_set = + expect_validator_set(runtime.as_ref(), backend.as_ref(), &header).await?; + let mut rounds = Rounds::new(best_beefy, active_set); + // Mark the round as already finalized. + rounds.conclude(best_beefy); + sessions.push_front(rounds); + } + let state = PersistedState::checked_new( + best_grandpa, + best_beefy, + sessions, + min_block_delta, + beefy_genesis, + ) + .ok_or_else(|| Error::Backend("Invalid BEEFY chain".into()))?; + break state + } + + if *header.number() == beefy_genesis { + // We've reached BEEFY genesis, initialize voter here. + let genesis_set = + expect_validator_set(runtime.as_ref(), backend.as_ref(), &header).await?; + info!( + target: LOG_TARGET, + "🥩 Loading BEEFY voter state from genesis on what appears to be first startup. \ + Starting voting rounds at block {:?}, genesis validator set {:?}.", + beefy_genesis, + genesis_set, + ); + + sessions.push_front(Rounds::new(beefy_genesis, genesis_set)); + break PersistedState::checked_new( + best_grandpa, + Zero::zero(), + sessions, + min_block_delta, + beefy_genesis, + ) + .ok_or_else(|| Error::Backend("Invalid BEEFY chain".into()))? + } + + if let Some(active) = find_authorities_change::(&header) { + debug!( + target: LOG_TARGET, + "🥩 Marking block {:?} as BEEFY Mandatory.", + *header.number() + ); + sessions.push_front(Rounds::new(*header.number(), active)); + } + + // Move up the chain. + header = wait_for_parent_header(blockchain, header, HEADER_SYNC_DELAY).await?; + }; + + aux_schema::write_current_version(backend.as_ref())?; + aux_schema::write_voter_state(backend.as_ref(), &state)?; + Ok(state) + } + + async fn load_or_init_state( + beefy_genesis: NumberFor, + best_grandpa: ::Header, + min_block_delta: u32, + backend: Arc, + runtime: Arc, + key_store: &BeefyKeystore, + metrics: &Option, + ) -> Result, Error> { + // Initialize voter state from AUX DB if compatible. + if let Some(mut state) = crate::aux_schema::load_persistent(backend.as_ref())? + // Verify state pallet genesis matches runtime. + .filter(|state| state.pallet_genesis() == beefy_genesis) + { + // Overwrite persisted state with current best GRANDPA block. + state.set_best_grandpa(best_grandpa.clone()); + // Overwrite persisted data with newly provided `min_block_delta`. + state.set_min_block_delta(min_block_delta); + debug!(target: LOG_TARGET, "🥩 Loading BEEFY voter state from db: {:?}.", state); + + // Make sure that all the headers that we need have been synced. + let mut new_sessions = vec![]; + let mut header = best_grandpa.clone(); + while *header.number() > state.best_beefy() { + if state.voting_oracle().can_add_session(*header.number()) { + if let Some(active) = find_authorities_change::(&header) { + new_sessions.push((active, *header.number())); + } + } + header = + wait_for_parent_header(backend.blockchain(), header, HEADER_SYNC_DELAY).await?; + } + + // Make sure we didn't miss any sessions during node restart. + for (validator_set, new_session_start) in new_sessions.drain(..).rev() { + debug!( + target: LOG_TARGET, + "🥩 Handling missed BEEFY session after node restart: {:?}.", + new_session_start + ); + state.init_session_at(new_session_start, validator_set, key_store, metrics); + } + return Ok(state) + } + + // No valid voter-state persisted, re-initialize from pallet genesis. + Self::init_state(beefy_genesis, best_grandpa, min_block_delta, backend, runtime).await + } +} /// Start the BEEFY gadget. /// @@ -277,7 +532,7 @@ pub async fn start_beefy_gadget( known_peers, prometheus_registry.clone(), ); - let mut beefy_comms = worker::BeefyComms { + let mut beefy_comms = BeefyComms { gossip_engine, gossip_validator, gossip_report_stream, @@ -287,57 +542,45 @@ pub async fn start_beefy_gadget( // We re-create and re-run the worker in this loop in order to quickly reinit and resume after // select recoverable errors. loop { - // Wait for BEEFY pallet to be active before starting voter. - let (beefy_genesis, best_grandpa) = match wait_for_runtime_pallet( - &*runtime, - &mut beefy_comms.gossip_engine, - &mut finality_notifications, - ) - .await - { - Ok(res) => res, - Err(e) => { - error!(target: LOG_TARGET, "Error: {:?}. Terminating.", e); - return - }, - }; - - let mut worker_base = worker::BeefyWorkerBase { - backend: backend.clone(), - runtime: runtime.clone(), - key_store: key_store.clone().into(), - metrics: metrics.clone(), - _phantom: Default::default(), - }; - - let persisted_state = match worker_base - .load_or_init_state(beefy_genesis, best_grandpa, min_block_delta) - .await - { - Ok(state) => state, - Err(e) => { - error!(target: LOG_TARGET, "Error: {:?}. Terminating.", e); - return - }, + // Make sure to pump gossip engine while waiting for initialization conditions. + let worker_builder = loop { + futures::select! { + builder_init_result = BeefyWorkerBuilder::async_initialize( + backend.clone(), + runtime.clone(), + key_store.clone().into(), + metrics.clone(), + min_block_delta, + beefy_comms.gossip_validator.clone(), + &mut finality_notifications, + ).fuse() => { + match builder_init_result { + Ok(builder) => break builder, + Err(e) => { + error!(target: LOG_TARGET, "🥩 Error: {:?}. Terminating.", e); + return + }, + } + }, + // Pump peer reports + _ = &mut beefy_comms.gossip_report_stream.next() => { + continue + }, + // Pump gossip engine. + _ = &mut beefy_comms.gossip_engine => { + error!(target: LOG_TARGET, "🥩 Gossip engine has unexpectedly terminated."); + return + } + } }; - // Update the gossip validator with the right starting round and set id. - if let Err(e) = persisted_state - .gossip_filter_config() - .map(|f| beefy_comms.gossip_validator.update_filter(f)) - { - error!(target: LOG_TARGET, "Error: {:?}. Terminating.", e); - return - } - let worker = worker::BeefyWorker { - base: worker_base, - payload_provider: payload_provider.clone(), - sync: sync.clone(), - comms: beefy_comms, - links: links.clone(), - pending_justifications: BTreeMap::new(), - persisted_state, - }; + let worker = worker_builder.build( + payload_provider.clone(), + sync.clone(), + beefy_comms, + links.clone(), + BTreeMap::new(), + ); match futures::future::select( Box::pin(worker.run(&mut block_import_justif, &mut finality_notifications)), @@ -404,9 +647,8 @@ where /// Should be called only once during worker initialization. async fn wait_for_runtime_pallet( runtime: &R, - mut gossip_engine: &mut GossipEngine, finality: &mut Fuse>, -) -> ClientResult<(NumberFor, ::Header)> +) -> Result<(NumberFor, ::Header), Error> where B: Block, R: ProvideRuntimeApi, @@ -414,33 +656,24 @@ where { info!(target: LOG_TARGET, "🥩 BEEFY gadget waiting for BEEFY pallet to become available..."); loop { - futures::select! { - notif = finality.next() => { - let notif = match notif { - Some(notif) => notif, - None => break - }; - let at = notif.header.hash(); - if let Some(start) = runtime.runtime_api().beefy_genesis(at).ok().flatten() { - if *notif.header.number() >= start { - // Beefy pallet available, return header for best grandpa at the time. - info!( - target: LOG_TARGET, - "🥩 BEEFY pallet available: block {:?} beefy genesis {:?}", - notif.header.number(), start - ); - return Ok((start, notif.header)) - } - } - }, - _ = gossip_engine => { - break + let notif = finality.next().await.ok_or_else(|| { + let err_msg = "🥩 Finality stream has unexpectedly terminated.".into(); + error!(target: LOG_TARGET, "{}", err_msg); + Error::Backend(err_msg) + })?; + let at = notif.header.hash(); + if let Some(start) = runtime.runtime_api().beefy_genesis(at).ok().flatten() { + if *notif.header.number() >= start { + // Beefy pallet available, return header for best grandpa at the time. + info!( + target: LOG_TARGET, + "🥩 BEEFY pallet available: block {:?} beefy genesis {:?}", + notif.header.number(), start + ); + return Ok((start, notif.header)) } } } - let err_msg = "🥩 Gossip engine has unexpectedly terminated.".into(); - error!(target: LOG_TARGET, "{}", err_msg); - Err(ClientError::Backend(err_msg)) } /// Provides validator set active `at_header`. It tries to get it from state, otherwise falls @@ -474,7 +707,7 @@ where if let Ok(Some(active)) = runtime.runtime_api().validator_set(header.hash()) { return Ok(active) } else { - match worker::find_authorities_change::(&header) { + match find_authorities_change::(&header) { Some(active) => return Ok(active), // Move up the chain. Ultimately we'll get it from chain genesis state, or error out // there. @@ -486,3 +719,18 @@ where } } } + +/// Scan the `header` digest log for a BEEFY validator set change. Return either the new +/// validator set or `None` in case no validator set change has been signaled. +pub(crate) fn find_authorities_change(header: &B::Header) -> Option> +where + B: Block, +{ + let id = OpaqueDigestItemId::Consensus(&BEEFY_ENGINE_ID); + + let filter = |log: ConsensusLog| match log { + ConsensusLog::AuthoritiesChange(validator_set) => Some(validator_set), + _ => None, + }; + header.digest().convert_first(|l| l.try_to(id).and_then(filter)) +} diff --git a/substrate/client/consensus/beefy/src/tests.rs b/substrate/client/consensus/beefy/src/tests.rs index 0e6ff210b15..d106c9dcd88 100644 --- a/substrate/client/consensus/beefy/src/tests.rs +++ b/substrate/client/consensus/beefy/src/tests.rs @@ -32,8 +32,8 @@ use crate::{ gossip_protocol_name, justification::*, wait_for_runtime_pallet, - worker::{BeefyWorkerBase, PersistedState}, - BeefyRPCLinks, BeefyVoterLinks, KnownPeers, + worker::PersistedState, + BeefyRPCLinks, BeefyVoterLinks, BeefyWorkerBuilder, KnownPeers, }; use futures::{future, stream::FuturesUnordered, Future, FutureExt, StreamExt}; use parking_lot::Mutex; @@ -368,27 +368,19 @@ async fn voter_init_setup( api: &TestApi, ) -> Result, Error> { let backend = net.peer(0).client().as_backend(); - let known_peers = Arc::new(Mutex::new(KnownPeers::new())); - let (gossip_validator, _) = GossipValidator::new(known_peers); - let gossip_validator = Arc::new(gossip_validator); - let mut gossip_engine = sc_network_gossip::GossipEngine::new( - net.peer(0).network_service().clone(), - net.peer(0).sync_service().clone(), - net.peer(0).take_notification_service(&beefy_gossip_proto_name()).unwrap(), - "/beefy/whatever", - gossip_validator, - None, - ); - let (beefy_genesis, best_grandpa) = - wait_for_runtime_pallet(api, &mut gossip_engine, finality).await.unwrap(); - let mut worker_base = BeefyWorkerBase { + let (beefy_genesis, best_grandpa) = wait_for_runtime_pallet(api, finality).await.unwrap(); + let key_store = None.into(); + let metrics = None; + BeefyWorkerBuilder::load_or_init_state( + beefy_genesis, + best_grandpa, + 1, backend, - runtime: Arc::new(api.clone()), - key_store: None.into(), - metrics: None, - _phantom: Default::default(), - }; - worker_base.load_or_init_state(beefy_genesis, best_grandpa, 1).await + Arc::new(api.clone()), + &key_store, + &metrics, + ) + .await } // Spawns beefy voters. Returns a future to spawn on the runtime. @@ -1065,32 +1057,7 @@ async fn should_initialize_voter_at_custom_genesis() { net.peer(0).client().as_client().finalize_block(hashes[8], None).unwrap(); // load persistent state - nothing in DB, should init at genesis - // - // NOTE: code from `voter_init_setup()` is moved here because the new network event system - // doesn't allow creating a new `GossipEngine` as the notification handle is consumed by the - // first `GossipEngine` - let known_peers = Arc::new(Mutex::new(KnownPeers::new())); - let (gossip_validator, _) = GossipValidator::new(known_peers); - let gossip_validator = Arc::new(gossip_validator); - let mut gossip_engine = sc_network_gossip::GossipEngine::new( - net.peer(0).network_service().clone(), - net.peer(0).sync_service().clone(), - net.peer(0).take_notification_service(&beefy_gossip_proto_name()).unwrap(), - "/beefy/whatever", - gossip_validator, - None, - ); - let (beefy_genesis, best_grandpa) = - wait_for_runtime_pallet(&api, &mut gossip_engine, &mut finality).await.unwrap(); - let mut worker_base = BeefyWorkerBase { - backend: backend.clone(), - runtime: Arc::new(api), - key_store: None.into(), - metrics: None, - _phantom: Default::default(), - }; - let persisted_state = - worker_base.load_or_init_state(beefy_genesis, best_grandpa, 1).await.unwrap(); + let persisted_state = voter_init_setup(&mut net, &mut finality, &api).await.unwrap(); // Test initialization at session boundary. // verify voter initialized with single session starting at block `custom_pallet_genesis` (7) @@ -1120,18 +1087,7 @@ async fn should_initialize_voter_at_custom_genesis() { net.peer(0).client().as_client().finalize_block(hashes[10], None).unwrap(); // load persistent state - state preset in DB, but with different pallet genesis - // the network state persists and uses the old `GossipEngine` initialized for `peer(0)` - let (beefy_genesis, best_grandpa) = - wait_for_runtime_pallet(&api, &mut gossip_engine, &mut finality).await.unwrap(); - let mut worker_base = BeefyWorkerBase { - backend: backend.clone(), - runtime: Arc::new(api), - key_store: None.into(), - metrics: None, - _phantom: Default::default(), - }; - let new_persisted_state = - worker_base.load_or_init_state(beefy_genesis, best_grandpa, 1).await.unwrap(); + let new_persisted_state = voter_init_setup(&mut net, &mut finality, &api).await.unwrap(); // verify voter initialized with single session starting at block `new_pallet_genesis` (10) let sessions = new_persisted_state.voting_oracle().sessions(); @@ -1322,32 +1278,7 @@ async fn should_catch_up_when_loading_saved_voter_state() { let api = TestApi::with_validator_set(&validator_set); // load persistent state - nothing in DB, should init at genesis - // - // NOTE: code from `voter_init_setup()` is moved here because the new network event system - // doesn't allow creating a new `GossipEngine` as the notification handle is consumed by the - // first `GossipEngine` - let known_peers = Arc::new(Mutex::new(KnownPeers::new())); - let (gossip_validator, _) = GossipValidator::new(known_peers); - let gossip_validator = Arc::new(gossip_validator); - let mut gossip_engine = sc_network_gossip::GossipEngine::new( - net.peer(0).network_service().clone(), - net.peer(0).sync_service().clone(), - net.peer(0).take_notification_service(&beefy_gossip_proto_name()).unwrap(), - "/beefy/whatever", - gossip_validator, - None, - ); - let (beefy_genesis, best_grandpa) = - wait_for_runtime_pallet(&api, &mut gossip_engine, &mut finality).await.unwrap(); - let mut worker_base = BeefyWorkerBase { - backend: backend.clone(), - runtime: Arc::new(api.clone()), - key_store: None.into(), - metrics: None, - _phantom: Default::default(), - }; - let persisted_state = - worker_base.load_or_init_state(beefy_genesis, best_grandpa, 1).await.unwrap(); + let persisted_state = voter_init_setup(&mut net, &mut finality, &api).await.unwrap(); // Test initialization at session boundary. // verify voter initialized with two sessions starting at blocks 1 and 10 @@ -1374,18 +1305,7 @@ async fn should_catch_up_when_loading_saved_voter_state() { // finalize 25 without justifications net.peer(0).client().as_client().finalize_block(hashes[25], None).unwrap(); // load persistent state - state preset in DB - // the network state persists and uses the old `GossipEngine` initialized for `peer(0)` - let (beefy_genesis, best_grandpa) = - wait_for_runtime_pallet(&api, &mut gossip_engine, &mut finality).await.unwrap(); - let mut worker_base = BeefyWorkerBase { - backend: backend.clone(), - runtime: Arc::new(api), - key_store: None.into(), - metrics: None, - _phantom: Default::default(), - }; - let persisted_state = - worker_base.load_or_init_state(beefy_genesis, best_grandpa, 1).await.unwrap(); + let persisted_state = voter_init_setup(&mut net, &mut finality, &api).await.unwrap(); // Verify voter initialized with old sessions plus a new one starting at block 20. // There shouldn't be any duplicates. diff --git a/substrate/client/consensus/beefy/src/worker.rs b/substrate/client/consensus/beefy/src/worker.rs index d7a229003cc..c8eb19621ba 100644 --- a/substrate/client/consensus/beefy/src/worker.rs +++ b/substrate/client/consensus/beefy/src/worker.rs @@ -17,46 +17,42 @@ // along with this program. If not, see . use crate::{ - aux_schema, communication::{ - gossip::{proofs_topic, votes_topic, GossipFilterCfg, GossipMessage, GossipValidator}, + gossip::{proofs_topic, votes_topic, GossipFilterCfg, GossipMessage}, peers::PeerReport, - request_response::outgoing_requests_engine::{OnDemandJustificationsEngine, ResponseInfo}, + request_response::outgoing_requests_engine::ResponseInfo, }, error::Error, - expect_validator_set, + find_authorities_change, justification::BeefyVersionedFinalityProof, keystore::BeefyKeystore, metric_inc, metric_set, metrics::VoterMetrics, round::{Rounds, VoteImportResult}, - wait_for_parent_header, BeefyVoterLinks, HEADER_SYNC_DELAY, LOG_TARGET, + BeefyComms, BeefyVoterLinks, LOG_TARGET, }; use codec::{Codec, Decode, DecodeAll, Encode}; use futures::{stream::Fuse, FutureExt, StreamExt}; use log::{debug, error, info, log_enabled, trace, warn}; use sc_client_api::{Backend, FinalityNotification, FinalityNotifications, HeaderBackend}; -use sc_network_gossip::GossipEngine; -use sc_utils::{mpsc::TracingUnboundedReceiver, notification::NotificationReceiver}; +use sc_utils::notification::NotificationReceiver; use sp_api::ProvideRuntimeApi; use sp_arithmetic::traits::{AtLeast32Bit, Saturating}; -use sp_blockchain::Backend as BlockchainBackend; use sp_consensus::SyncOracle; use sp_consensus_beefy::{ check_equivocation_proof, ecdsa_crypto::{AuthorityId, Signature}, - BeefyApi, BeefySignatureHasher, Commitment, ConsensusLog, EquivocationProof, PayloadProvider, - ValidatorSet, VersionedFinalityProof, VoteMessage, BEEFY_ENGINE_ID, + BeefyApi, BeefySignatureHasher, Commitment, EquivocationProof, PayloadProvider, ValidatorSet, + VersionedFinalityProof, VoteMessage, BEEFY_ENGINE_ID, }; use sp_runtime::{ - generic::{BlockId, OpaqueDigestItemId}, + generic::BlockId, traits::{Block, Header, NumberFor, Zero}, SaturatedConversion, }; use std::{ collections::{BTreeMap, BTreeSet, VecDeque}, fmt::Debug, - marker::PhantomData, sync::Arc, }; @@ -180,8 +176,8 @@ impl VoterOracle { } } - // Check if an observed session can be added to the Oracle. - fn can_add_session(&self, session_start: NumberFor) -> bool { + /// Check if an observed session can be added to the Oracle. + pub fn can_add_session(&self, session_start: NumberFor) -> bool { let latest_known_session_start = self.sessions.back().map(|session| session.session_start()); Some(session_start) > latest_known_session_start @@ -319,229 +315,28 @@ impl PersistedState { self.voting_oracle.best_grandpa_block_header = best_grandpa; } + pub fn voting_oracle(&self) -> &VoterOracle { + &self.voting_oracle + } + pub(crate) fn gossip_filter_config(&self) -> Result, Error> { let (start, end) = self.voting_oracle.accepted_interval()?; let validator_set = self.voting_oracle.current_validator_set()?; Ok(GossipFilterCfg { start, end, validator_set }) } -} - -/// Helper object holding BEEFY worker communication/gossip components. -/// -/// These are created once, but will be reused if worker is restarted/reinitialized. -pub(crate) struct BeefyComms { - pub gossip_engine: GossipEngine, - pub gossip_validator: Arc>, - pub gossip_report_stream: TracingUnboundedReceiver, - pub on_demand_justifications: OnDemandJustificationsEngine, -} - -pub(crate) struct BeefyWorkerBase { - // utilities - pub backend: Arc, - pub runtime: Arc, - pub key_store: BeefyKeystore, - - /// BEEFY client metrics. - pub metrics: Option, - - pub _phantom: PhantomData, -} - -impl BeefyWorkerBase -where - B: Block + Codec, - BE: Backend, - R: ProvideRuntimeApi, - R::Api: BeefyApi, -{ - // If no persisted state present, walk back the chain from first GRANDPA notification to either: - // - latest BEEFY finalized block, or if none found on the way, - // - BEEFY pallet genesis; - // Enqueue any BEEFY mandatory blocks (session boundaries) found on the way, for voter to - // finalize. - async fn init_state( - &self, - beefy_genesis: NumberFor, - best_grandpa: ::Header, - min_block_delta: u32, - ) -> Result, Error> { - let blockchain = self.backend.blockchain(); - - let beefy_genesis = self - .runtime - .runtime_api() - .beefy_genesis(best_grandpa.hash()) - .ok() - .flatten() - .filter(|genesis| *genesis == beefy_genesis) - .ok_or_else(|| Error::Backend("BEEFY pallet expected to be active.".into()))?; - // Walk back the imported blocks and initialize voter either, at the last block with - // a BEEFY justification, or at pallet genesis block; voter will resume from there. - let mut sessions = VecDeque::new(); - let mut header = best_grandpa.clone(); - let state = loop { - if let Some(true) = blockchain - .justifications(header.hash()) - .ok() - .flatten() - .map(|justifs| justifs.get(BEEFY_ENGINE_ID).is_some()) - { - debug!( - target: LOG_TARGET, - "🥩 Initialize BEEFY voter at last BEEFY finalized block: {:?}.", - *header.number() - ); - let best_beefy = *header.number(); - // If no session boundaries detected so far, just initialize new rounds here. - if sessions.is_empty() { - let active_set = - expect_validator_set(self.runtime.as_ref(), self.backend.as_ref(), &header) - .await?; - let mut rounds = Rounds::new(best_beefy, active_set); - // Mark the round as already finalized. - rounds.conclude(best_beefy); - sessions.push_front(rounds); - } - let state = PersistedState::checked_new( - best_grandpa, - best_beefy, - sessions, - min_block_delta, - beefy_genesis, - ) - .ok_or_else(|| Error::Backend("Invalid BEEFY chain".into()))?; - break state - } - - if *header.number() == beefy_genesis { - // We've reached BEEFY genesis, initialize voter here. - let genesis_set = - expect_validator_set(self.runtime.as_ref(), self.backend.as_ref(), &header) - .await?; - info!( - target: LOG_TARGET, - "🥩 Loading BEEFY voter state from genesis on what appears to be first startup. \ - Starting voting rounds at block {:?}, genesis validator set {:?}.", - beefy_genesis, - genesis_set, - ); - - sessions.push_front(Rounds::new(beefy_genesis, genesis_set)); - break PersistedState::checked_new( - best_grandpa, - Zero::zero(), - sessions, - min_block_delta, - beefy_genesis, - ) - .ok_or_else(|| Error::Backend("Invalid BEEFY chain".into()))? - } - - if let Some(active) = find_authorities_change::(&header) { - debug!( - target: LOG_TARGET, - "🥩 Marking block {:?} as BEEFY Mandatory.", - *header.number() - ); - sessions.push_front(Rounds::new(*header.number(), active)); - } - - // Move up the chain. - header = wait_for_parent_header(blockchain, header, HEADER_SYNC_DELAY).await?; - }; - - aux_schema::write_current_version(self.backend.as_ref())?; - aux_schema::write_voter_state(self.backend.as_ref(), &state)?; - Ok(state) - } - - pub async fn load_or_init_state( - &mut self, - beefy_genesis: NumberFor, - best_grandpa: ::Header, - min_block_delta: u32, - ) -> Result, Error> { - // Initialize voter state from AUX DB if compatible. - if let Some(mut state) = crate::aux_schema::load_persistent(self.backend.as_ref())? - // Verify state pallet genesis matches runtime. - .filter(|state| state.pallet_genesis() == beefy_genesis) - { - // Overwrite persisted state with current best GRANDPA block. - state.set_best_grandpa(best_grandpa.clone()); - // Overwrite persisted data with newly provided `min_block_delta`. - state.set_min_block_delta(min_block_delta); - debug!(target: LOG_TARGET, "🥩 Loading BEEFY voter state from db: {:?}.", state); - - // Make sure that all the headers that we need have been synced. - let mut new_sessions = vec![]; - let mut header = best_grandpa.clone(); - while *header.number() > state.best_beefy() { - if state.voting_oracle.can_add_session(*header.number()) { - if let Some(active) = find_authorities_change::(&header) { - new_sessions.push((active, *header.number())); - } - } - header = - wait_for_parent_header(self.backend.blockchain(), header, HEADER_SYNC_DELAY) - .await?; - } - - // Make sure we didn't miss any sessions during node restart. - for (validator_set, new_session_start) in new_sessions.drain(..).rev() { - debug!( - target: LOG_TARGET, - "🥩 Handling missed BEEFY session after node restart: {:?}.", - new_session_start - ); - self.init_session_at(&mut state, validator_set, new_session_start); - } - return Ok(state) - } - - // No valid voter-state persisted, re-initialize from pallet genesis. - self.init_state(beefy_genesis, best_grandpa, min_block_delta).await - } - - /// Verify `active` validator set for `block` against the key store - /// - /// We want to make sure that we have _at least one_ key in our keystore that - /// is part of the validator set, that's because if there are no local keys - /// then we can't perform our job as a validator. - /// - /// Note that for a non-authority node there will be no keystore, and we will - /// return an error and don't check. The error can usually be ignored. - fn verify_validator_set( - &self, - block: &NumberFor, - active: &ValidatorSet, - ) -> Result<(), Error> { - let active: BTreeSet<&AuthorityId> = active.validators().iter().collect(); - - let public_keys = self.key_store.public_keys()?; - let store: BTreeSet<&AuthorityId> = public_keys.iter().collect(); - - if store.intersection(&active).count() == 0 { - let msg = "no authority public key found in store".to_string(); - debug!(target: LOG_TARGET, "🥩 for block {:?} {}", block, msg); - metric_inc!(self.metrics, beefy_no_authority_found_in_store); - Err(Error::Keystore(msg)) - } else { - Ok(()) - } - } /// Handle session changes by starting new voting round for mandatory blocks. - fn init_session_at( + pub fn init_session_at( &mut self, - persisted_state: &mut PersistedState, - validator_set: ValidatorSet, new_session_start: NumberFor, + validator_set: ValidatorSet, + key_store: &BeefyKeystore, + metrics: &Option, ) { debug!(target: LOG_TARGET, "🥩 New active validator set: {:?}", validator_set); // BEEFY should finalize a mandatory block during each session. - if let Ok(active_session) = persisted_state.voting_oracle.active_rounds() { + if let Ok(active_session) = self.voting_oracle.active_rounds() { if !active_session.mandatory_done() { debug!( target: LOG_TARGET, @@ -549,20 +344,20 @@ where validator_set.id(), active_session.validator_set_id(), ); - metric_inc!(self.metrics, beefy_lagging_sessions); + metric_inc!(metrics, beefy_lagging_sessions); } } if log_enabled!(target: LOG_TARGET, log::Level::Debug) { // verify the new validator set - only do it if we're also logging the warning - let _ = self.verify_validator_set(&new_session_start, &validator_set); + if verify_validator_set::(&new_session_start, &validator_set, key_store).is_err() { + metric_inc!(metrics, beefy_no_authority_found_in_store); + } } let id = validator_set.id(); - persisted_state - .voting_oracle - .add_session(Rounds::new(new_session_start, validator_set)); - metric_set!(self.metrics, beefy_validator_set_id, id); + self.voting_oracle.add_session(Rounds::new(new_session_start, validator_set)); + metric_set!(metrics, beefy_validator_set_id, id); info!( target: LOG_TARGET, "🥩 New Rounds for validator set id: {:?} with session_start {:?}", @@ -574,9 +369,10 @@ where /// A BEEFY worker/voter that follows the BEEFY protocol pub(crate) struct BeefyWorker { - pub base: BeefyWorkerBase, - - // utils + // utilities + pub backend: Arc, + pub runtime: Arc, + pub key_store: BeefyKeystore, pub payload_provider: P, pub sync: Arc, @@ -592,6 +388,8 @@ pub(crate) struct BeefyWorker { pub pending_justifications: BTreeMap, BeefyVersionedFinalityProof>, /// Persisted voter state. pub persisted_state: PersistedState, + /// BEEFY voter metrics + pub metrics: Option, } impl BeefyWorker @@ -622,8 +420,12 @@ where validator_set: ValidatorSet, new_session_start: NumberFor, ) { - self.base - .init_session_at(&mut self.persisted_state, validator_set, new_session_start); + self.persisted_state.init_session_at( + new_session_start, + validator_set, + &self.key_store, + &self.metrics, + ); } fn handle_finality_notification( @@ -639,8 +441,7 @@ where notification.tree_route, ); - self.base - .runtime + self.runtime .runtime_api() .beefy_genesis(header.hash()) .ok() @@ -654,7 +455,7 @@ where self.persisted_state.set_best_grandpa(header.clone()); // Check all (newly) finalized blocks for new session(s). - let backend = self.base.backend.clone(); + let backend = self.backend.clone(); for header in notification .tree_route .iter() @@ -673,7 +474,7 @@ where } if new_session_added { - crate::aux_schema::write_voter_state(&*self.base.backend, &self.persisted_state) + crate::aux_schema::write_voter_state(&*self.backend, &self.persisted_state) .map_err(|e| Error::Backend(e.to_string()))?; } @@ -707,7 +508,7 @@ where true, ); }, - RoundAction::Drop => metric_inc!(self.base.metrics, beefy_stale_votes), + RoundAction::Drop => metric_inc!(self.metrics, beefy_stale_votes), RoundAction::Enqueue => error!(target: LOG_TARGET, "🥩 unexpected vote: {:?}.", vote), }; Ok(()) @@ -727,23 +528,23 @@ where match self.voting_oracle().triage_round(block_num)? { RoundAction::Process => { debug!(target: LOG_TARGET, "🥩 Process justification for round: {:?}.", block_num); - metric_inc!(self.base.metrics, beefy_imported_justifications); + metric_inc!(self.metrics, beefy_imported_justifications); self.finalize(justification)? }, RoundAction::Enqueue => { debug!(target: LOG_TARGET, "🥩 Buffer justification for round: {:?}.", block_num); if self.pending_justifications.len() < MAX_BUFFERED_JUSTIFICATIONS { self.pending_justifications.entry(block_num).or_insert(justification); - metric_inc!(self.base.metrics, beefy_buffered_justifications); + metric_inc!(self.metrics, beefy_buffered_justifications); } else { - metric_inc!(self.base.metrics, beefy_buffered_justifications_dropped); + metric_inc!(self.metrics, beefy_buffered_justifications_dropped); warn!( target: LOG_TARGET, "🥩 Buffer justification dropped for round: {:?}.", block_num ); } }, - RoundAction::Drop => metric_inc!(self.base.metrics, beefy_stale_justifications), + RoundAction::Drop => metric_inc!(self.metrics, beefy_stale_justifications), }; Ok(()) } @@ -765,7 +566,7 @@ where // We created the `finality_proof` and know to be valid. // New state is persisted after finalization. self.finalize(finality_proof.clone())?; - metric_inc!(self.base.metrics, beefy_good_votes_processed); + metric_inc!(self.metrics, beefy_good_votes_processed); return Ok(Some(finality_proof)) }, VoteImportResult::Ok => { @@ -776,20 +577,17 @@ where .map(|(mandatory_num, _)| mandatory_num == block_number) .unwrap_or(false) { - crate::aux_schema::write_voter_state( - &*self.base.backend, - &self.persisted_state, - ) - .map_err(|e| Error::Backend(e.to_string()))?; + crate::aux_schema::write_voter_state(&*self.backend, &self.persisted_state) + .map_err(|e| Error::Backend(e.to_string()))?; } - metric_inc!(self.base.metrics, beefy_good_votes_processed); + metric_inc!(self.metrics, beefy_good_votes_processed); }, VoteImportResult::Equivocation(proof) => { - metric_inc!(self.base.metrics, beefy_equivocation_votes); + metric_inc!(self.metrics, beefy_equivocation_votes); self.report_equivocation(proof)?; }, - VoteImportResult::Invalid => metric_inc!(self.base.metrics, beefy_invalid_votes), - VoteImportResult::Stale => metric_inc!(self.base.metrics, beefy_stale_votes), + VoteImportResult::Invalid => metric_inc!(self.metrics, beefy_invalid_votes), + VoteImportResult::Stale => metric_inc!(self.metrics, beefy_stale_votes), }; Ok(None) } @@ -816,15 +614,14 @@ where // Set new best BEEFY block number. self.persisted_state.set_best_beefy(block_num); - crate::aux_schema::write_voter_state(&*self.base.backend, &self.persisted_state) + crate::aux_schema::write_voter_state(&*self.backend, &self.persisted_state) .map_err(|e| Error::Backend(e.to_string()))?; - metric_set!(self.base.metrics, beefy_best_block, block_num); + metric_set!(self.metrics, beefy_best_block, block_num); self.comms.on_demand_justifications.cancel_requests_older_than(block_num); if let Err(e) = self - .base .backend .blockchain() .expect_block_hash_from_id(&BlockId::Number(block_num)) @@ -834,8 +631,7 @@ where .notify(|| Ok::<_, ()>(hash)) .expect("forwards closure result; the closure always returns Ok; qed."); - self.base - .backend + self.backend .append_justification(hash, (BEEFY_ENGINE_ID, finality_proof.encode())) }) { debug!( @@ -872,13 +668,13 @@ where for (num, justification) in justifs_to_process.into_iter() { debug!(target: LOG_TARGET, "🥩 Handle buffered justification for: {:?}.", num); - metric_inc!(self.base.metrics, beefy_imported_justifications); + metric_inc!(self.metrics, beefy_imported_justifications); if let Err(err) = self.finalize(justification) { error!(target: LOG_TARGET, "🥩 Error finalizing block: {}", err); } } metric_set!( - self.base.metrics, + self.metrics, beefy_buffered_justifications, self.pending_justifications.len() ); @@ -890,7 +686,7 @@ where fn try_to_vote(&mut self) -> Result<(), Error> { // Vote if there's now a new vote target. if let Some(target) = self.voting_oracle().voting_target() { - metric_set!(self.base.metrics, beefy_should_vote_on, target); + metric_set!(self.metrics, beefy_should_vote_on, target); if target > self.persisted_state.best_voted { self.do_vote(target)?; } @@ -910,7 +706,6 @@ where self.persisted_state.voting_oracle.best_grandpa_block_header.clone() } else { let hash = self - .base .backend .blockchain() .expect_block_hash_from_id(&BlockId::Number(target_number)) @@ -922,7 +717,7 @@ where Error::Backend(err_msg) })?; - self.base.backend.blockchain().expect_header(hash).map_err(|err| { + self.backend.blockchain().expect_header(hash).map_err(|err| { let err_msg = format!( "Couldn't get header for block #{:?} ({:?}) (error: {:?}), skipping vote..", target_number, hash, err @@ -942,7 +737,7 @@ where let rounds = self.persisted_state.voting_oracle.active_rounds_mut()?; let (validators, validator_set_id) = (rounds.validators(), rounds.validator_set_id()); - let authority_id = if let Some(id) = self.base.key_store.authority_id(validators) { + let authority_id = if let Some(id) = self.key_store.authority_id(validators) { debug!(target: LOG_TARGET, "🥩 Local authority id: {:?}", id); id } else { @@ -956,7 +751,7 @@ where let commitment = Commitment { payload, block_number: target_number, validator_set_id }; let encoded_commitment = commitment.encode(); - let signature = match self.base.key_store.sign(&authority_id, &encoded_commitment) { + let signature = match self.key_store.sign(&authority_id, &encoded_commitment) { Ok(sig) => sig, Err(err) => { warn!(target: LOG_TARGET, "🥩 Error signing commitment: {:?}", err); @@ -981,7 +776,7 @@ where .gossip_engine .gossip_message(proofs_topic::(), encoded_proof, true); } else { - metric_inc!(self.base.metrics, beefy_votes_sent); + metric_inc!(self.metrics, beefy_votes_sent); debug!(target: LOG_TARGET, "🥩 Sent vote message: {:?}", vote); let encoded_vote = GossipMessage::::Vote(vote).encode(); self.comms.gossip_engine.gossip_message(votes_topic::(), encoded_vote, false); @@ -989,8 +784,8 @@ where // Persist state after vote to avoid double voting in case of voter restarts. self.persisted_state.best_voted = target_number; - metric_set!(self.base.metrics, beefy_best_voted, target_number); - crate::aux_schema::write_voter_state(&*self.base.backend, &self.persisted_state) + metric_set!(self.metrics, beefy_best_voted, target_number); + crate::aux_schema::write_voter_state(&*self.backend, &self.persisted_state) .map_err(|e| Error::Backend(e.to_string())) } @@ -1164,7 +959,7 @@ where if !check_equivocation_proof::<_, _, BeefySignatureHasher>(&proof) { debug!(target: LOG_TARGET, "🥩 Skip report for bad equivocation {:?}", proof); return Ok(()) - } else if let Some(local_id) = self.base.key_store.authority_id(validators) { + } else if let Some(local_id) = self.key_store.authority_id(validators) { if offender_id == local_id { warn!(target: LOG_TARGET, "🥩 Skip equivocation report for own equivocation"); return Ok(()) @@ -1173,7 +968,6 @@ where let number = *proof.round_number(); let hash = self - .base .backend .blockchain() .expect_block_hash_from_id(&BlockId::Number(number)) @@ -1184,7 +978,7 @@ where ); Error::Backend(err_msg) })?; - let runtime_api = self.base.runtime.runtime_api(); + let runtime_api = self.runtime.runtime_api(); // generate key ownership proof at that block let key_owner_proof = match runtime_api .generate_key_ownership_proof(hash, validator_set_id, offender_id) @@ -1201,7 +995,7 @@ where }; // submit equivocation report at **best** block - let best_block_hash = self.base.backend.blockchain().info().best_hash; + let best_block_hash = self.backend.blockchain().info().best_hash; runtime_api .submit_report_equivocation_unsigned_extrinsic(best_block_hash, proof, key_owner_proof) .map_err(Error::RuntimeApi)?; @@ -1210,21 +1004,6 @@ where } } -/// Scan the `header` digest log for a BEEFY validator set change. Return either the new -/// validator set or `None` in case no validator set change has been signaled. -pub(crate) fn find_authorities_change(header: &B::Header) -> Option> -where - B: Block, -{ - let id = OpaqueDigestItemId::Consensus(&BEEFY_ENGINE_ID); - - let filter = |log: ConsensusLog| match log { - ConsensusLog::AuthoritiesChange(validator_set) => Some(validator_set), - _ => None, - }; - header.digest().convert_first(|l| l.try_to(id).and_then(filter)) -} - /// Calculate next block number to vote on. /// /// Return `None` if there is no votable target yet. @@ -1261,11 +1040,42 @@ where } } +/// Verify `active` validator set for `block` against the key store +/// +/// We want to make sure that we have _at least one_ key in our keystore that +/// is part of the validator set, that's because if there are no local keys +/// then we can't perform our job as a validator. +/// +/// Note that for a non-authority node there will be no keystore, and we will +/// return an error and don't check. The error can usually be ignored. +fn verify_validator_set( + block: &NumberFor, + active: &ValidatorSet, + key_store: &BeefyKeystore, +) -> Result<(), Error> { + let active: BTreeSet<&AuthorityId> = active.validators().iter().collect(); + + let public_keys = key_store.public_keys()?; + let store: BTreeSet<&AuthorityId> = public_keys.iter().collect(); + + if store.intersection(&active).count() == 0 { + let msg = "no authority public key found in store".to_string(); + debug!(target: LOG_TARGET, "🥩 for block {:?} {}", block, msg); + Err(Error::Keystore(msg)) + } else { + Ok(()) + } +} + #[cfg(test)] pub(crate) mod tests { use super::*; use crate::{ - communication::notification::{BeefyBestBlockStream, BeefyVersionedFinalityProofStream}, + communication::{ + gossip::GossipValidator, + notification::{BeefyBestBlockStream, BeefyVersionedFinalityProofStream}, + request_response::outgoing_requests_engine::OnDemandJustificationsEngine, + }, tests::{ create_beefy_keystore, get_beefy_streams, make_beefy_ids, BeefyPeer, BeefyTestNet, TestApi, @@ -1275,6 +1085,7 @@ pub(crate) mod tests { use futures::{future::poll_fn, task::Poll}; use parking_lot::Mutex; use sc_client_api::{Backend as BackendT, HeaderBackend}; + use sc_network_gossip::GossipEngine; use sc_network_sync::SyncingService; use sc_network_test::TestNetFactory; use sp_blockchain::Backend as BlockchainBackendT; @@ -1283,7 +1094,7 @@ pub(crate) mod tests { known_payloads::MMR_ROOT_ID, mmr::MmrRootProvider, test_utils::{generate_equivocation_proof, Keyring}, - Payload, SignedCommitment, + ConsensusLog, Payload, SignedCommitment, }; use sp_runtime::traits::{Header as HeaderT, One}; use substrate_test_runtime_client::{ @@ -1292,10 +1103,6 @@ pub(crate) mod tests { }; impl PersistedState { - pub fn voting_oracle(&self) -> &VoterOracle { - &self.voting_oracle - } - pub fn active_round(&self) -> Result<&Rounds, Error> { self.voting_oracle.active_rounds() } @@ -1391,13 +1198,10 @@ pub(crate) mod tests { on_demand_justifications, }; BeefyWorker { - base: BeefyWorkerBase { - backend, - runtime: api, - key_store: Some(keystore).into(), - metrics, - _phantom: Default::default(), - }, + backend, + runtime: api, + key_store: Some(keystore).into(), + metrics, payload_provider, sync: Arc::new(sync), links, @@ -1675,19 +1479,22 @@ pub(crate) mod tests { let mut worker = create_beefy_worker(net.peer(0), &keys[0], 1, validator_set.clone()); // keystore doesn't contain other keys than validators' - assert_eq!(worker.base.verify_validator_set(&1, &validator_set), Ok(())); + assert_eq!(verify_validator_set::(&1, &validator_set, &worker.key_store), Ok(())); // unknown `Bob` key let keys = &[Keyring::Bob]; let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); let err_msg = "no authority public key found in store".to_string(); let expected = Err(Error::Keystore(err_msg)); - assert_eq!(worker.base.verify_validator_set(&1, &validator_set), expected); + assert_eq!(verify_validator_set::(&1, &validator_set, &worker.key_store), expected); // worker has no keystore - worker.base.key_store = None.into(); + worker.key_store = None.into(); let expected_err = Err(Error::Keystore("no Keystore".into())); - assert_eq!(worker.base.verify_validator_set(&1, &validator_set), expected_err); + assert_eq!( + verify_validator_set::(&1, &validator_set, &worker.key_store), + expected_err + ); } #[tokio::test] @@ -1839,7 +1646,7 @@ pub(crate) mod tests { let mut net = BeefyTestNet::new(1); let mut worker = create_beefy_worker(net.peer(0), &keys[0], 1, validator_set.clone()); - worker.base.runtime = api_alice.clone(); + worker.runtime = api_alice.clone(); // let there be a block with num = 1: let _ = net.peer(0).push_blocks(1, false); diff --git a/substrate/primitives/consensus/beefy/src/commitment.rs b/substrate/primitives/consensus/beefy/src/commitment.rs index 1f0fb34ebf1..335c6b604f0 100644 --- a/substrate/primitives/consensus/beefy/src/commitment.rs +++ b/substrate/primitives/consensus/beefy/src/commitment.rs @@ -97,6 +97,19 @@ pub struct SignedCommitment { pub signatures: Vec>, } +impl sp_std::fmt::Display + for SignedCommitment +{ + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { + let signatures_count = self.signatures.iter().filter(|s| s.is_some()).count(); + write!( + f, + "SignedCommitment(commitment: {:?}, signatures_count: {})", + self.commitment, signatures_count + ) + } +} + impl SignedCommitment { /// Return the number of collected signatures. pub fn no_of_signatures(&self) -> usize { @@ -241,6 +254,14 @@ pub enum VersionedFinalityProof { V1(SignedCommitment), } +impl sp_std::fmt::Display for VersionedFinalityProof { + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { + match self { + VersionedFinalityProof::V1(sc) => write!(f, "VersionedFinalityProof::V1({})", sc), + } + } +} + impl From> for VersionedFinalityProof { fn from(commitment: SignedCommitment) -> Self { VersionedFinalityProof::V1(commitment) -- GitLab From 9bf1a5e23884921498b381728bfddaae93f83744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 22 Feb 2024 14:35:00 +0100 Subject: [PATCH 114/133] Check that the validation code matches the parachain code (#3433) This introduces a check to ensure that the parachain code matches the validation code stored in the relay chain state. If not, it will print a warning. This should be mainly useful for parachain builders to make sure they have setup everything correctly. --- .../consensus/aura/src/collators/basic.rs | 22 +++++++- .../consensus/aura/src/collators/lookahead.rs | 17 ++++-- .../consensus/aura/src/collators/mod.rs | 55 +++++++++++++++++++ cumulus/client/consensus/common/src/tests.rs | 9 +++ cumulus/client/network/src/tests.rs | 9 +++ .../src/lib.rs | 15 ++++- .../client/relay-chain-interface/src/lib.rs | 22 +++++++- .../relay-chain-rpc-interface/src/lib.rs | 13 ++++- .../src/rpc_client.rs | 14 +++++ polkadot/node/core/backing/src/lib.rs | 10 ++-- 10 files changed, 173 insertions(+), 13 deletions(-) diff --git a/cumulus/client/consensus/aura/src/collators/basic.rs b/cumulus/client/consensus/aura/src/collators/basic.rs index 8740b06005d..52b83254951 100644 --- a/cumulus/client/consensus/aura/src/collators/basic.rs +++ b/cumulus/client/consensus/aura/src/collators/basic.rs @@ -33,12 +33,12 @@ use cumulus_relay_chain_interface::RelayChainInterface; use polkadot_node_primitives::CollationResult; use polkadot_overseer::Handle as OverseerHandle; -use polkadot_primitives::{CollatorPair, Id as ParaId}; +use polkadot_primitives::{CollatorPair, Id as ParaId, ValidationCode}; use futures::{channel::mpsc::Receiver, prelude::*}; use sc_client_api::{backend::AuxStore, BlockBackend, BlockOf}; use sc_consensus::BlockImport; -use sp_api::ProvideRuntimeApi; +use sp_api::{CallApiAt, ProvideRuntimeApi}; use sp_application_crypto::AppPublic; use sp_blockchain::HeaderBackend; use sp_consensus::SyncOracle; @@ -47,6 +47,7 @@ use sp_core::crypto::Pair; use sp_inherents::CreateInherentDataProviders; use sp_keystore::KeystorePtr; use sp_runtime::traits::{Block as BlockT, Header as HeaderT, Member}; +use sp_state_machine::Backend as _; use std::{convert::TryFrom, sync::Arc, time::Duration}; use crate::collator as collator_util; @@ -100,6 +101,7 @@ where + AuxStore + HeaderBackend + BlockBackend + + CallApiAt + Send + Sync + 'static, @@ -172,6 +174,22 @@ where continue } + let Ok(Some(code)) = + params.para_client.state_at(parent_hash).map_err(drop).and_then(|s| { + s.storage(&sp_core::storage::well_known_keys::CODE).map_err(drop) + }) + else { + continue; + }; + + super::check_validation_code_or_log( + &ValidationCode::from(code).hash(), + params.para_id, + ¶ms.relay_client, + *request.relay_parent(), + ) + .await; + let relay_parent_header = match params.relay_client.header(RBlockId::hash(*request.relay_parent())).await { Err(e) => reject_with_error!(e), diff --git a/cumulus/client/consensus/aura/src/collators/lookahead.rs b/cumulus/client/consensus/aura/src/collators/lookahead.rs index a9f33173d83..161f10d55a1 100644 --- a/cumulus/client/consensus/aura/src/collators/lookahead.rs +++ b/cumulus/client/consensus/aura/src/collators/lookahead.rs @@ -290,10 +290,7 @@ where // If the longest chain has space, build upon that. Otherwise, don't // build at all. potential_parents.sort_by_key(|a| a.depth); - let initial_parent = match potential_parents.pop() { - None => continue, - Some(p) => p, - }; + let Some(initial_parent) = potential_parents.pop() else { continue }; // Build in a loop until not allowed. Note that the authorities can change // at any block, so we need to re-claim our slot every time. @@ -301,6 +298,10 @@ where let mut parent_header = initial_parent.header; let overseer_handle = &mut params.overseer_handle; + // We mainly call this to inform users at genesis if there is a mismatch with the + // on-chain data. + collator.collator_service().check_block_status(parent_hash, &parent_header); + // This needs to change to support elastic scaling, but for continuously // scheduled chains this ensures that the backlog will grow steadily. for n_built in 0..2 { @@ -353,6 +354,14 @@ where Some(v) => v, }; + super::check_validation_code_or_log( + &validation_code_hash, + params.para_id, + ¶ms.relay_client, + relay_parent, + ) + .await; + match collator .collate( &parent_header, diff --git a/cumulus/client/consensus/aura/src/collators/mod.rs b/cumulus/client/consensus/aura/src/collators/mod.rs index 4c7b759daf7..6e0067d0ced 100644 --- a/cumulus/client/consensus/aura/src/collators/mod.rs +++ b/cumulus/client/consensus/aura/src/collators/mod.rs @@ -20,5 +20,60 @@ //! included parachain block, as well as the [`lookahead`] collator, which prospectively //! builds on parachain blocks which have not yet been included in the relay chain. +use cumulus_relay_chain_interface::RelayChainInterface; +use polkadot_primitives::{ + Hash as RHash, Id as ParaId, OccupiedCoreAssumption, ValidationCodeHash, +}; + pub mod basic; pub mod lookahead; + +/// Check the `local_validation_code_hash` against the validation code hash in the relay chain +/// state. +/// +/// If the code hashes do not match, it prints a warning. +async fn check_validation_code_or_log( + local_validation_code_hash: &ValidationCodeHash, + para_id: ParaId, + relay_client: &impl RelayChainInterface, + relay_parent: RHash, +) { + let state_validation_code_hash = match relay_client + .validation_code_hash(relay_parent, para_id, OccupiedCoreAssumption::Included) + .await + { + Ok(hash) => hash, + Err(error) => { + tracing::debug!( + target: super::LOG_TARGET, + %error, + ?relay_parent, + %para_id, + "Failed to fetch validation code hash", + ); + return + }, + }; + + match state_validation_code_hash { + Some(state) => + if state != *local_validation_code_hash { + tracing::warn!( + target: super::LOG_TARGET, + %para_id, + ?relay_parent, + ?local_validation_code_hash, + relay_validation_code_hash = ?state, + "Parachain code doesn't match validation code stored in the relay chain state", + ); + }, + None => { + tracing::warn!( + target: super::LOG_TARGET, + %para_id, + ?relay_parent, + "Could not find validation code for parachain in the relay chain state.", + ); + }, + } +} diff --git a/cumulus/client/consensus/common/src/tests.rs b/cumulus/client/consensus/common/src/tests.rs index 597d1ab2acc..bfb95ae388a 100644 --- a/cumulus/client/consensus/common/src/tests.rs +++ b/cumulus/client/consensus/common/src/tests.rs @@ -136,6 +136,15 @@ impl RelayChainInterface for Relaychain { Ok(Some(PersistedValidationData { parent_head, ..Default::default() })) } + async fn validation_code_hash( + &self, + _: PHash, + _: ParaId, + _: OccupiedCoreAssumption, + ) -> RelayChainResult> { + unimplemented!("Not needed for test") + } + async fn candidate_pending_availability( &self, _: PHash, diff --git a/cumulus/client/network/src/tests.rs b/cumulus/client/network/src/tests.rs index e03f470753b..d986635f961 100644 --- a/cumulus/client/network/src/tests.rs +++ b/cumulus/client/network/src/tests.rs @@ -117,6 +117,15 @@ impl RelayChainInterface for DummyRelayChainInterface { })) } + async fn validation_code_hash( + &self, + _: PHash, + _: ParaId, + _: OccupiedCoreAssumption, + ) -> RelayChainResult> { + unimplemented!("Not needed for test") + } + async fn candidate_pending_availability( &self, _: PHash, diff --git a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs index 866214fe2c5..6ea02b2e7c1 100644 --- a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs +++ b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs @@ -21,7 +21,7 @@ use cumulus_primitives_core::{ relay_chain::{ runtime_api::ParachainHost, Block as PBlock, BlockId, CommittedCandidateReceipt, Hash as PHash, Header as PHeader, InboundHrmpMessage, OccupiedCoreAssumption, SessionIndex, - ValidatorId, + ValidationCodeHash, ValidatorId, }, InboundDownwardMessage, ParaId, PersistedValidationData, }; @@ -115,6 +115,19 @@ impl RelayChainInterface for RelayChainInProcessInterface { )?) } + async fn validation_code_hash( + &self, + hash: PHash, + para_id: ParaId, + occupied_core_assumption: OccupiedCoreAssumption, + ) -> RelayChainResult> { + Ok(self.full_client.runtime_api().validation_code_hash( + hash, + para_id, + occupied_core_assumption, + )?) + } + async fn candidate_pending_availability( &self, hash: PHash, diff --git a/cumulus/client/relay-chain-interface/src/lib.rs b/cumulus/client/relay-chain-interface/src/lib.rs index de5e7891b30..bb93e6a168c 100644 --- a/cumulus/client/relay-chain-interface/src/lib.rs +++ b/cumulus/client/relay-chain-interface/src/lib.rs @@ -30,7 +30,7 @@ use cumulus_primitives_core::relay_chain::BlockId; pub use cumulus_primitives_core::{ relay_chain::{ CommittedCandidateReceipt, Hash as PHash, Header as PHeader, InboundHrmpMessage, - OccupiedCoreAssumption, SessionIndex, ValidatorId, + OccupiedCoreAssumption, SessionIndex, ValidationCodeHash, ValidatorId, }, InboundDownwardMessage, ParaId, PersistedValidationData, }; @@ -194,6 +194,15 @@ pub trait RelayChainInterface: Send + Sync { relay_parent: PHash, relevant_keys: &Vec>, ) -> RelayChainResult; + + /// Returns the validation code hash for the given `para_id` using the given + /// `occupied_core_assumption`. + async fn validation_code_hash( + &self, + relay_parent: PHash, + para_id: ParaId, + occupied_core_assumption: OccupiedCoreAssumption, + ) -> RelayChainResult>; } #[async_trait] @@ -301,4 +310,15 @@ where async fn header(&self, block_id: BlockId) -> RelayChainResult> { (**self).header(block_id).await } + + async fn validation_code_hash( + &self, + relay_parent: PHash, + para_id: ParaId, + occupied_core_assumption: OccupiedCoreAssumption, + ) -> RelayChainResult> { + (**self) + .validation_code_hash(relay_parent, para_id, occupied_core_assumption) + .await + } } diff --git a/cumulus/client/relay-chain-rpc-interface/src/lib.rs b/cumulus/client/relay-chain-rpc-interface/src/lib.rs index 96f8fc8b556..3a4c186e301 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/lib.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/lib.rs @@ -19,7 +19,7 @@ use core::time::Duration; use cumulus_primitives_core::{ relay_chain::{ CommittedCandidateReceipt, Hash as RelayHash, Header as RelayHeader, InboundHrmpMessage, - OccupiedCoreAssumption, SessionIndex, ValidatorId, + OccupiedCoreAssumption, SessionIndex, ValidationCodeHash, ValidatorId, }, InboundDownwardMessage, ParaId, PersistedValidationData, }; @@ -110,6 +110,17 @@ impl RelayChainInterface for RelayChainRpcInterface { .await } + async fn validation_code_hash( + &self, + hash: RelayHash, + para_id: ParaId, + occupied_core_assumption: OccupiedCoreAssumption, + ) -> RelayChainResult> { + self.rpc_client + .validation_code_hash(hash, para_id, occupied_core_assumption) + .await + } + async fn candidate_pending_availability( &self, hash: RelayHash, diff --git a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs index a912997e947..6578210a259 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs @@ -647,6 +647,20 @@ impl RelayChainRpcClient { .await } + pub async fn validation_code_hash( + &self, + at: RelayHash, + para_id: ParaId, + occupied_core_assumption: OccupiedCoreAssumption, + ) -> Result, RelayChainError> { + self.call_remote_runtime_function( + "ParachainHost_validation_code_hash", + at, + Some((para_id, occupied_core_assumption)), + ) + .await + } + fn send_register_message_to_worker( &self, message: RpcDispatcherMessage, diff --git a/polkadot/node/core/backing/src/lib.rs b/polkadot/node/core/backing/src/lib.rs index cc192607cea..26f20a6d48e 100644 --- a/polkadot/node/core/backing/src/lib.rs +++ b/polkadot/node/core/backing/src/lib.rs @@ -1887,18 +1887,20 @@ async fn background_validate_and_make_available( if rp_state.awaiting_validation.insert(candidate_hash) { // spawn background task. let bg = async move { - if let Err(e) = validate_and_make_available(params).await { - if let Error::BackgroundValidationMpsc(error) = e { + if let Err(error) = validate_and_make_available(params).await { + if let Error::BackgroundValidationMpsc(error) = error { gum::debug!( target: LOG_TARGET, + ?candidate_hash, ?error, "Mpsc background validation mpsc died during validation- leaf no longer active?" ); } else { gum::error!( target: LOG_TARGET, - "Failed to validate and make available: {:?}", - e + ?candidate_hash, + ?error, + "Failed to validate and make available", ); } } -- GitLab From e4b6b8cd7973633f86d1b92a56abf2a946b7be84 Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Fri, 23 Feb 2024 09:51:26 +0100 Subject: [PATCH 115/133] Add support for BHP local and BHK local (#3443) Related to https://github.com/paritytech/polkadot-sdk/issues/3400 Extracting small parts of https://github.com/paritytech/polkadot-sdk/pull/3429 into separate PR: - Add support for BHP local and BHK local - Increase the timeout for the bridge zomienet tests --- bridges/testing/tests/0001-asset-transfer/run.sh | 4 ++-- .../tests/0002-mandatory-headers-synced-while-idle/run.sh | 4 ++-- cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs | 8 ++++++++ cumulus/polkadot-parachain/src/command.rs | 6 ++++-- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/bridges/testing/tests/0001-asset-transfer/run.sh b/bridges/testing/tests/0001-asset-transfer/run.sh index 8a053ee7209..1e0d6cad301 100755 --- a/bridges/testing/tests/0001-asset-transfer/run.sh +++ b/bridges/testing/tests/0001-asset-transfer/run.sh @@ -8,11 +8,11 @@ source "${BASH_SOURCE%/*}/../../utils/zombienet.sh" ${BASH_SOURCE%/*}/../../environments/rococo-westend/spawn.sh --init --start-relayer & env_pid=$! -ensure_process_file $env_pid $TEST_DIR/rococo.env 400 +ensure_process_file $env_pid $TEST_DIR/rococo.env 600 rococo_dir=`cat $TEST_DIR/rococo.env` echo -ensure_process_file $env_pid $TEST_DIR/westend.env 180 +ensure_process_file $env_pid $TEST_DIR/westend.env 300 westend_dir=`cat $TEST_DIR/westend.env` echo diff --git a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh index 423f4a1bcc0..c39796d51d5 100755 --- a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh +++ b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh @@ -10,11 +10,11 @@ source "${BASH_SOURCE%/*}/../../utils/zombienet.sh" ${BASH_SOURCE%/*}/../../environments/rococo-westend/spawn.sh & env_pid=$! -ensure_process_file $env_pid $TEST_DIR/rococo.env 400 +ensure_process_file $env_pid $TEST_DIR/rococo.env 600 rococo_dir=`cat $TEST_DIR/rococo.env` echo -ensure_process_file $env_pid $TEST_DIR/westend.env 180 +ensure_process_file $env_pid $TEST_DIR/westend.env 300 westend_dir=`cat $TEST_DIR/westend.env` echo diff --git a/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs b/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs index 1db826ea7da..15e8a1bf11a 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs @@ -25,7 +25,10 @@ use std::str::FromStr; #[derive(Debug, PartialEq)] pub enum BridgeHubRuntimeType { Kusama, + KusamaLocal, + Polkadot, + PolkadotLocal, Rococo, RococoLocal, @@ -44,7 +47,9 @@ impl FromStr for BridgeHubRuntimeType { fn from_str(value: &str) -> Result { match value { polkadot::BRIDGE_HUB_POLKADOT => Ok(BridgeHubRuntimeType::Polkadot), + polkadot::BRIDGE_HUB_POLKADOT_LOCAL => Ok(BridgeHubRuntimeType::PolkadotLocal), kusama::BRIDGE_HUB_KUSAMA => Ok(BridgeHubRuntimeType::Kusama), + kusama::BRIDGE_HUB_KUSAMA_LOCAL => Ok(BridgeHubRuntimeType::KusamaLocal), westend::BRIDGE_HUB_WESTEND => Ok(BridgeHubRuntimeType::Westend), westend::BRIDGE_HUB_WESTEND_LOCAL => Ok(BridgeHubRuntimeType::WestendLocal), westend::BRIDGE_HUB_WESTEND_DEVELOPMENT => Ok(BridgeHubRuntimeType::WestendDevelopment), @@ -103,6 +108,7 @@ impl BridgeHubRuntimeType { Some("Bob".to_string()), |_| (), ))), + other => Err(std::format!("No default config present for {:?}", other)), } } } @@ -242,6 +248,7 @@ pub mod rococo { /// Sub-module for Kusama setup pub mod kusama { pub(crate) const BRIDGE_HUB_KUSAMA: &str = "bridge-hub-kusama"; + pub(crate) const BRIDGE_HUB_KUSAMA_LOCAL: &str = "bridge-hub-kusama-local"; } /// Sub-module for Westend setup. @@ -358,4 +365,5 @@ pub mod westend { /// Sub-module for Polkadot setup pub mod polkadot { pub(crate) const BRIDGE_HUB_POLKADOT: &str = "bridge-hub-polkadot"; + pub(crate) const BRIDGE_HUB_POLKADOT_LOCAL: &str = "bridge-hub-polkadot-local"; } diff --git a/cumulus/polkadot-parachain/src/command.rs b/cumulus/polkadot-parachain/src/command.rs index acba32f048b..a40c356dcd1 100644 --- a/cumulus/polkadot-parachain/src/command.rs +++ b/cumulus/polkadot-parachain/src/command.rs @@ -755,14 +755,16 @@ pub fn run() -> Result<()> { .map_err(Into::into), BridgeHub(bridge_hub_runtime_type) => match bridge_hub_runtime_type { - chain_spec::bridge_hubs::BridgeHubRuntimeType::Polkadot => + chain_spec::bridge_hubs::BridgeHubRuntimeType::Polkadot | + chain_spec::bridge_hubs::BridgeHubRuntimeType::PolkadotLocal => crate::service::start_generic_aura_node::< RuntimeApi, AuraId, >(config, polkadot_config, collator_options, id, hwbench) .await .map(|r| r.0), - chain_spec::bridge_hubs::BridgeHubRuntimeType::Kusama => + chain_spec::bridge_hubs::BridgeHubRuntimeType::Kusama | + chain_spec::bridge_hubs::BridgeHubRuntimeType::KusamaLocal => crate::service::start_generic_aura_node::< RuntimeApi, AuraId, -- GitLab From 11b5354fd3ced03003da1901aba704791f367849 Mon Sep 17 00:00:00 2001 From: Ignacio Palacios Date: Fri, 23 Feb 2024 12:54:54 +0100 Subject: [PATCH 116/133] Fix `DepositReserveAsset` fees payment (#3340) The `fee` should be calculated with the reanchored asset, otherwise it could lead to a failure where the set aside fee ends up not being enough. @acatangiu --------- Co-authored-by: Adrian Catangiu --- Cargo.lock | 2 + polkadot/node/test/service/src/chain_spec.rs | 1 + .../runtime/test-runtime/src/xcm_config.rs | 54 +++++++++--- .../xcm-executor/integration-tests/Cargo.toml | 2 + .../xcm-executor/integration-tests/src/lib.rs | 83 +++++++++++++++++++ polkadot/xcm/xcm-executor/src/lib.rs | 4 +- 6 files changed, 132 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 950b4e0889a..d9791ec0147 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22323,10 +22323,12 @@ dependencies = [ "pallet-transaction-payment", "pallet-xcm", "parity-scale-codec", + "polkadot-service", "polkadot-test-client", "polkadot-test-runtime", "polkadot-test-service", "sp-consensus", + "sp-core", "sp-keyring", "sp-runtime", "sp-state-machine", diff --git a/polkadot/node/test/service/src/chain_spec.rs b/polkadot/node/test/service/src/chain_spec.rs index 0295090b952..4cc387317c3 100644 --- a/polkadot/node/test/service/src/chain_spec.rs +++ b/polkadot/node/test/service/src/chain_spec.rs @@ -169,6 +169,7 @@ fn polkadot_testnet_genesis( paras_availability_period: 4, no_show_slots: 10, minimum_validation_upgrade_delay: 5, + max_downward_message_size: 1024, ..Default::default() }, } diff --git a/polkadot/runtime/test-runtime/src/xcm_config.rs b/polkadot/runtime/test-runtime/src/xcm_config.rs index a81d35f4d78..a48bca17e9f 100644 --- a/polkadot/runtime/test-runtime/src/xcm_config.rs +++ b/polkadot/runtime/test-runtime/src/xcm_config.rs @@ -16,14 +16,16 @@ use frame_support::{ parameter_types, - traits::{Everything, Nothing}, + traits::{Everything, Get, Nothing}, weights::Weight, }; use frame_system::EnsureRoot; +use polkadot_runtime_parachains::FeeTracker; +use runtime_common::xcm_sender::{ChildParachainRouter, PriceForMessageDelivery}; use xcm::latest::prelude::*; use xcm_builder::{ AllowUnpaidExecutionFrom, EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, - SignedAccountId32AsNative, SignedToAccountId32, + SignedAccountId32AsNative, SignedToAccountId32, WithUniqueTopic, }; use xcm_executor::{ traits::{TransactAsset, WeightTrader}, @@ -36,6 +38,8 @@ parameter_types! { pub const MaxInstructions: u32 = 100; pub const MaxAssetsIntoHolding: u32 = 16; pub const UniversalLocation: xcm::latest::InteriorLocation = xcm::latest::Junctions::Here; + pub TokenLocation: Location = Here.into_location(); + pub FeeAssetId: AssetId = AssetId(TokenLocation::get()); } /// Type to convert an `Origin` type value into a `Location` value which represents an interior @@ -45,17 +49,43 @@ pub type LocalOriginToLocation = ( SignedToAccountId32, ); -pub struct DoNothingRouter; -impl SendXcm for DoNothingRouter { - type Ticket = (); - fn validate(_dest: &mut Option, _msg: &mut Option>) -> SendResult<()> { - Ok(((), Assets::new())) - } - fn deliver(_: ()) -> Result { - Ok([0; 32]) +/// Implementation of [`PriceForMessageDelivery`], returning a different price +/// based on whether a message contains a reanchored asset or not. +/// This implementation ensures that messages with non-reanchored assets return higher +/// prices than messages with reanchored assets. +/// Useful for `deposit_reserve_asset_works_for_any_xcm_sender` integration test. +pub struct TestDeliveryPrice(sp_std::marker::PhantomData<(A, F)>); +impl, F: FeeTracker> PriceForMessageDelivery for TestDeliveryPrice { + type Id = F::Id; + + fn price_for_delivery(_: Self::Id, msg: &Xcm<()>) -> Assets { + let base_fee: super::Balance = 1_000_000; + + let parents = msg.iter().find_map(|xcm| match xcm { + ReserveAssetDeposited(assets) => { + let AssetId(location) = &assets.inner().first().unwrap().id; + Some(location.parents) + }, + _ => None, + }); + + // If no asset is found, price defaults to `base_fee`. + let amount = base_fee + .saturating_add(base_fee.saturating_mul(parents.unwrap_or(0) as super::Balance)); + + (A::get(), amount).into() } } +pub type PriceForChildParachainDelivery = TestDeliveryPrice; + +/// The XCM router. When we want to send an XCM message, we use this type. It amalgamates all of our +/// individual routers. +pub type XcmRouter = WithUniqueTopic< + // Only one router so far - use DMP to communicate with child parachains. + ChildParachainRouter, +>; + pub type Barrier = AllowUnpaidExecutionFrom; pub struct DummyAssetTransactor; @@ -99,7 +129,7 @@ type OriginConverter = ( pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = super::RuntimeCall; - type XcmSender = DoNothingRouter; + type XcmSender = XcmRouter; type AssetTransactor = DummyAssetTransactor; type OriginConverter = OriginConverter; type IsReserve = (); @@ -133,7 +163,7 @@ impl pallet_xcm::Config for crate::Runtime { type UniversalLocation = UniversalLocation; type SendXcmOrigin = EnsureXcmOrigin; type Weigher = FixedWeightBounds; - type XcmRouter = DoNothingRouter; + type XcmRouter = XcmRouter; type XcmExecuteFilter = Everything; type XcmExecutor = xcm_executor::XcmExecutor; type XcmTeleportFilter = Everything; diff --git a/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml b/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml index cafe12dc587..1e572e6210a 100644 --- a/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml +++ b/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml @@ -20,6 +20,7 @@ pallet-xcm = { path = "../../pallet-xcm" } polkadot-test-client = { path = "../../../node/test/client" } polkadot-test-runtime = { path = "../../../runtime/test-runtime" } polkadot-test-service = { path = "../../../node/test/service" } +polkadot-service = { path = "../../../node/service" } sp-consensus = { path = "../../../../substrate/primitives/consensus/common" } sp-keyring = { path = "../../../../substrate/primitives/keyring" } sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } @@ -27,6 +28,7 @@ sp-state-machine = { path = "../../../../substrate/primitives/state-machine" } xcm = { package = "staging-xcm", path = "../..", default-features = false } xcm-executor = { package = "staging-xcm-executor", path = ".." } sp-tracing = { path = "../../../../substrate/primitives/tracing" } +sp-core = { path = "../../../../substrate/primitives/core" } [features] default = ["std"] diff --git a/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs b/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs index 79d6cb1c411..da7fc0d9782 100644 --- a/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs @@ -19,12 +19,14 @@ use codec::Encode; use frame_support::{dispatch::GetDispatchInfo, weights::Weight}; +use polkadot_service::chain_spec::get_account_id_from_seed; use polkadot_test_client::{ BlockBuilderExt, ClientBlockImportExt, DefaultTestClientBuilderExt, InitPolkadotBlockBuilder, TestClientBuilder, TestClientBuilderExt, }; use polkadot_test_runtime::{pallet_test_notifier, xcm_config::XcmConfig}; use polkadot_test_service::construct_extrinsic; +use sp_core::sr25519; use sp_runtime::traits::Block; use sp_state_machine::InspectState; use xcm::{latest::prelude::*, VersionedResponse, VersionedXcm}; @@ -323,3 +325,84 @@ fn query_response_elicits_handler() { ))); }); } + +/// Simulates a cross-chain message from Parachain to Parachain through Relay Chain +/// that deposits assets into the reserve of the destination. +/// Regression test for `DepostiReserveAsset` changes in +/// +#[test] +fn deposit_reserve_asset_works_for_any_xcm_sender() { + sp_tracing::try_init_simple(); + let mut client = TestClientBuilder::new().build(); + + // Init values for the simulated origin Parachain + let amount_to_send: u128 = 1_000_000_000_000; + let assets: Assets = (Parent, amount_to_send).into(); + let fee_asset_item = 0; + let max_assets = assets.len() as u32; + let fees = assets.get(fee_asset_item as usize).unwrap().clone(); + let weight_limit = Unlimited; + let reserve = Location::parent(); + let dest = Location::new(1, [Parachain(2000)]); + let beneficiary_id = get_account_id_from_seed::("Alice"); + let beneficiary = Location::new(0, [AccountId32 { network: None, id: beneficiary_id.into() }]); + + // spends up to half of fees for execution on reserve and other half for execution on + // destination + let fee1 = amount_to_send.saturating_div(2); + let fee2 = amount_to_send.saturating_sub(fee1); + let fees_half_1 = Asset::from((fees.id.clone(), Fungible(fee1))); + let fees_half_2 = Asset::from((fees.id.clone(), Fungible(fee2))); + + let reserve_context = ::UniversalLocation::get(); + // identifies fee item as seen by `reserve` - to be used at reserve chain + let reserve_fees = fees_half_1.reanchored(&reserve, &reserve_context).unwrap(); + // identifies fee item as seen by `dest` - to be used at destination chain + let dest_fees = fees_half_2.reanchored(&dest, &reserve_context).unwrap(); + // identifies assets as seen by `reserve` - to be used at reserve chain + let assets_reanchored = assets.reanchored(&reserve, &reserve_context).unwrap(); + // identifies `dest` as seen by `reserve` + let dest = dest.reanchored(&reserve, &reserve_context).unwrap(); + // xcm to be executed at dest + let xcm_on_dest = Xcm(vec![ + BuyExecution { fees: dest_fees, weight_limit: weight_limit.clone() }, + DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }, + ]); + // xcm to be executed at reserve + let msg = Xcm(vec![ + WithdrawAsset(assets_reanchored), + ClearOrigin, + BuyExecution { fees: reserve_fees, weight_limit }, + DepositReserveAsset { assets: Wild(AllCounted(max_assets)), dest, xcm: xcm_on_dest }, + ]); + + let mut block_builder = client.init_polkadot_block_builder(); + + // Simulate execution of an incoming XCM message at the reserve chain + let execute = construct_extrinsic( + &client, + polkadot_test_runtime::RuntimeCall::Xcm(pallet_xcm::Call::execute { + message: Box::new(VersionedXcm::from(msg)), + max_weight: Weight::from_parts(1_000_000_000, 1024 * 1024), + }), + sp_keyring::Sr25519Keyring::Alice, + 0, + ); + + block_builder.push_polkadot_extrinsic(execute).expect("pushes extrinsic"); + + let block = block_builder.build().expect("Finalizes the block").block; + let block_hash = block.hash(); + + futures::executor::block_on(client.import(sp_consensus::BlockOrigin::Own, block)) + .expect("imports the block"); + + client.state_at(block_hash).expect("state should exist").inspect_state(|| { + assert!(polkadot_test_runtime::System::events().iter().any(|r| matches!( + r.event, + polkadot_test_runtime::RuntimeEvent::Xcm(pallet_xcm::Event::Attempted { + outcome: Outcome::Complete { .. } + }), + ))); + }); +} diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index b26779f3ae9..c61e1e1d15b 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -826,9 +826,9 @@ impl XcmExecutor { // be weighed let to_weigh = self.holding.saturating_take(assets.clone()); self.holding.subsume_assets(to_weigh.clone()); - + let to_weigh_reanchored = Self::reanchored(to_weigh, &dest, None); let mut message_to_weigh = - vec![ReserveAssetDeposited(to_weigh.into()), ClearOrigin]; + vec![ReserveAssetDeposited(to_weigh_reanchored), ClearOrigin]; message_to_weigh.extend(xcm.0.clone().into_iter()); let (_, fee) = validate_send::(dest.clone(), Xcm(message_to_weigh))?; -- GitLab From 64050cf3474adb8705991f8e3a0e2fd36bf5dcd4 Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Fri, 23 Feb 2024 14:04:10 +0100 Subject: [PATCH 117/133] prdoc: Print errors on CI (#3459) By passing `RUST_LOG=info` to the check command, we will be able to see the exact problem with a given prdoc. Before: ``` PR #3243 -> ERR ``` After: ``` [2024-02-23T12:53:55Z INFO prdoclib::commands::check] Checking directory prdoc [2024-02-23T12:53:55Z INFO prdoclib::commands::check] Using schema: /Users/sebastian/work/repos/polkadot-sdk/prdoc/schema_user.json [2024-02-23T12:53:55Z WARN prdoclib::schema] validation_result: false [2024-02-23T12:53:55Z WARN prdoclib::schema] validation_result_strict: false [2024-02-23T12:53:55Z WARN prdoclib::schema] errors: [ Required { path: "/title", }, ] [2024-02-23T12:53:55Z WARN prdoclib::schema] missing: [] [2024-02-23T12:53:55Z ERROR prdoclib::commands::check] Loading the schema failed: [2024-02-23T12:53:55Z ERROR prdoclib::commands::check] ValidationErrors(ValidationState { errors: [Required { path: "/title" }], missing: [], replacement: None, evaluated: {"/doc/0/description", "/crates/0/name", "/doc/0", "/crates", "/crates/0", "", "/doc", "/doc/0/audience"} }) PR #3243 -> ERR ``` --- .github/workflows/check-prdoc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-prdoc.yml b/.github/workflows/check-prdoc.yml index 5503b61d681..91701ca036e 100644 --- a/.github/workflows/check-prdoc.yml +++ b/.github/workflows/check-prdoc.yml @@ -56,4 +56,4 @@ jobs: run: | echo "Checking for PR#${GITHUB_PR}" echo "You can find more information about PRDoc at $PRDOC_DOC" - $ENGINE run --rm -v $PWD:/repo $IMAGE check -n ${GITHUB_PR} + $ENGINE run --rm -v $PWD:/repo -e RUST_LOG=info $IMAGE check -n ${GITHUB_PR} -- GitLab From f2645eec12f080a66228aedbeaecea2c913570ed Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Fri, 23 Feb 2024 14:10:06 +0100 Subject: [PATCH 118/133] Contracts Add new version for marking new stable API (#3415) Add a `ApiVersion` constant to the pallet-contracts Config to communicate with developers the current state of the host functions exposed by the pallet --- .../contracts-rococo/src/contracts.rs | 1 + prdoc/pr_3415.prdoc | 9 +++++++++ substrate/bin/node/runtime/src/lib.rs | 1 + .../src/parachain/contracts_config.rs | 1 + substrate/frame/contracts/src/lib.rs | 18 ++++++++++++++++++ substrate/frame/contracts/src/tests.rs | 1 + 6 files changed, 31 insertions(+) create mode 100644 prdoc/pr_3415.prdoc diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs index 7b89f2df807..681b95ce6a5 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs @@ -72,5 +72,6 @@ impl Config for Runtime { type RuntimeHoldReason = RuntimeHoldReason; type Debug = (); type Environment = (); + type ApiVersion = (); type Xcm = pallet_xcm::Pallet; } diff --git a/prdoc/pr_3415.prdoc b/prdoc/pr_3415.prdoc new file mode 100644 index 00000000000..c56a5d3ffaa --- /dev/null +++ b/prdoc/pr_3415.prdoc @@ -0,0 +1,9 @@ +title: "[pallet-contracts] Add APIVersion to the config." + +doc: + - audience: Runtime Dev + description: | + Add `APIVersion` to the config to communicate the state of the Host functions exposed by the pallet. + +crates: + - name: pallet-contracts diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 24c8f6f48e9..b34d8c89e56 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1367,6 +1367,7 @@ impl pallet_contracts::Config for Runtime { type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type Debug = (); type Environment = (); + type ApiVersion = (); type Xcm = (); } diff --git a/substrate/frame/contracts/mock-network/src/parachain/contracts_config.rs b/substrate/frame/contracts/mock-network/src/parachain/contracts_config.rs index dadba394e26..3f26c6f372e 100644 --- a/substrate/frame/contracts/mock-network/src/parachain/contracts_config.rs +++ b/substrate/frame/contracts/mock-network/src/parachain/contracts_config.rs @@ -94,5 +94,6 @@ impl pallet_contracts::Config for Runtime { type WeightPrice = Self; type Debug = (); type Environment = (); + type ApiVersion = (); type Xcm = pallet_xcm::Pallet; } diff --git a/substrate/frame/contracts/src/lib.rs b/substrate/frame/contracts/src/lib.rs index 943bf5f85d4..d8939ee88ba 100644 --- a/substrate/frame/contracts/src/lib.rs +++ b/substrate/frame/contracts/src/lib.rs @@ -214,6 +214,18 @@ pub struct Environment { block_number: EnvironmentType>, } +/// Defines the current version of the HostFn APIs. +/// This is used to communicate the available APIs in pallet-contracts. +/// +/// The version is bumped any time a new HostFn is added or stabilized. +#[derive(Encode, Decode, TypeInfo)] +pub struct ApiVersion(u16); +impl Default for ApiVersion { + fn default() -> Self { + Self(1) + } +} + #[frame_support::pallet] pub mod pallet { use super::*; @@ -402,6 +414,12 @@ pub mod pallet { #[pallet::constant] type Environment: Get>; + /// The version of the HostFn APIs that are available in the runtime. + /// + /// Only valid value is `()`. + #[pallet::constant] + type ApiVersion: Get; + /// A type that exposes XCM APIs, allowing contracts to interact with other parachains, and /// execute XCM programs. type Xcm: xcm_builder::Controller< diff --git a/substrate/frame/contracts/src/tests.rs b/substrate/frame/contracts/src/tests.rs index b39fc79b62a..2d1c5b315a3 100644 --- a/substrate/frame/contracts/src/tests.rs +++ b/substrate/frame/contracts/src/tests.rs @@ -465,6 +465,7 @@ impl Config for Test { type MaxDelegateDependencies = MaxDelegateDependencies; type Debug = TestDebug; type Environment = (); + type ApiVersion = (); type Xcm = (); } -- GitLab From 5fc6d67be081b4f2638d3d46d0002f95392b370c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dino=20Pa=C4=8Dandi?= <3002868+Dinonard@users.noreply.github.com> Date: Fri, 23 Feb 2024 14:56:32 +0100 Subject: [PATCH 119/133] pallet-membership weights (#3324) ## Summary * use benchamarked weights instead of hardcoded ones for `pallet-membership` * rename benchmark to match extrinsic name * remove unnecessary dependency from `clear_prime` --------- Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Oliver Tale-Yazdi --- prdoc/pr_3324.prdoc | 13 +++ substrate/frame/membership/src/lib.rs | 124 ++++++++++++++-------- substrate/frame/membership/src/weights.rs | 18 ++-- 3 files changed, 99 insertions(+), 56 deletions(-) create mode 100644 prdoc/pr_3324.prdoc diff --git a/prdoc/pr_3324.prdoc b/prdoc/pr_3324.prdoc new file mode 100644 index 00000000000..0425fbf317c --- /dev/null +++ b/prdoc/pr_3324.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Fix weight calculation and event emission in pallet-membership" + +doc: + - audience: Runtime Dev + description: | + Bug fix for the membership pallet to use correct weights. Also no event will be emitted + anymore when `change_key` is called with identical accounts. + +crates: + - name: pallet-membership diff --git a/substrate/frame/membership/src/lib.rs b/substrate/frame/membership/src/lib.rs index 19b5e54d308..f3226397003 100644 --- a/substrate/frame/membership/src/lib.rs +++ b/substrate/frame/membership/src/lib.rs @@ -27,7 +27,7 @@ use frame_support::{ traits::{ChangeMembers, Contains, Get, InitializeMembers, SortedMembers}, BoundedVec, }; -use sp_runtime::traits::StaticLookup; +use sp_runtime::traits::{StaticLookup, UniqueSaturatedInto}; use sp_std::prelude::*; pub mod migrations; @@ -163,12 +163,16 @@ pub mod pallet { /// /// May only be called from `T::AddOrigin`. #[pallet::call_index(0)] - #[pallet::weight({50_000_000})] - pub fn add_member(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { + #[pallet::weight(T::WeightInfo::add_member(T::MaxMembers::get()))] + pub fn add_member( + origin: OriginFor, + who: AccountIdLookupOf, + ) -> DispatchResultWithPostInfo { T::AddOrigin::ensure_origin(origin)?; let who = T::Lookup::lookup(who)?; let mut members = >::get(); + let init_length = members.len(); let location = members.binary_search(&who).err().ok_or(Error::::AlreadyMember)?; members .try_insert(location, who.clone()) @@ -179,19 +183,24 @@ pub mod pallet { T::MembershipChanged::change_members_sorted(&[who], &[], &members[..]); Self::deposit_event(Event::MemberAdded); - Ok(()) + + Ok(Some(T::WeightInfo::add_member(init_length as u32)).into()) } /// Remove a member `who` from the set. /// /// May only be called from `T::RemoveOrigin`. #[pallet::call_index(1)] - #[pallet::weight({50_000_000})] - pub fn remove_member(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { + #[pallet::weight(T::WeightInfo::remove_member(T::MaxMembers::get()))] + pub fn remove_member( + origin: OriginFor, + who: AccountIdLookupOf, + ) -> DispatchResultWithPostInfo { T::RemoveOrigin::ensure_origin(origin)?; let who = T::Lookup::lookup(who)?; let mut members = >::get(); + let init_length = members.len(); let location = members.binary_search(&who).ok().ok_or(Error::::NotMember)?; members.remove(location); @@ -201,7 +210,7 @@ pub mod pallet { Self::rejig_prime(&members); Self::deposit_event(Event::MemberRemoved); - Ok(()) + Ok(Some(T::WeightInfo::remove_member(init_length as u32)).into()) } /// Swap out one member `remove` for another `add`. @@ -210,18 +219,18 @@ pub mod pallet { /// /// Prime membership is *not* passed from `remove` to `add`, if extant. #[pallet::call_index(2)] - #[pallet::weight({50_000_000})] + #[pallet::weight(T::WeightInfo::swap_member(T::MaxMembers::get()))] pub fn swap_member( origin: OriginFor, remove: AccountIdLookupOf, add: AccountIdLookupOf, - ) -> DispatchResult { + ) -> DispatchResultWithPostInfo { T::SwapOrigin::ensure_origin(origin)?; let remove = T::Lookup::lookup(remove)?; let add = T::Lookup::lookup(add)?; if remove == add { - return Ok(()) + return Ok(().into()); } let mut members = >::get(); @@ -236,7 +245,7 @@ pub mod pallet { Self::rejig_prime(&members); Self::deposit_event(Event::MembersSwapped); - Ok(()) + Ok(Some(T::WeightInfo::swap_member(members.len() as u32)).into()) } /// Change the membership to a new set, disregarding the existing membership. Be nice and @@ -244,7 +253,7 @@ pub mod pallet { /// /// May only be called from `T::ResetOrigin`. #[pallet::call_index(3)] - #[pallet::weight({50_000_000})] + #[pallet::weight(T::WeightInfo::reset_members(members.len().unique_saturated_into()))] pub fn reset_members(origin: OriginFor, members: Vec) -> DispatchResult { T::ResetOrigin::ensure_origin(origin)?; @@ -267,56 +276,65 @@ pub mod pallet { /// /// Prime membership is passed from the origin account to `new`, if extant. #[pallet::call_index(4)] - #[pallet::weight({50_000_000})] - pub fn change_key(origin: OriginFor, new: AccountIdLookupOf) -> DispatchResult { + #[pallet::weight(T::WeightInfo::change_key(T::MaxMembers::get()))] + pub fn change_key( + origin: OriginFor, + new: AccountIdLookupOf, + ) -> DispatchResultWithPostInfo { let remove = ensure_signed(origin)?; let new = T::Lookup::lookup(new)?; - if remove != new { - let mut members = >::get(); - let location = - members.binary_search(&remove).ok().ok_or(Error::::NotMember)?; - let _ = members.binary_search(&new).err().ok_or(Error::::AlreadyMember)?; - members[location] = new.clone(); - members.sort(); - - >::put(&members); - - T::MembershipChanged::change_members_sorted( - &[new.clone()], - &[remove.clone()], - &members[..], - ); - - if Prime::::get() == Some(remove) { - Prime::::put(&new); - T::MembershipChanged::set_prime(Some(new)); - } + if remove == new { + return Ok(().into()); + } + + let mut members = >::get(); + let members_length = members.len() as u32; + let location = members.binary_search(&remove).ok().ok_or(Error::::NotMember)?; + let _ = members.binary_search(&new).err().ok_or(Error::::AlreadyMember)?; + members[location] = new.clone(); + members.sort(); + + >::put(&members); + + T::MembershipChanged::change_members_sorted( + &[new.clone()], + &[remove.clone()], + &members[..], + ); + + if Prime::::get() == Some(remove) { + Prime::::put(&new); + T::MembershipChanged::set_prime(Some(new)); } Self::deposit_event(Event::KeyChanged); - Ok(()) + Ok(Some(T::WeightInfo::change_key(members_length)).into()) } /// Set the prime member. Must be a current member. /// /// May only be called from `T::PrimeOrigin`. #[pallet::call_index(5)] - #[pallet::weight({50_000_000})] - pub fn set_prime(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { + #[pallet::weight(T::WeightInfo::set_prime(T::MaxMembers::get()))] + pub fn set_prime( + origin: OriginFor, + who: AccountIdLookupOf, + ) -> DispatchResultWithPostInfo { T::PrimeOrigin::ensure_origin(origin)?; let who = T::Lookup::lookup(who)?; - Self::members().binary_search(&who).ok().ok_or(Error::::NotMember)?; + let members = Self::members(); + members.binary_search(&who).ok().ok_or(Error::::NotMember)?; Prime::::put(&who); T::MembershipChanged::set_prime(Some(who)); - Ok(()) + Ok(Some(T::WeightInfo::set_prime(members.len() as u32)).into()) } /// Remove the prime member if it exists. /// /// May only be called from `T::PrimeOrigin`. #[pallet::call_index(6)] - #[pallet::weight({50_000_000})] + #[pallet::weight(T::WeightInfo::clear_prime())] pub fn clear_prime(origin: OriginFor) -> DispatchResult { T::PrimeOrigin::ensure_origin(origin)?; Prime::::kill(); @@ -442,7 +460,7 @@ mod benchmark { } // er keep the prime common between incoming and outgoing to make sure it is rejigged. - reset_member { + reset_members { let m in 1 .. T::MaxMembers::get(); let members = (1..m+1).map(|i| account("member", i, SEED)).collect::>(); @@ -500,8 +518,7 @@ mod benchmark { } clear_prime { - let m in 1 .. T::MaxMembers::get(); - let members = (0..m).map(|i| account("member", i, SEED)).collect::>(); + let members = (0..T::MaxMembers::get()).map(|i| account("member", i, SEED)).collect::>(); let prime = members.last().cloned().unwrap(); set_members::(members, None); }: { @@ -526,7 +543,8 @@ mod tests { use sp_runtime::{bounded_vec, traits::BadOrigin, BuildStorage}; use frame_support::{ - assert_noop, assert_ok, derive_impl, ord_parameter_types, parameter_types, + assert_noop, assert_ok, assert_storage_noop, derive_impl, ord_parameter_types, + parameter_types, traits::{ConstU32, StorageVersion}, }; use frame_system::EnsureSignedBy; @@ -716,6 +734,17 @@ mod tests { }); } + #[test] + fn swap_member_with_identical_arguments_changes_nothing() { + new_test_ext().execute_with(|| { + assert_storage_noop!(assert_ok!(Membership::swap_member( + RuntimeOrigin::signed(3), + 10, + 10 + ))); + }); + } + #[test] fn change_key_works() { new_test_ext().execute_with(|| { @@ -745,6 +774,13 @@ mod tests { }); } + #[test] + fn change_key_with_same_caller_as_argument_changes_nothing() { + new_test_ext().execute_with(|| { + assert_storage_noop!(assert_ok!(Membership::change_key(RuntimeOrigin::signed(10), 10))); + }); + } + #[test] fn reset_members_works() { new_test_ext().execute_with(|| { diff --git a/substrate/frame/membership/src/weights.rs b/substrate/frame/membership/src/weights.rs index 18ea7fcb315..2d18848b89a 100644 --- a/substrate/frame/membership/src/weights.rs +++ b/substrate/frame/membership/src/weights.rs @@ -55,10 +55,10 @@ pub trait WeightInfo { fn add_member(m: u32, ) -> Weight; fn remove_member(m: u32, ) -> Weight; fn swap_member(m: u32, ) -> Weight; - fn reset_member(m: u32, ) -> Weight; + fn reset_members(m: u32, ) -> Weight; fn change_key(m: u32, ) -> Weight; fn set_prime(m: u32, ) -> Weight; - fn clear_prime(m: u32, ) -> Weight; + fn clear_prime() -> Weight; } /// Weights for pallet_membership using the Substrate node and recommended hardware. @@ -142,7 +142,7 @@ impl WeightInfo for SubstrateWeight { /// Storage: TechnicalCommittee Prime (r:0 w:1) /// Proof Skipped: TechnicalCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[1, 100]`. - fn reset_member(m: u32, ) -> Weight { + fn reset_members(m: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `312 + m * (64 ±0)` // Estimated: `4687 + m * (64 ±0)` @@ -200,15 +200,12 @@ impl WeightInfo for SubstrateWeight { /// Proof: TechnicalMembership Prime (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) /// Storage: TechnicalCommittee Prime (r:0 w:1) /// Proof Skipped: TechnicalCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) - /// The range of component `m` is `[1, 100]`. - fn clear_prime(m: u32, ) -> Weight { + fn clear_prime() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` // Minimum execution time: 3_373_000 picoseconds. Weight::from_parts(3_750_452, 0) - // Standard Error: 142 - .saturating_add(Weight::from_parts(505, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().writes(2_u64)) } } @@ -293,7 +290,7 @@ impl WeightInfo for () { /// Storage: TechnicalCommittee Prime (r:0 w:1) /// Proof Skipped: TechnicalCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[1, 100]`. - fn reset_member(m: u32, ) -> Weight { + fn reset_members(m: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `312 + m * (64 ±0)` // Estimated: `4687 + m * (64 ±0)` @@ -351,15 +348,12 @@ impl WeightInfo for () { /// Proof: TechnicalMembership Prime (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) /// Storage: TechnicalCommittee Prime (r:0 w:1) /// Proof Skipped: TechnicalCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) - /// The range of component `m` is `[1, 100]`. - fn clear_prime(m: u32, ) -> Weight { + fn clear_prime() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` // Minimum execution time: 3_373_000 picoseconds. Weight::from_parts(3_750_452, 0) - // Standard Error: 142 - .saturating_add(Weight::from_parts(505, 0).saturating_mul(m.into())) .saturating_add(RocksDbWeight::get().writes(2_u64)) } } -- GitLab From 3386377b0f88cb2c7cfafe8a5f5b3b41b85dde59 Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Fri, 23 Feb 2024 15:09:49 +0100 Subject: [PATCH 120/133] PoV Reclaim Runtime Side (#3002) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Runtime side for PoV Reclaim ## Implementation Overview - Hostfunction to fetch the storage proof size has been added to the PVF. It uses the size tracking recorder that was introduced in my previous PR. - Mechanisms to use the reclaim HostFunction have been introduced. - 1. A SignedExtension that checks the node-reported proof size before and after application of an extrinsic. Then it reclaims the difference. - 2. A manual helper to make reclaiming easier when manual interaction is required, for example in `on_idle` or other hooks. - In order to utilize the manual reclaiming, I modified `WeightMeter` to support the reduction of consumed weight, at least for storage proof size. ## How to use To enable the general functionality for a parachain: 1. Add the SignedExtension to your parachain runtime. 2. Provide the HostFunction to the node 3. Enable proof recording during block import ## TODO - [x] PRDoc --------- Co-authored-by: Dmitry Markin Co-authored-by: Davide Galassi Co-authored-by: Bastian Köcher --- .gitlab/pipeline/check.yml | 4 +- Cargo.lock | 23 + Cargo.toml | 1 + .../src/validate_block/implementation.rs | 29 +- .../src/validate_block/tests.rs | 2 +- .../src/validate_block/trie_recorder.rs | 11 + .../parachain-template/node/src/command.rs | 3 +- .../parachain-template/node/src/service.rs | 8 +- cumulus/parachain-template/runtime/Cargo.toml | 2 + cumulus/parachain-template/runtime/src/lib.rs | 1 + cumulus/polkadot-parachain/src/command.rs | 3 +- cumulus/polkadot-parachain/src/service.rs | 13 +- .../proof-size-hostfunction/src/lib.rs | 4 +- .../storage-weight-reclaim/Cargo.toml | 46 ++ .../storage-weight-reclaim/src/lib.rs | 663 ++++++++++++++++++ cumulus/test/client/Cargo.toml | 1 + cumulus/test/client/src/lib.rs | 20 +- cumulus/test/runtime/Cargo.toml | 2 + cumulus/test/runtime/src/lib.rs | 1 + cumulus/test/service/Cargo.toml | 1 + cumulus/test/service/src/lib.rs | 5 +- prdoc/pr_3002.prdoc | 29 + substrate/frame/system/src/lib.rs | 3 +- .../primitives/weights/src/weight_meter.rs | 20 + substrate/test-utils/client/src/lib.rs | 9 + 25 files changed, 875 insertions(+), 29 deletions(-) create mode 100644 cumulus/primitives/storage-weight-reclaim/Cargo.toml create mode 100644 cumulus/primitives/storage-weight-reclaim/src/lib.rs create mode 100644 prdoc/pr_3002.prdoc diff --git a/.gitlab/pipeline/check.yml b/.gitlab/pipeline/check.yml index cdb5d1b05d0..4d71a473372 100644 --- a/.gitlab/pipeline/check.yml +++ b/.gitlab/pipeline/check.yml @@ -108,8 +108,10 @@ check-toml-format: export RUST_LOG=remote-ext=debug,runtime=debug echo "---------- Downloading try-runtime CLI ----------" - curl -sL https://github.com/paritytech/try-runtime-cli/releases/download/v0.5.0/try-runtime-x86_64-unknown-linux-musl -o try-runtime + curl -sL https://github.com/paritytech/try-runtime-cli/releases/download/v0.5.4/try-runtime-x86_64-unknown-linux-musl -o try-runtime chmod +x ./try-runtime + echo "Using try-runtime-cli version:" + ./try-runtime --version echo "---------- Building ${PACKAGE} runtime ----------" time cargo build --release --locked -p "$PACKAGE" --features try-runtime diff --git a/Cargo.lock b/Cargo.lock index d9791ec0147..fbe2729acba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4047,6 +4047,25 @@ dependencies = [ "sp-trie", ] +[[package]] +name = "cumulus-primitives-storage-weight-reclaim" +version = "1.0.0" +dependencies = [ + "cumulus-primitives-core", + "cumulus-primitives-proof-size-hostfunction", + "cumulus-test-runtime", + "docify", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "sp-std 14.0.0", + "sp-trie", +] + [[package]] name = "cumulus-primitives-timestamp" version = "0.7.0" @@ -4209,6 +4228,7 @@ dependencies = [ "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", + "cumulus-primitives-storage-weight-reclaim", "cumulus-test-relay-sproof-builder", "cumulus-test-runtime", "cumulus-test-service", @@ -4253,6 +4273,7 @@ version = "0.1.0" dependencies = [ "cumulus-pallet-parachain-system", "cumulus-primitives-core", + "cumulus-primitives-storage-weight-reclaim", "frame-executive", "frame-support", "frame-system", @@ -4295,6 +4316,7 @@ dependencies = [ "cumulus-client-service", "cumulus-pallet-parachain-system", "cumulus-primitives-core", + "cumulus-primitives-storage-weight-reclaim", "cumulus-relay-chain-inprocess-interface", "cumulus-relay-chain-interface", "cumulus-relay-chain-minimal-node", @@ -11340,6 +11362,7 @@ dependencies = [ "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-primitives-core", + "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-utility", "frame-benchmarking", "frame-executive", diff --git a/Cargo.toml b/Cargo.toml index 774ce1b52a3..1d27cfe9539 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -127,6 +127,7 @@ members = [ "cumulus/primitives/core", "cumulus/primitives/parachain-inherent", "cumulus/primitives/proof-size-hostfunction", + "cumulus/primitives/storage-weight-reclaim", "cumulus/primitives/timestamp", "cumulus/primitives/utility", "cumulus/test/client", diff --git a/cumulus/pallets/parachain-system/src/validate_block/implementation.rs b/cumulus/pallets/parachain-system/src/validate_block/implementation.rs index ce3b724420f..ecab7a9a093 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/implementation.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/implementation.rs @@ -16,7 +16,7 @@ //! The actual implementation of the validate block functionality. -use super::{trie_cache, MemoryOptimizedValidationParams}; +use super::{trie_cache, trie_recorder, MemoryOptimizedValidationParams}; use cumulus_primitives_core::{ relay_chain::Hash as RHash, ParachainBlockData, PersistedValidationData, }; @@ -34,12 +34,14 @@ use sp_externalities::{set_and_run_with_externalities, Externalities}; use sp_io::KillStorageResult; use sp_runtime::traits::{Block as BlockT, Extrinsic, HashingFor, Header as HeaderT}; use sp_std::prelude::*; -use sp_trie::MemoryDB; +use sp_trie::{MemoryDB, ProofSizeProvider}; +use trie_recorder::SizeOnlyRecorderProvider; type TrieBackend = sp_state_machine::TrieBackend< MemoryDB>, HashingFor, trie_cache::CacheProvider>, + SizeOnlyRecorderProvider>, >; type Ext<'a, B> = sp_state_machine::Ext<'a, HashingFor, TrieBackend>; @@ -48,6 +50,9 @@ fn with_externalities R, R>(f: F) -> R { sp_externalities::with_externalities(f).expect("Environmental externalities not set.") } +// Recorder instance to be used during this validate_block call. +environmental::environmental!(recorder: trait ProofSizeProvider); + /// Validate the given parachain block. /// /// This function is doing roughly the following: @@ -120,6 +125,7 @@ where sp_std::mem::drop(storage_proof); + let mut recorder = SizeOnlyRecorderProvider::new(); let cache_provider = trie_cache::CacheProvider::new(); // We use the storage root of the `parent_head` to ensure that it is the correct root. // This is already being done above while creating the in-memory db, but let's be paranoid!! @@ -128,6 +134,7 @@ where *parent_header.state_root(), cache_provider, ) + .with_recorder(recorder.clone()) .build(); let _guard = ( @@ -167,9 +174,11 @@ where .replace_implementation(host_default_child_storage_next_key), sp_io::offchain_index::host_set.replace_implementation(host_offchain_index_set), sp_io::offchain_index::host_clear.replace_implementation(host_offchain_index_clear), + cumulus_primitives_proof_size_hostfunction::storage_proof_size::host_storage_proof_size + .replace_implementation(host_storage_proof_size), ); - run_with_externalities::(&backend, || { + run_with_externalities_and_recorder::(&backend, &mut recorder, || { let relay_chain_proof = crate::RelayChainStateProof::new( PSC::SelfParaId::get(), inherent_data.validation_data.relay_parent_storage_root, @@ -190,7 +199,7 @@ where } }); - run_with_externalities::(&backend, || { + run_with_externalities_and_recorder::(&backend, &mut recorder, || { let head_data = HeadData(block.header().encode()); E::execute_block(block); @@ -265,15 +274,17 @@ fn validate_validation_data( ); } -/// Run the given closure with the externalities set. -fn run_with_externalities R>( +/// Run the given closure with the externalities and recorder set. +fn run_with_externalities_and_recorder R>( backend: &TrieBackend, + recorder: &mut SizeOnlyRecorderProvider>, execute: F, ) -> R { let mut overlay = sp_state_machine::OverlayedChanges::default(); let mut ext = Ext::::new(&mut overlay, backend); + recorder.reset(); - set_and_run_with_externalities(&mut ext, || execute()) + recorder::using(recorder, || set_and_run_with_externalities(&mut ext, || execute())) } fn host_storage_read(key: &[u8], value_out: &mut [u8], value_offset: u32) -> Option { @@ -305,6 +316,10 @@ fn host_storage_clear(key: &[u8]) { with_externalities(|ext| ext.place_storage(key.to_vec(), None)) } +fn host_storage_proof_size() -> u64 { + recorder::with(|rec| rec.estimate_encoded_size()).expect("Recorder is always set; qed") as _ +} + fn host_storage_root(version: StateVersion) -> Vec { with_externalities(|ext| ext.storage_root(version)) } diff --git a/cumulus/pallets/parachain-system/src/validate_block/tests.rs b/cumulus/pallets/parachain-system/src/validate_block/tests.rs index f17ac6007a0..a9fb65e1108 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/tests.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/tests.rs @@ -59,7 +59,7 @@ fn call_validate_block( } fn create_test_client() -> (Client, Header) { - let client = TestClientBuilder::new().build(); + let client = TestClientBuilder::new().enable_import_proof_recording().build(); let genesis_header = client .header(client.chain_info().genesis_hash) diff --git a/cumulus/pallets/parachain-system/src/validate_block/trie_recorder.rs b/cumulus/pallets/parachain-system/src/validate_block/trie_recorder.rs index e73aef70aa4..48310670c07 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/trie_recorder.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/trie_recorder.rs @@ -97,6 +97,7 @@ pub(crate) struct SizeOnlyRecorderProvider { } impl SizeOnlyRecorderProvider { + /// Create a new instance of [`SizeOnlyRecorderProvider`] pub fn new() -> Self { Self { seen_nodes: Default::default(), @@ -104,6 +105,13 @@ impl SizeOnlyRecorderProvider { recorded_keys: Default::default(), } } + + /// Reset the internal state. + pub fn reset(&self) { + self.seen_nodes.borrow_mut().clear(); + *self.encoded_size.borrow_mut() = 0; + self.recorded_keys.borrow_mut().clear(); + } } impl sp_trie::TrieRecorderProvider for SizeOnlyRecorderProvider { @@ -281,6 +289,9 @@ mod tests { reference_recorder.estimate_encoded_size(), recorder_for_test.estimate_encoded_size() ); + + recorder_for_test.reset(); + assert_eq!(recorder_for_test.estimate_encoded_size(), 0) } } } diff --git a/cumulus/parachain-template/node/src/command.rs b/cumulus/parachain-template/node/src/command.rs index 72b3ab7bb4b..82624ae0be5 100644 --- a/cumulus/parachain-template/node/src/command.rs +++ b/cumulus/parachain-template/node/src/command.rs @@ -1,5 +1,6 @@ use std::net::SocketAddr; +use cumulus_client_service::storage_proof_size::HostFunctions as ReclaimHostFunctions; use cumulus_primitives_core::ParaId; use frame_benchmarking_cli::{BenchmarkCmd, SUBSTRATE_REFERENCE_HARDWARE}; use log::info; @@ -183,7 +184,7 @@ pub fn run() -> Result<()> { match cmd { BenchmarkCmd::Pallet(cmd) => if cfg!(feature = "runtime-benchmarks") { - runner.sync_run(|config| cmd.run::, ()>(config)) + runner.sync_run(|config| cmd.run::, ReclaimHostFunctions>(config)) } else { Err("Benchmarking wasn't enabled when building the node. \ You can enable it with `--features runtime-benchmarks`." diff --git a/cumulus/parachain-template/node/src/service.rs b/cumulus/parachain-template/node/src/service.rs index 830b6e82f96..4dd24803e9b 100644 --- a/cumulus/parachain-template/node/src/service.rs +++ b/cumulus/parachain-template/node/src/service.rs @@ -40,7 +40,10 @@ use substrate_prometheus_endpoint::Registry; pub struct ParachainNativeExecutor; impl sc_executor::NativeExecutionDispatch for ParachainNativeExecutor { - type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; + type ExtendHostFunctions = ( + cumulus_client_service::storage_proof_size::HostFunctions, + frame_benchmarking::benchmarking::HostFunctions, + ); fn dispatch(method: &str, data: &[u8]) -> Option> { parachain_template_runtime::api::dispatch(method, data) @@ -100,10 +103,11 @@ pub fn new_partial(config: &Configuration) -> Result let executor = ParachainExecutor::new_with_wasm_executor(wasm); let (client, backend, keystore_container, task_manager) = - sc_service::new_full_parts::( + sc_service::new_full_parts_record_import::( config, telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()), executor, + true, )?; let client = Arc::new(client); diff --git a/cumulus/parachain-template/runtime/Cargo.toml b/cumulus/parachain-template/runtime/Cargo.toml index 44d96ffc4e6..1873bd0a23e 100644 --- a/cumulus/parachain-template/runtime/Cargo.toml +++ b/cumulus/parachain-template/runtime/Cargo.toml @@ -73,6 +73,7 @@ cumulus-pallet-xcm = { path = "../../pallets/xcm", default-features = false } cumulus-pallet-xcmp-queue = { path = "../../pallets/xcmp-queue", default-features = false } cumulus-primitives-core = { path = "../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../primitives/utility", default-features = false } +cumulus-primitives-storage-weight-reclaim = { path = "../../primitives/storage-weight-reclaim", default-features = false } pallet-collator-selection = { path = "../../pallets/collator-selection", default-features = false } parachains-common = { path = "../../parachains/common", default-features = false } parachain-info = { package = "staging-parachain-info", path = "../../parachains/pallets/parachain-info", default-features = false } @@ -87,6 +88,7 @@ std = [ "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", "cumulus-primitives-core/std", + "cumulus-primitives-storage-weight-reclaim/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", "frame-executive/std", diff --git a/cumulus/parachain-template/runtime/src/lib.rs b/cumulus/parachain-template/runtime/src/lib.rs index d9bc111fcef..cee9b33bf37 100644 --- a/cumulus/parachain-template/runtime/src/lib.rs +++ b/cumulus/parachain-template/runtime/src/lib.rs @@ -107,6 +107,7 @@ pub type SignedExtra = ( frame_system::CheckNonce, frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, + cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim, ); /// Unchecked extrinsic type as expected by this runtime. diff --git a/cumulus/polkadot-parachain/src/command.rs b/cumulus/polkadot-parachain/src/command.rs index a40c356dcd1..4d44879af51 100644 --- a/cumulus/polkadot-parachain/src/command.rs +++ b/cumulus/polkadot-parachain/src/command.rs @@ -23,6 +23,7 @@ use crate::{ }, service::{new_partial, Block}, }; +use cumulus_client_service::storage_proof_size::HostFunctions as ReclaimHostFunctions; use cumulus_primitives_core::ParaId; use frame_benchmarking_cli::{BenchmarkCmd, SUBSTRATE_REFERENCE_HARDWARE}; use log::info; @@ -584,7 +585,7 @@ pub fn run() -> Result<()> { match cmd { BenchmarkCmd::Pallet(cmd) => if cfg!(feature = "runtime-benchmarks") { - runner.sync_run(|config| cmd.run::, ()>(config)) + runner.sync_run(|config| cmd.run::, ReclaimHostFunctions>(config)) } else { Err("Benchmarking wasn't enabled when building the node. \ You can enable it with `--features runtime-benchmarks`." diff --git a/cumulus/polkadot-parachain/src/service.rs b/cumulus/polkadot-parachain/src/service.rs index 553975b01a8..4e06cd38f1d 100644 --- a/cumulus/polkadot-parachain/src/service.rs +++ b/cumulus/polkadot-parachain/src/service.rs @@ -68,11 +68,15 @@ use substrate_prometheus_endpoint::Registry; use polkadot_primitives::CollatorPair; #[cfg(not(feature = "runtime-benchmarks"))] -type HostFunctions = sp_io::SubstrateHostFunctions; +type HostFunctions = + (sp_io::SubstrateHostFunctions, cumulus_client_service::storage_proof_size::HostFunctions); #[cfg(feature = "runtime-benchmarks")] -type HostFunctions = - (sp_io::SubstrateHostFunctions, frame_benchmarking::benchmarking::HostFunctions); +type HostFunctions = ( + sp_io::SubstrateHostFunctions, + cumulus_client_service::storage_proof_size::HostFunctions, + frame_benchmarking::benchmarking::HostFunctions, +); type ParachainClient = TFullClient>; @@ -274,10 +278,11 @@ where .build(); let (client, backend, keystore_container, task_manager) = - sc_service::new_full_parts::( + sc_service::new_full_parts_record_import::( config, telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()), executor, + true, )?; let client = Arc::new(client); diff --git a/cumulus/primitives/proof-size-hostfunction/src/lib.rs b/cumulus/primitives/proof-size-hostfunction/src/lib.rs index 6da6235e585..8ebc58ea450 100644 --- a/cumulus/primitives/proof-size-hostfunction/src/lib.rs +++ b/cumulus/primitives/proof-size-hostfunction/src/lib.rs @@ -18,6 +18,7 @@ #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(feature = "std")] use sp_externalities::ExternalitiesExt; use sp_runtime_interface::runtime_interface; @@ -35,7 +36,8 @@ pub const PROOF_RECORDING_DISABLED: u64 = u64::MAX; pub trait StorageProofSize { /// Returns the current storage proof size. fn storage_proof_size(&mut self) -> u64 { - self.extension::().map_or(u64::MAX, |e| e.storage_proof_size()) + self.extension::() + .map_or(PROOF_RECORDING_DISABLED, |e| e.storage_proof_size()) } } diff --git a/cumulus/primitives/storage-weight-reclaim/Cargo.toml b/cumulus/primitives/storage-weight-reclaim/Cargo.toml new file mode 100644 index 00000000000..4835fb5192b --- /dev/null +++ b/cumulus/primitives/storage-weight-reclaim/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "cumulus-primitives-storage-weight-reclaim" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +description = "Utilities to reclaim storage weight." +license = "Apache-2.0" + +[lints] +workspace = true + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { workspace = true } +scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } + +frame-support = { path = "../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../substrate/frame/system", default-features = false } + +sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } +sp-std = { path = "../../../substrate/primitives/std", default-features = false } + +cumulus-primitives-core = { path = "../../primitives/core", default-features = false } +cumulus-primitives-proof-size-hostfunction = { path = "../../primitives/proof-size-hostfunction", default-features = false } +docify = "0.2.7" + +[dev-dependencies] +sp-trie = { path = "../../../substrate/primitives/trie", default-features = false } +sp-io = { path = "../../../substrate/primitives/io", default-features = false } +cumulus-test-runtime = { path = "../../test/runtime" } + +[features] +default = ["std"] +std = [ + "codec/std", + "cumulus-primitives-core/std", + "cumulus-primitives-proof-size-hostfunction/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "sp-trie/std", +] diff --git a/cumulus/primitives/storage-weight-reclaim/src/lib.rs b/cumulus/primitives/storage-weight-reclaim/src/lib.rs new file mode 100644 index 00000000000..5dddc92e395 --- /dev/null +++ b/cumulus/primitives/storage-weight-reclaim/src/lib.rs @@ -0,0 +1,663 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Mechanism to reclaim PoV proof size weight after an extrinsic has been applied. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode}; +use cumulus_primitives_core::Weight; +use cumulus_primitives_proof_size_hostfunction::{ + storage_proof_size::storage_proof_size, PROOF_RECORDING_DISABLED, +}; +use frame_support::{ + dispatch::{DispatchInfo, PostDispatchInfo}, + weights::WeightMeter, +}; +use frame_system::Config; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{DispatchInfoOf, Dispatchable, PostDispatchInfoOf, SignedExtension}, + transaction_validity::TransactionValidityError, + DispatchResult, +}; +use sp_std::marker::PhantomData; + +const LOG_TARGET: &'static str = "runtime::storage_reclaim"; + +/// `StorageWeightReclaimer` is a mechanism for manually reclaiming storage weight. +/// +/// It internally keeps track of the proof size and storage weight at initialization time. At +/// reclaim it computes the real consumed storage weight and refunds excess weight. +/// +/// # Example +#[doc = docify::embed!("src/lib.rs", simple_reclaimer_example)] +pub struct StorageWeightReclaimer { + previous_remaining_proof_size: u64, + previous_reported_proof_size: Option, +} + +impl StorageWeightReclaimer { + /// Creates a new `StorageWeightReclaimer` instance and initializes it with the storage + /// size provided by `weight_meter` and reported proof size from the node. + #[must_use = "Must call `reclaim_with_meter` to reclaim the weight"] + pub fn new(weight_meter: &WeightMeter) -> StorageWeightReclaimer { + let previous_remaining_proof_size = weight_meter.remaining().proof_size(); + let previous_reported_proof_size = get_proof_size(); + Self { previous_remaining_proof_size, previous_reported_proof_size } + } + + /// Check the consumed storage weight and calculate the consumed excess weight. + fn reclaim(&mut self, remaining_weight: Weight) -> Option { + let current_remaining_weight = remaining_weight.proof_size(); + let current_storage_proof_size = get_proof_size()?; + let previous_storage_proof_size = self.previous_reported_proof_size?; + let used_weight = + self.previous_remaining_proof_size.saturating_sub(current_remaining_weight); + let reported_used_size = + current_storage_proof_size.saturating_sub(previous_storage_proof_size); + let reclaimable = used_weight.saturating_sub(reported_used_size); + log::trace!( + target: LOG_TARGET, + "Found reclaimable storage weight. benchmarked: {used_weight}, consumed: {reported_used_size}" + ); + + self.previous_remaining_proof_size = current_remaining_weight.saturating_add(reclaimable); + self.previous_reported_proof_size = Some(current_storage_proof_size); + Some(Weight::from_parts(0, reclaimable)) + } + + /// Check the consumed storage weight and add the reclaimed + /// weight budget back to `weight_meter`. + pub fn reclaim_with_meter(&mut self, weight_meter: &mut WeightMeter) -> Option { + let reclaimed = self.reclaim(weight_meter.remaining())?; + weight_meter.reclaim_proof_size(reclaimed.proof_size()); + Some(reclaimed) + } +} + +/// Returns the current storage proof size from the host side. +/// +/// Returns `None` if proof recording is disabled on the host. +pub fn get_proof_size() -> Option { + let proof_size = storage_proof_size(); + (proof_size != PROOF_RECORDING_DISABLED).then_some(proof_size) +} + +/// Storage weight reclaim mechanism. +/// +/// This extension checks the size of the node-side storage proof +/// before and after executing a given extrinsic. The difference between +/// benchmarked and spent weight can be reclaimed. +#[derive(Encode, Decode, Clone, Eq, PartialEq, Default, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct StorageWeightReclaim(PhantomData); + +impl StorageWeightReclaim { + /// Create a new `StorageWeightReclaim` instance. + pub fn new() -> Self { + Self(Default::default()) + } +} + +impl core::fmt::Debug for StorageWeightReclaim { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + let _ = write!(f, "StorageWeightReclaim"); + Ok(()) + } +} + +impl SignedExtension for StorageWeightReclaim +where + T::RuntimeCall: Dispatchable, +{ + const IDENTIFIER: &'static str = "StorageWeightReclaim"; + + type AccountId = T::AccountId; + type Call = T::RuntimeCall; + type AdditionalSigned = (); + type Pre = Option; + + fn additional_signed( + &self, + ) -> Result + { + Ok(()) + } + + fn pre_dispatch( + self, + _who: &Self::AccountId, + _call: &Self::Call, + _info: &sp_runtime::traits::DispatchInfoOf, + _len: usize, + ) -> Result { + Ok(get_proof_size()) + } + + fn post_dispatch( + pre: Option, + info: &DispatchInfoOf, + post_info: &PostDispatchInfoOf, + _len: usize, + _result: &DispatchResult, + ) -> Result<(), TransactionValidityError> { + let Some(Some(pre_dispatch_proof_size)) = pre else { + return Ok(()); + }; + + let Some(post_dispatch_proof_size) = get_proof_size() else { + log::debug!( + target: LOG_TARGET, + "Proof recording enabled during pre-dispatch, now disabled. This should not happen." + ); + return Ok(()) + }; + let benchmarked_weight = info.weight.proof_size(); + let consumed_weight = post_dispatch_proof_size.saturating_sub(pre_dispatch_proof_size); + + // Unspent weight according to the `actual_weight` from `PostDispatchInfo` + // This unspent weight will be refunded by the `CheckWeight` extension, so we need to + // account for that. + let unspent = post_info.calc_unspent(info).proof_size(); + let storage_size_diff = + benchmarked_weight.saturating_sub(unspent).abs_diff(consumed_weight as u64); + + // This value will be reclaimed by [`frame_system::CheckWeight`], so we need to calculate + // that in. + frame_system::BlockWeight::::mutate(|current| { + if consumed_weight > benchmarked_weight { + log::error!( + target: LOG_TARGET, + "Benchmarked storage weight smaller than consumed storage weight. benchmarked: {benchmarked_weight} consumed: {consumed_weight} unspent: {unspent}" + ); + current.accrue(Weight::from_parts(0, storage_size_diff), info.class) + } else { + log::trace!( + target: LOG_TARGET, + "Reclaiming storage weight. benchmarked: {benchmarked_weight}, consumed: {consumed_weight} unspent: {unspent}" + ); + current.reduce(Weight::from_parts(0, storage_size_diff), info.class) + } + }); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use frame_support::{ + assert_ok, + dispatch::DispatchClass, + weights::{Weight, WeightMeter}, + }; + use frame_system::{BlockWeight, CheckWeight}; + use sp_runtime::{AccountId32, BuildStorage}; + use sp_std::marker::PhantomData; + use sp_trie::proof_size_extension::ProofSizeExt; + + type Test = cumulus_test_runtime::Runtime; + const CALL: &::RuntimeCall = + &cumulus_test_runtime::RuntimeCall::System(frame_system::Call::set_heap_pages { + pages: 0u64, + }); + const ALICE: AccountId32 = AccountId32::new([1u8; 32]); + const LEN: usize = 0; + + pub fn new_test_ext() -> sp_io::TestExternalities { + let ext: sp_io::TestExternalities = cumulus_test_runtime::RuntimeGenesisConfig::default() + .build_storage() + .unwrap() + .into(); + ext + } + + struct TestRecorder { + return_values: Box<[usize]>, + counter: std::sync::atomic::AtomicUsize, + } + + impl TestRecorder { + fn new(values: &[usize]) -> Self { + TestRecorder { return_values: values.into(), counter: Default::default() } + } + } + + impl sp_trie::ProofSizeProvider for TestRecorder { + fn estimate_encoded_size(&self) -> usize { + let counter = self.counter.fetch_add(1, core::sync::atomic::Ordering::Relaxed); + self.return_values[counter] + } + } + + fn setup_test_externalities(proof_values: &[usize]) -> sp_io::TestExternalities { + let mut test_ext = new_test_ext(); + let test_recorder = TestRecorder::new(proof_values); + test_ext.register_extension(ProofSizeExt::new(test_recorder)); + test_ext + } + + fn set_current_storage_weight(new_weight: u64) { + BlockWeight::::mutate(|current_weight| { + current_weight.set(Weight::from_parts(0, new_weight), DispatchClass::Normal); + }); + } + + #[test] + fn basic_refund() { + // The real cost will be 100 bytes of storage size + let mut test_ext = setup_test_externalities(&[0, 100]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + + // Benchmarked storage weight: 500 + let info = DispatchInfo { weight: Weight::from_parts(0, 500), ..Default::default() }; + let post_info = PostDispatchInfo::default(); + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, Some(0)); + + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + // We expect a refund of 400 + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + + assert_eq!(BlockWeight::::get().total().proof_size(), 600); + }) + } + + #[test] + fn does_nothing_without_extension() { + let mut test_ext = new_test_ext(); + + // Proof size extension not registered + test_ext.execute_with(|| { + set_current_storage_weight(1000); + + // Benchmarked storage weight: 500 + let info = DispatchInfo { weight: Weight::from_parts(0, 500), ..Default::default() }; + let post_info = PostDispatchInfo::default(); + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, None); + + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + + assert_eq!(BlockWeight::::get().total().proof_size(), 1000); + }) + } + + #[test] + fn negative_refund_is_added_to_weight() { + let mut test_ext = setup_test_externalities(&[100, 300]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + // Benchmarked storage weight: 100 + let info = DispatchInfo { weight: Weight::from_parts(0, 100), ..Default::default() }; + let post_info = PostDispatchInfo::default(); + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, Some(100)); + + // We expect no refund + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + + assert_eq!(BlockWeight::::get().total().proof_size(), 1100); + }) + } + + #[test] + fn test_zero_proof_size() { + let mut test_ext = setup_test_externalities(&[0, 0]); + + test_ext.execute_with(|| { + let info = DispatchInfo { weight: Weight::from_parts(0, 500), ..Default::default() }; + let post_info = PostDispatchInfo::default(); + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, Some(0)); + + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + + assert_eq!(BlockWeight::::get().total().proof_size(), 0); + }); + } + + #[test] + fn test_larger_pre_dispatch_proof_size() { + let mut test_ext = setup_test_externalities(&[300, 100]); + + test_ext.execute_with(|| { + set_current_storage_weight(1300); + + let info = DispatchInfo { weight: Weight::from_parts(0, 500), ..Default::default() }; + let post_info = PostDispatchInfo::default(); + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, Some(300)); + + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + + assert_eq!(BlockWeight::::get().total().proof_size(), 800); + }); + } + + #[test] + fn test_incorporates_check_weight_unspent_weight() { + let mut test_ext = setup_test_externalities(&[100, 300]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + + // Benchmarked storage weight: 300 + let info = DispatchInfo { weight: Weight::from_parts(100, 300), ..Default::default() }; + + // Actual weight is 50 + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(50, 250)), + pays_fee: Default::default(), + }; + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, Some(100)); + + // The `CheckWeight` extension will refunt `actual_weight` from `PostDispatchInfo` + // we always need to call `post_dispatch` to verify that they interoperate correctly. + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + + assert_eq!(BlockWeight::::get().total().proof_size(), 900); + }) + } + + #[test] + fn test_incorporates_check_weight_unspent_weight_on_negative() { + let mut test_ext = setup_test_externalities(&[100, 300]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + // Benchmarked storage weight: 50 + let info = DispatchInfo { weight: Weight::from_parts(100, 50), ..Default::default() }; + + // Actual weight is 25 + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(50, 25)), + pays_fee: Default::default(), + }; + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, Some(100)); + + // The `CheckWeight` extension will refunt `actual_weight` from `PostDispatchInfo` + // we always need to call `post_dispatch` to verify that they interoperate correctly. + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + + assert_eq!(BlockWeight::::get().total().proof_size(), 1150); + }) + } + + #[test] + fn test_incorporates_check_weight_unspent_weight_reverse_order() { + let mut test_ext = setup_test_externalities(&[100, 300]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + + // Benchmarked storage weight: 300 + let info = DispatchInfo { weight: Weight::from_parts(100, 300), ..Default::default() }; + + // Actual weight is 50 + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(50, 250)), + pays_fee: Default::default(), + }; + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, Some(100)); + + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + // `CheckWeight` gets called after `StorageWeightReclaim` this time. + // The `CheckWeight` extension will refunt `actual_weight` from `PostDispatchInfo` + // we always need to call `post_dispatch` to verify that they interoperate correctly. + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + + assert_eq!(BlockWeight::::get().total().proof_size(), 900); + }) + } + + #[test] + fn test_incorporates_check_weight_unspent_weight_on_negative_reverse_order() { + let mut test_ext = setup_test_externalities(&[100, 300]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + // Benchmarked storage weight: 50 + let info = DispatchInfo { weight: Weight::from_parts(100, 50), ..Default::default() }; + + // Actual weight is 25 + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(50, 25)), + pays_fee: Default::default(), + }; + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, Some(100)); + + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + // `CheckWeight` gets called after `StorageWeightReclaim` this time. + // The `CheckWeight` extension will refunt `actual_weight` from `PostDispatchInfo` + // we always need to call `post_dispatch` to verify that they interoperate correctly. + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + + assert_eq!(BlockWeight::::get().total().proof_size(), 1150); + }) + } + + #[test] + fn storage_size_reported_correctly() { + let mut test_ext = setup_test_externalities(&[1000]); + test_ext.execute_with(|| { + assert_eq!(get_proof_size(), Some(1000)); + }); + + let mut test_ext = new_test_ext(); + + let test_recorder = TestRecorder::new(&[0]); + + test_ext.register_extension(ProofSizeExt::new(test_recorder)); + + test_ext.execute_with(|| { + assert_eq!(get_proof_size(), Some(0)); + }); + } + + #[test] + fn storage_size_disabled_reported_correctly() { + let mut test_ext = setup_test_externalities(&[PROOF_RECORDING_DISABLED as usize]); + + test_ext.execute_with(|| { + assert_eq!(get_proof_size(), None); + }); + } + + #[test] + fn test_reclaim_helper() { + let mut test_ext = setup_test_externalities(&[1000, 1300, 1800]); + + test_ext.execute_with(|| { + let mut remaining_weight_meter = WeightMeter::with_limit(Weight::from_parts(0, 2000)); + let mut reclaim_helper = StorageWeightReclaimer::new(&remaining_weight_meter); + remaining_weight_meter.consume(Weight::from_parts(0, 500)); + let reclaimed = reclaim_helper.reclaim_with_meter(&mut remaining_weight_meter); + + assert_eq!(reclaimed, Some(Weight::from_parts(0, 200))); + + remaining_weight_meter.consume(Weight::from_parts(0, 800)); + let reclaimed = reclaim_helper.reclaim_with_meter(&mut remaining_weight_meter); + assert_eq!(reclaimed, Some(Weight::from_parts(0, 300))); + assert_eq!(remaining_weight_meter.remaining(), Weight::from_parts(0, 1200)); + }); + } + + #[test] + fn test_reclaim_helper_does_not_reclaim_negative() { + // Benchmarked weight does not change at all + let mut test_ext = setup_test_externalities(&[1000, 1300]); + + test_ext.execute_with(|| { + let mut remaining_weight_meter = WeightMeter::with_limit(Weight::from_parts(0, 1000)); + let mut reclaim_helper = StorageWeightReclaimer::new(&remaining_weight_meter); + let reclaimed = reclaim_helper.reclaim_with_meter(&mut remaining_weight_meter); + + assert_eq!(reclaimed, Some(Weight::from_parts(0, 0))); + assert_eq!(remaining_weight_meter.remaining(), Weight::from_parts(0, 1000)); + }); + + // Benchmarked weight increases less than storage proof consumes + let mut test_ext = setup_test_externalities(&[1000, 1300]); + + test_ext.execute_with(|| { + let mut remaining_weight_meter = WeightMeter::with_limit(Weight::from_parts(0, 1000)); + let mut reclaim_helper = StorageWeightReclaimer::new(&remaining_weight_meter); + remaining_weight_meter.consume(Weight::from_parts(0, 0)); + let reclaimed = reclaim_helper.reclaim_with_meter(&mut remaining_weight_meter); + + assert_eq!(reclaimed, Some(Weight::from_parts(0, 0))); + }); + } + + /// Just here for doc purposes + fn get_benched_weight() -> Weight { + Weight::from_parts(0, 5) + } + + /// Just here for doc purposes + fn do_work() {} + + #[docify::export_content(simple_reclaimer_example)] + fn reclaim_with_weight_meter() { + let mut remaining_weight_meter = WeightMeter::with_limit(Weight::from_parts(10, 10)); + + let benched_weight = get_benched_weight(); + + // It is important to instantiate the `StorageWeightReclaimer` before we consume the weight + // for a piece of work from the weight meter. + let mut reclaim_helper = StorageWeightReclaimer::new(&remaining_weight_meter); + + if remaining_weight_meter.try_consume(benched_weight).is_ok() { + // Perform some work that takes has `benched_weight` storage weight. + do_work(); + + // Reclaimer will detect that we only consumed 2 bytes, so 3 bytes are reclaimed. + let reclaimed = reclaim_helper.reclaim_with_meter(&mut remaining_weight_meter); + + // We reclaimed 3 bytes of storage size! + assert_eq!(reclaimed, Some(Weight::from_parts(0, 3))); + assert_eq!(BlockWeight::::get().total().proof_size(), 10); + assert_eq!(remaining_weight_meter.remaining(), Weight::from_parts(10, 8)); + } + } + + #[test] + fn test_reclaim_helper_works_with_meter() { + // The node will report 12 - 10 = 2 consumed storage size between the calls. + let mut test_ext = setup_test_externalities(&[10, 12]); + + test_ext.execute_with(|| { + // Initial storage size is 10. + set_current_storage_weight(10); + reclaim_with_weight_meter(); + }); + } +} diff --git a/cumulus/test/client/Cargo.toml b/cumulus/test/client/Cargo.toml index 7190172101c..028733ce235 100644 --- a/cumulus/test/client/Cargo.toml +++ b/cumulus/test/client/Cargo.toml @@ -41,6 +41,7 @@ cumulus-test-relay-sproof-builder = { path = "../relay-sproof-builder" } cumulus-primitives-core = { path = "../../primitives/core" } cumulus-primitives-proof-size-hostfunction = { path = "../../primitives/proof-size-hostfunction" } cumulus-primitives-parachain-inherent = { path = "../../primitives/parachain-inherent" } +cumulus-primitives-storage-weight-reclaim = { path = "../../primitives/storage-weight-reclaim" } [features] runtime-benchmarks = [ diff --git a/cumulus/test/client/src/lib.rs b/cumulus/test/client/src/lib.rs index df63f683de6..c46f4da7f67 100644 --- a/cumulus/test/client/src/lib.rs +++ b/cumulus/test/client/src/lib.rs @@ -151,6 +151,7 @@ pub fn generate_extrinsic_with_pair( frame_system::CheckNonce::::from(nonce), frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(tip), + cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::::new(), ); let function = function.into(); @@ -158,7 +159,7 @@ pub fn generate_extrinsic_with_pair( let raw_payload = SignedPayload::from_raw( function.clone(), extra.clone(), - ((), VERSION.spec_version, genesis_block, current_block_hash, (), (), ()), + ((), VERSION.spec_version, genesis_block, current_block_hash, (), (), (), ()), ); let signature = raw_payload.using_encoded(|e| origin.sign(e)); @@ -203,13 +204,16 @@ pub fn validate_block( let mut ext_ext = ext.ext(); let heap_pages = HeapAllocStrategy::Static { extra_pages: 1024 }; - let executor = WasmExecutor::::builder() - .with_execution_method(WasmExecutionMethod::default()) - .with_max_runtime_instances(1) - .with_runtime_cache_size(2) - .with_onchain_heap_alloc_strategy(heap_pages) - .with_offchain_heap_alloc_strategy(heap_pages) - .build(); + let executor = WasmExecutor::<( + sp_io::SubstrateHostFunctions, + cumulus_primitives_proof_size_hostfunction::storage_proof_size::HostFunctions, + )>::builder() + .with_execution_method(WasmExecutionMethod::default()) + .with_max_runtime_instances(1) + .with_runtime_cache_size(2) + .with_onchain_heap_alloc_strategy(heap_pages) + .with_offchain_heap_alloc_strategy(heap_pages) + .build(); executor .uncached_call( diff --git a/cumulus/test/runtime/Cargo.toml b/cumulus/test/runtime/Cargo.toml index 5902a62512b..449a8b819bc 100644 --- a/cumulus/test/runtime/Cargo.toml +++ b/cumulus/test/runtime/Cargo.toml @@ -39,6 +39,7 @@ sp-version = { path = "../../../substrate/primitives/version", default-features # Cumulus cumulus-pallet-parachain-system = { path = "../../pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook"] } cumulus-primitives-core = { path = "../../primitives/core", default-features = false } +cumulus-primitives-storage-weight-reclaim = { path = "../../primitives/storage-weight-reclaim", default-features = false } [build-dependencies] substrate-wasm-builder = { path = "../../../substrate/utils/wasm-builder", optional = true } @@ -49,6 +50,7 @@ std = [ "codec/std", "cumulus-pallet-parachain-system/std", "cumulus-primitives-core/std", + "cumulus-primitives-storage-weight-reclaim/std", "frame-executive/std", "frame-support/std", "frame-system-rpc-runtime-api/std", diff --git a/cumulus/test/runtime/src/lib.rs b/cumulus/test/runtime/src/lib.rs index 6068f895c83..5fb31410984 100644 --- a/cumulus/test/runtime/src/lib.rs +++ b/cumulus/test/runtime/src/lib.rs @@ -331,6 +331,7 @@ pub type SignedExtra = ( frame_system::CheckNonce, frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, + cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim, ); /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = diff --git a/cumulus/test/service/Cargo.toml b/cumulus/test/service/Cargo.toml index b26f0b9967c..27273f4e0a8 100644 --- a/cumulus/test/service/Cargo.toml +++ b/cumulus/test/service/Cargo.toml @@ -81,6 +81,7 @@ cumulus-relay-chain-minimal-node = { path = "../../client/relay-chain-minimal-no cumulus-client-pov-recovery = { path = "../../client/pov-recovery" } cumulus-test-relay-sproof-builder = { path = "../relay-sproof-builder" } cumulus-pallet-parachain-system = { path = "../../pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook"] } +cumulus-primitives-storage-weight-reclaim = { path = "../../primitives/storage-weight-reclaim" } pallet-timestamp = { path = "../../../substrate/frame/timestamp" } [dev-dependencies] diff --git a/cumulus/test/service/src/lib.rs b/cumulus/test/service/src/lib.rs index 1c2e1db9741..3554a383f21 100644 --- a/cumulus/test/service/src/lib.rs +++ b/cumulus/test/service/src/lib.rs @@ -112,7 +112,7 @@ pub type AnnounceBlockFn = Arc>) + Send + Sync>; pub struct RuntimeExecutor; impl sc_executor::NativeExecutionDispatch for RuntimeExecutor { - type ExtendHostFunctions = (); + type ExtendHostFunctions = cumulus_client_service::storage_proof_size::HostFunctions; fn dispatch(method: &str, data: &[u8]) -> Option> { cumulus_test_runtime::api::dispatch(method, data) @@ -894,11 +894,12 @@ pub fn construct_extrinsic( frame_system::CheckNonce::::from(nonce), frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(tip), + cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::::new(), ); let raw_payload = runtime::SignedPayload::from_raw( function.clone(), extra.clone(), - ((), runtime::VERSION.spec_version, genesis_block, current_block_hash, (), (), ()), + ((), runtime::VERSION.spec_version, genesis_block, current_block_hash, (), (), (), ()), ); let signature = raw_payload.using_encoded(|e| caller.sign(e)); runtime::UncheckedExtrinsic::new_signed( diff --git a/prdoc/pr_3002.prdoc b/prdoc/pr_3002.prdoc new file mode 100644 index 00000000000..511a07e39c4 --- /dev/null +++ b/prdoc/pr_3002.prdoc @@ -0,0 +1,29 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: PoV Reclaim Runtime Side +author: skunert +topic: runtime +doc: + - audience: Runtime Dev + description: | + Adds a mechanism to reclaim proof size weight. + 1. Introduces a new `SignedExtension` that reclaims the difference + between benchmarked proof size weight and actual consumed proof size weight. + 2. Introduces a manual mechanism, `StorageWeightReclaimer`, to reclaim excess storage weight for situations + that require manual weight management. The most prominent case is the `on_idle` hook. + 3. Adds the `storage_proof_size` host function to the PVF. Parachain nodes should add it to ensure compatibility. + + To enable proof size reclaiming, add the host `storage_proof_size` host function to the parachain node. Add the + `StorageWeightReclaim` `SignedExtension` to your runtime and enable proof recording during block import. + + +crates: + - name: "cumulus-primitives-storage-weight-reclaim" +host_functions: + - name: "storage_proof_size" + description: | + This host function is used to pass the current size of the storage proof to the runtime. + It was introduced before but becomes relevant now. + Note: This host function is intended to be used through `cumulus_primitives_storage_weight_reclaim::get_proof_size`. + Direct usage is not recommended. diff --git a/substrate/frame/system/src/lib.rs b/substrate/frame/system/src/lib.rs index 069217bcee4..1405c7303f8 100644 --- a/substrate/frame/system/src/lib.rs +++ b/substrate/frame/system/src/lib.rs @@ -148,6 +148,7 @@ use sp_io::TestExternalities; pub mod limits; #[cfg(test)] pub(crate) mod mock; + pub mod offchain; mod extensions; @@ -847,7 +848,7 @@ pub mod pallet { #[pallet::storage] #[pallet::whitelist_storage] #[pallet::getter(fn block_weight)] - pub(super) type BlockWeight = StorageValue<_, ConsumedWeight, ValueQuery>; + pub type BlockWeight = StorageValue<_, ConsumedWeight, ValueQuery>; /// Total length (in bytes) for all extrinsics put together, for the current block. #[pallet::storage] diff --git a/substrate/primitives/weights/src/weight_meter.rs b/substrate/primitives/weights/src/weight_meter.rs index 584d22304c3..1738948e4c3 100644 --- a/substrate/primitives/weights/src/weight_meter.rs +++ b/substrate/primitives/weights/src/weight_meter.rs @@ -149,6 +149,11 @@ impl WeightMeter { pub fn can_consume(&self, w: Weight) -> bool { self.consumed.checked_add(&w).map_or(false, |t| t.all_lte(self.limit)) } + + /// Reclaim the given weight. + pub fn reclaim_proof_size(&mut self, s: u64) { + self.consumed.saturating_reduce(Weight::from_parts(0, s)); + } } #[cfg(test)] @@ -277,6 +282,21 @@ mod tests { assert_eq!(meter.consumed(), Weight::from_parts(5, 10)); } + #[test] + #[cfg(debug_assertions)] + fn reclaim_works() { + let mut meter = WeightMeter::with_limit(Weight::from_parts(5, 10)); + + meter.consume(Weight::from_parts(5, 10)); + assert_eq!(meter.consumed(), Weight::from_parts(5, 10)); + + meter.reclaim_proof_size(3); + assert_eq!(meter.consumed(), Weight::from_parts(5, 7)); + + meter.reclaim_proof_size(10); + assert_eq!(meter.consumed(), Weight::from_parts(5, 0)); + } + #[test] #[cfg(debug_assertions)] #[should_panic(expected = "Weight counter overflow")] diff --git a/substrate/test-utils/client/src/lib.rs b/substrate/test-utils/client/src/lib.rs index e3f06e27563..d283b24f286 100644 --- a/substrate/test-utils/client/src/lib.rs +++ b/substrate/test-utils/client/src/lib.rs @@ -72,6 +72,7 @@ pub struct TestClientBuilder, bad_blocks: BadBlocks, enable_offchain_indexing_api: bool, + enable_import_proof_recording: bool, no_genesis: bool, } @@ -120,6 +121,7 @@ impl bad_blocks: None, enable_offchain_indexing_api: false, no_genesis: false, + enable_import_proof_recording: false, } } @@ -165,6 +167,12 @@ impl self } + /// Enable proof recording on import. + pub fn enable_import_proof_recording(mut self) -> Self { + self.enable_import_proof_recording = true; + self + } + /// Disable writing genesis. pub fn set_no_genesis(mut self) -> Self { self.no_genesis = true; @@ -202,6 +210,7 @@ impl }; let client_config = ClientConfig { + enable_import_proof_recording: self.enable_import_proof_recording, offchain_indexing_api: self.enable_offchain_indexing_api, no_genesis: self.no_genesis, ..Default::default() -- GitLab From 6fc1d41d4487b9164451cd8214674ce195ab06a0 Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Fri, 23 Feb 2024 16:53:08 +0100 Subject: [PATCH 121/133] Bridge zombienet tests: move all "framework" files under one folder (#3462) Related to https://github.com/paritytech/polkadot-sdk/issues/3400 Moving all bridges testing "framework" files under one folder in order to be able to download the entire folder when we want to add tests in other repos No significant functional changes --- .../rococo-westend/bridges_rococo_westend.sh | 2 +- bridges/testing/environments/rococo-westend/helper.sh | 2 +- .../environments/rococo-westend/rococo-init.zndsl | 4 ++-- .../testing/environments/rococo-westend/rococo.zndsl | 4 ++-- bridges/testing/environments/rococo-westend/spawn.sh | 2 +- .../environments/rococo-westend/start_relayer.sh | 4 ++-- .../environments/rococo-westend/westend-init.zndsl | 4 ++-- .../testing/environments/rococo-westend/westend.zndsl | 4 ++-- .../best-finalized-header-at-bridged-chain.js | 0 .../js-helpers/chains/rococo-at-westend.js | 0 .../js-helpers/chains/westend-at-rococo.js | 0 .../js-helpers/native-assets-balance-increased.js | 0 .../only-mandatory-headers-synced-when-idle.js | 0 .../only-required-headers-synced-when-idle.js | 0 .../{ => framework}/js-helpers/relayer-rewards.js | 0 bridges/testing/{ => framework}/js-helpers/utils.js | 0 .../js-helpers/wait-hrmp-channel-opened.js | 0 .../js-helpers/wrapped-assets-balance.js | 0 bridges/testing/{ => framework}/utils/bridges.sh | 4 ++-- bridges/testing/{ => framework}/utils/common.sh | 0 .../utils/generate_hex_encoded_call/index.js | 0 .../utils/generate_hex_encoded_call/package-lock.json | 0 .../utils/generate_hex_encoded_call/package.json | 0 bridges/testing/{ => framework}/utils/zombienet.sh | 0 bridges/testing/run-new-test.sh | 1 + .../0001-asset-transfer/roc-reaches-westend.zndsl | 8 ++++---- bridges/testing/tests/0001-asset-transfer/run.sh | 8 +++++--- .../tests/0001-asset-transfer/wnd-reaches-rococo.zndsl | 8 ++++---- .../0001-asset-transfer/wroc-reaches-rococo.zndsl | 6 +++--- .../0001-asset-transfer/wwnd-reaches-westend.zndsl | 6 +++--- .../rococo-to-westend.zndsl | 4 ++-- .../0002-mandatory-headers-synced-while-idle/run.sh | 10 +++++----- .../westend-to-rococo.zndsl | 4 ++-- .../bridges_zombienet_tests_injected.Dockerfile | 2 +- 34 files changed, 45 insertions(+), 42 deletions(-) rename bridges/testing/{ => framework}/js-helpers/best-finalized-header-at-bridged-chain.js (100%) rename bridges/testing/{ => framework}/js-helpers/chains/rococo-at-westend.js (100%) rename bridges/testing/{ => framework}/js-helpers/chains/westend-at-rococo.js (100%) rename bridges/testing/{ => framework}/js-helpers/native-assets-balance-increased.js (100%) rename bridges/testing/{ => framework}/js-helpers/only-mandatory-headers-synced-when-idle.js (100%) rename bridges/testing/{ => framework}/js-helpers/only-required-headers-synced-when-idle.js (100%) rename bridges/testing/{ => framework}/js-helpers/relayer-rewards.js (100%) rename bridges/testing/{ => framework}/js-helpers/utils.js (100%) rename bridges/testing/{ => framework}/js-helpers/wait-hrmp-channel-opened.js (100%) rename bridges/testing/{ => framework}/js-helpers/wrapped-assets-balance.js (100%) rename bridges/testing/{ => framework}/utils/bridges.sh (98%) rename bridges/testing/{ => framework}/utils/common.sh (100%) rename bridges/testing/{ => framework}/utils/generate_hex_encoded_call/index.js (100%) rename bridges/testing/{ => framework}/utils/generate_hex_encoded_call/package-lock.json (100%) rename bridges/testing/{ => framework}/utils/generate_hex_encoded_call/package.json (100%) rename bridges/testing/{ => framework}/utils/zombienet.sh (100%) diff --git a/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh b/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh index 84764cdaca3..de5be2e0af8 100755 --- a/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh +++ b/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh @@ -1,7 +1,7 @@ #!/bin/bash # import common functions -source "${BASH_SOURCE%/*}/../../utils/bridges.sh" +source "$FRAMEWORK_PATH/utils/bridges.sh" # Expected sovereign accounts. # diff --git a/bridges/testing/environments/rococo-westend/helper.sh b/bridges/testing/environments/rococo-westend/helper.sh index 211a5b53b3d..0a13ded213f 100755 --- a/bridges/testing/environments/rococo-westend/helper.sh +++ b/bridges/testing/environments/rococo-westend/helper.sh @@ -1,3 +1,3 @@ #!/bin/bash -$POLKADOT_SDK_PATH/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh "$@" +$ENV_PATH/bridges_rococo_westend.sh "$@" diff --git a/bridges/testing/environments/rococo-westend/rococo-init.zndsl b/bridges/testing/environments/rococo-westend/rococo-init.zndsl index 145f2df73a6..c913e4db31f 100644 --- a/bridges/testing/environments/rococo-westend/rococo-init.zndsl +++ b/bridges/testing/environments/rococo-westend/rococo-init.zndsl @@ -1,8 +1,8 @@ -Description: User is able to transfer WND from Westend Asset Hub to Rococo Asset Hub and back +Description: Check if the HRMP channel between Rococo BH and Rococo AH was opened successfully Network: ./bridge_hub_rococo_local_network.toml Creds: config # ensure that initialization has completed -asset-hub-rococo-collator1: js-script ../../js-helpers/wait-hrmp-channel-opened.js with "1013" within 300 seconds +asset-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/wait-hrmp-channel-opened.js with "1013" within 300 seconds diff --git a/bridges/testing/environments/rococo-westend/rococo.zndsl b/bridges/testing/environments/rococo-westend/rococo.zndsl index bd8681af219..5b49c7c632f 100644 --- a/bridges/testing/environments/rococo-westend/rococo.zndsl +++ b/bridges/testing/environments/rococo-westend/rococo.zndsl @@ -1,7 +1,7 @@ -Description: User is able to transfer WND from Westend Asset Hub to Rococo Asset Hub and back +Description: Check if the with-Westend GRANPDA pallet was initialized at Rococo BH Network: ./bridge_hub_rococo_local_network.toml Creds: config # relay is already started - let's wait until with-Westend GRANPDA pallet is initialized at Rococo -bridge-hub-rococo-collator1: js-script ../../js-helpers/best-finalized-header-at-bridged-chain.js with "Westend,0" within 400 seconds +bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/best-finalized-header-at-bridged-chain.js with "Westend,0" within 400 seconds diff --git a/bridges/testing/environments/rococo-westend/spawn.sh b/bridges/testing/environments/rococo-westend/spawn.sh index 5a0d65ce65d..cbd0b1bc623 100755 --- a/bridges/testing/environments/rococo-westend/spawn.sh +++ b/bridges/testing/environments/rococo-westend/spawn.sh @@ -4,7 +4,7 @@ set -e trap "trap - SIGTERM && kill -9 -$$" SIGINT SIGTERM EXIT -source "${BASH_SOURCE%/*}/../../utils/zombienet.sh" +source "$FRAMEWORK_PATH/utils/zombienet.sh" # whether to init the chains (open HRMP channels, set XCM version, create reserve assets, etc) init=0 diff --git a/bridges/testing/environments/rococo-westend/start_relayer.sh b/bridges/testing/environments/rococo-westend/start_relayer.sh index c57d4f1a437..7ddd312d395 100755 --- a/bridges/testing/environments/rococo-westend/start_relayer.sh +++ b/bridges/testing/environments/rococo-westend/start_relayer.sh @@ -2,8 +2,8 @@ set -e -source "${BASH_SOURCE%/*}/../../utils/common.sh" -source "${BASH_SOURCE%/*}/../../utils/zombienet.sh" +source "$FRAMEWORK_PATH/utils/common.sh" +source "$FRAMEWORK_PATH/utils/zombienet.sh" rococo_dir=$1 westend_dir=$2 diff --git a/bridges/testing/environments/rococo-westend/westend-init.zndsl b/bridges/testing/environments/rococo-westend/westend-init.zndsl index 2f8e665d592..0f5428eed3b 100644 --- a/bridges/testing/environments/rococo-westend/westend-init.zndsl +++ b/bridges/testing/environments/rococo-westend/westend-init.zndsl @@ -1,7 +1,7 @@ -Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back +Description: Check if the HRMP channel between Westend BH and Westend AH was opened successfully Network: ./bridge_hub_westend_local_network.toml Creds: config # ensure that initialization has completed -asset-hub-westend-collator1: js-script ../../js-helpers/wait-hrmp-channel-opened.js with "1002" within 600 seconds +asset-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/wait-hrmp-channel-opened.js with "1002" within 600 seconds diff --git a/bridges/testing/environments/rococo-westend/westend.zndsl b/bridges/testing/environments/rococo-westend/westend.zndsl index c75ae579d27..07968838852 100644 --- a/bridges/testing/environments/rococo-westend/westend.zndsl +++ b/bridges/testing/environments/rococo-westend/westend.zndsl @@ -1,6 +1,6 @@ -Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back +Description: Check if the with-Rococo GRANPDA pallet was initialized at Westend BH Network: ./bridge_hub_westend_local_network.toml Creds: config # relay is already started - let's wait until with-Rococo GRANPDA pallet is initialized at Westend -bridge-hub-westend-collator1: js-script ../../js-helpers/best-finalized-header-at-bridged-chain.js with "Rococo,0" within 400 seconds +bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/best-finalized-header-at-bridged-chain.js with "Rococo,0" within 400 seconds diff --git a/bridges/testing/js-helpers/best-finalized-header-at-bridged-chain.js b/bridges/testing/framework/js-helpers/best-finalized-header-at-bridged-chain.js similarity index 100% rename from bridges/testing/js-helpers/best-finalized-header-at-bridged-chain.js rename to bridges/testing/framework/js-helpers/best-finalized-header-at-bridged-chain.js diff --git a/bridges/testing/js-helpers/chains/rococo-at-westend.js b/bridges/testing/framework/js-helpers/chains/rococo-at-westend.js similarity index 100% rename from bridges/testing/js-helpers/chains/rococo-at-westend.js rename to bridges/testing/framework/js-helpers/chains/rococo-at-westend.js diff --git a/bridges/testing/js-helpers/chains/westend-at-rococo.js b/bridges/testing/framework/js-helpers/chains/westend-at-rococo.js similarity index 100% rename from bridges/testing/js-helpers/chains/westend-at-rococo.js rename to bridges/testing/framework/js-helpers/chains/westend-at-rococo.js diff --git a/bridges/testing/js-helpers/native-assets-balance-increased.js b/bridges/testing/framework/js-helpers/native-assets-balance-increased.js similarity index 100% rename from bridges/testing/js-helpers/native-assets-balance-increased.js rename to bridges/testing/framework/js-helpers/native-assets-balance-increased.js diff --git a/bridges/testing/js-helpers/only-mandatory-headers-synced-when-idle.js b/bridges/testing/framework/js-helpers/only-mandatory-headers-synced-when-idle.js similarity index 100% rename from bridges/testing/js-helpers/only-mandatory-headers-synced-when-idle.js rename to bridges/testing/framework/js-helpers/only-mandatory-headers-synced-when-idle.js diff --git a/bridges/testing/js-helpers/only-required-headers-synced-when-idle.js b/bridges/testing/framework/js-helpers/only-required-headers-synced-when-idle.js similarity index 100% rename from bridges/testing/js-helpers/only-required-headers-synced-when-idle.js rename to bridges/testing/framework/js-helpers/only-required-headers-synced-when-idle.js diff --git a/bridges/testing/js-helpers/relayer-rewards.js b/bridges/testing/framework/js-helpers/relayer-rewards.js similarity index 100% rename from bridges/testing/js-helpers/relayer-rewards.js rename to bridges/testing/framework/js-helpers/relayer-rewards.js diff --git a/bridges/testing/js-helpers/utils.js b/bridges/testing/framework/js-helpers/utils.js similarity index 100% rename from bridges/testing/js-helpers/utils.js rename to bridges/testing/framework/js-helpers/utils.js diff --git a/bridges/testing/js-helpers/wait-hrmp-channel-opened.js b/bridges/testing/framework/js-helpers/wait-hrmp-channel-opened.js similarity index 100% rename from bridges/testing/js-helpers/wait-hrmp-channel-opened.js rename to bridges/testing/framework/js-helpers/wait-hrmp-channel-opened.js diff --git a/bridges/testing/js-helpers/wrapped-assets-balance.js b/bridges/testing/framework/js-helpers/wrapped-assets-balance.js similarity index 100% rename from bridges/testing/js-helpers/wrapped-assets-balance.js rename to bridges/testing/framework/js-helpers/wrapped-assets-balance.js diff --git a/bridges/testing/utils/bridges.sh b/bridges/testing/framework/utils/bridges.sh similarity index 98% rename from bridges/testing/utils/bridges.sh rename to bridges/testing/framework/utils/bridges.sh index cfde5dfd26b..7c839946158 100755 --- a/bridges/testing/utils/bridges.sh +++ b/bridges/testing/framework/utils/bridges.sh @@ -41,8 +41,8 @@ function ensure_polkadot_js_api() { echo "" echo "" echo "-------------------" - echo "Installing (nodejs) sub module: $(dirname "$0")/generate_hex_encoded_call" - pushd $(dirname "$0")/generate_hex_encoded_call + echo "Installing (nodejs) sub module: ${BASH_SOURCE%/*}/generate_hex_encoded_call" + pushd ${BASH_SOURCE%/*}/generate_hex_encoded_call npm install popd fi diff --git a/bridges/testing/utils/common.sh b/bridges/testing/framework/utils/common.sh similarity index 100% rename from bridges/testing/utils/common.sh rename to bridges/testing/framework/utils/common.sh diff --git a/bridges/testing/utils/generate_hex_encoded_call/index.js b/bridges/testing/framework/utils/generate_hex_encoded_call/index.js similarity index 100% rename from bridges/testing/utils/generate_hex_encoded_call/index.js rename to bridges/testing/framework/utils/generate_hex_encoded_call/index.js diff --git a/bridges/testing/utils/generate_hex_encoded_call/package-lock.json b/bridges/testing/framework/utils/generate_hex_encoded_call/package-lock.json similarity index 100% rename from bridges/testing/utils/generate_hex_encoded_call/package-lock.json rename to bridges/testing/framework/utils/generate_hex_encoded_call/package-lock.json diff --git a/bridges/testing/utils/generate_hex_encoded_call/package.json b/bridges/testing/framework/utils/generate_hex_encoded_call/package.json similarity index 100% rename from bridges/testing/utils/generate_hex_encoded_call/package.json rename to bridges/testing/framework/utils/generate_hex_encoded_call/package.json diff --git a/bridges/testing/utils/zombienet.sh b/bridges/testing/framework/utils/zombienet.sh similarity index 100% rename from bridges/testing/utils/zombienet.sh rename to bridges/testing/framework/utils/zombienet.sh diff --git a/bridges/testing/run-new-test.sh b/bridges/testing/run-new-test.sh index 2ed2a412b8a..7c84a69aa47 100755 --- a/bridges/testing/run-new-test.sh +++ b/bridges/testing/run-new-test.sh @@ -21,6 +21,7 @@ do done export POLKADOT_SDK_PATH=`realpath ${BASH_SOURCE%/*}/../..` +export FRAMEWORK_PATH=`realpath ${BASH_SOURCE%/*}/framework` # set path to binaries if [ "$ZOMBIENET_DOCKER_PATHS" -eq 1 ]; then diff --git a/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl b/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl index 203c95b73eb..4725362ae82 100644 --- a/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl +++ b/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl @@ -1,12 +1,12 @@ Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back -Network: ../../environments/rococo-westend/bridge_hub_westend_local_network.toml +Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml Creds: config # send ROC to //Alice from Rococo AH to Westend AH -asset-hub-westend-collator1: run ../../environments/rococo-westend/helper.sh with "reserve-transfer-assets-from-asset-hub-rococo-local" within 120 seconds +asset-hub-westend-collator1: run {{ENV_PATH}}/helper.sh with "reserve-transfer-assets-from-asset-hub-rococo-local" within 120 seconds # check that //Alice received the ROC on Westend AH -asset-hub-westend-collator1: js-script ../../js-helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,0,Rococo" within 300 seconds +asset-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,0,Rococo" within 300 seconds # check that the relayer //Charlie is rewarded by Westend AH -bridge-hub-westend-collator1: js-script ../../js-helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x6268726F,ThisChain,0" within 30 seconds +bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x6268726F,ThisChain,0" within 30 seconds diff --git a/bridges/testing/tests/0001-asset-transfer/run.sh b/bridges/testing/tests/0001-asset-transfer/run.sh index 1e0d6cad301..a7bb122919b 100755 --- a/bridges/testing/tests/0001-asset-transfer/run.sh +++ b/bridges/testing/tests/0001-asset-transfer/run.sh @@ -2,10 +2,12 @@ set -e -source "${BASH_SOURCE%/*}/../../utils/common.sh" -source "${BASH_SOURCE%/*}/../../utils/zombienet.sh" +source "${BASH_SOURCE%/*}/../../framework/utils/common.sh" +source "${BASH_SOURCE%/*}/../../framework/utils/zombienet.sh" -${BASH_SOURCE%/*}/../../environments/rococo-westend/spawn.sh --init --start-relayer & +export ENV_PATH=`realpath ${BASH_SOURCE%/*}/../../environments/rococo-westend` + +$ENV_PATH/spawn.sh --init --start-relayer & env_pid=$! ensure_process_file $env_pid $TEST_DIR/rococo.env 600 diff --git a/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl b/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl index bbd95db9cfd..77267239c3b 100644 --- a/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl +++ b/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl @@ -1,12 +1,12 @@ Description: User is able to transfer WND from Westend Asset Hub to Rococo Asset Hub and back -Network: ../../environments/rococo-westend/bridge_hub_rococo_local_network.toml +Network: {{ENV_PATH}}/bridge_hub_rococo_local_network.toml Creds: config # send WND to //Alice from Westend AH to Rococo AH -asset-hub-rococo-collator1: run ../../environments/rococo-westend/helper.sh with "reserve-transfer-assets-from-asset-hub-westend-local" within 120 seconds +asset-hub-rococo-collator1: run {{ENV_PATH}}/helper.sh with "reserve-transfer-assets-from-asset-hub-westend-local" within 120 seconds # check that //Alice received the WND on Rococo AH -asset-hub-rococo-collator1: js-script ../../js-helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,0,Westend" within 300 seconds +asset-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,0,Westend" within 300 seconds # check that the relayer //Charlie is rewarded by Rococo AH -bridge-hub-rococo-collator1: js-script ../../js-helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x62687764,ThisChain,0" within 30 seconds +bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x62687764,ThisChain,0" within 30 seconds diff --git a/bridges/testing/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl b/bridges/testing/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl index 4c0a4675234..f72b76e6026 100644 --- a/bridges/testing/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl +++ b/bridges/testing/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl @@ -1,10 +1,10 @@ Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back -Network: ../../environments/rococo-westend/bridge_hub_westend_local_network.toml +Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml Creds: config # send wROC back to Alice from Westend AH to Rococo AH -asset-hub-rococo-collator1: run ../../environments/rococo-westend/helper.sh with "withdraw-reserve-assets-from-asset-hub-westend-local" within 120 seconds +asset-hub-rococo-collator1: run {{ENV_PATH}}/helper.sh with "withdraw-reserve-assets-from-asset-hub-westend-local" within 120 seconds # check that //Alice received the wROC on Rococo AH # (we wait until //Alice account increases here - there are no other transactions that may increase it) -asset-hub-rococo-collator1: js-script ../../js-helpers/native-assets-balance-increased.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" within 300 seconds +asset-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-assets-balance-increased.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" within 300 seconds diff --git a/bridges/testing/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl b/bridges/testing/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl index 3acded97d5c..9d893c8d90a 100644 --- a/bridges/testing/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl +++ b/bridges/testing/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl @@ -1,10 +1,10 @@ Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back -Network: ../../environments/rococo-westend/bridge_hub_westend_local_network.toml +Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml Creds: config # send wWND back to Alice from Rococo AH to Westend AH -asset-hub-westend-collator1: run ../../environments/rococo-westend/helper.sh with "withdraw-reserve-assets-from-asset-hub-rococo-local" within 120 seconds +asset-hub-westend-collator1: run {{ENV_PATH}}/helper.sh with "withdraw-reserve-assets-from-asset-hub-rococo-local" within 120 seconds # check that //Alice received the wWND on Westend AH # (we wait until //Alice account increases here - there are no other transactions that may increase it) -asset-hub-westend-collator1: js-script ../../js-helpers/native-assets-balance-increased.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" within 300 seconds +asset-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-assets-balance-increased.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" within 300 seconds diff --git a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl index 82a1a103b14..6e381f53773 100644 --- a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl +++ b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl @@ -1,8 +1,8 @@ Description: While relayer is idle, we only sync mandatory Rococo (and a single Rococo BH) headers to Westend BH. -Network: ../../environments/rococo-westend/bridge_hub_westend_local_network.toml +Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml Creds: config # ensure that relayer is only syncing mandatory headers while idle. This includes both headers that were # generated while relay was offline and those in the next 100 seconds while script is active. -bridge-hub-westend-collator1: js-script ../../js-helpers/only-mandatory-headers-synced-when-idle.js with "300,rococo-at-westend" within 600 seconds +bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/only-mandatory-headers-synced-when-idle.js with "300,rococo-at-westend" within 600 seconds diff --git a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh index c39796d51d5..7d5b8d92736 100755 --- a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh +++ b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh @@ -2,12 +2,12 @@ set -e -source "${BASH_SOURCE%/*}/../../utils/common.sh" -source "${BASH_SOURCE%/*}/../../utils/zombienet.sh" +source "${BASH_SOURCE%/*}/../../framework/utils/common.sh" +source "${BASH_SOURCE%/*}/../../framework/utils/zombienet.sh" -# We use `--relayer-delay` in order to sleep some time before starting relayer. -# We want to sleep for at least 1 session, which is expected to be 60 seconds for test environment. -${BASH_SOURCE%/*}/../../environments/rococo-westend/spawn.sh & +export ENV_PATH=`realpath ${BASH_SOURCE%/*}/../../environments/rococo-westend` + +$ENV_PATH/spawn.sh & env_pid=$! ensure_process_file $env_pid $TEST_DIR/rococo.env 600 diff --git a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl index 86581324625..b4b3e436791 100644 --- a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl +++ b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl @@ -1,7 +1,7 @@ Description: While relayer is idle, we only sync mandatory Westend (and a single Westend BH) headers to Rococo BH. -Network: ../../environments/rococo-westend/bridge_hub_rococo_local_network.toml +Network: {{ENV_PATH}}/bridge_hub_rococo_local_network.toml Creds: config # ensure that relayer is only syncing mandatory headers while idle. This includes both headers that were # generated while relay was offline and those in the next 100 seconds while script is active. -bridge-hub-rococo-collator1: js-script ../../js-helpers/only-mandatory-headers-synced-when-idle.js with "300,westend-at-rococo" within 600 seconds +bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/only-mandatory-headers-synced-when-idle.js with "300,westend-at-rococo" within 600 seconds diff --git a/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile b/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile index fde9cc6e7cf..4bfb73acda0 100644 --- a/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile +++ b/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile @@ -45,7 +45,7 @@ RUN mkdir -p /home/nonroot/bridges-polkadot-sdk COPY ./artifacts/bridges-polkadot-sdk /home/nonroot/bridges-polkadot-sdk # also prepare `generate_hex_encoded_call` for running RUN set -eux; \ - cd /home/nonroot/bridges-polkadot-sdk/bridges/testing/utils/generate_hex_encoded_call; \ + cd /home/nonroot/bridges-polkadot-sdk/bridges/testing/framework/utils/generate_hex_encoded_call; \ npm install # check if executable works in this container -- GitLab From 2431001ec012334226bc181d69a754c49f168328 Mon Sep 17 00:00:00 2001 From: Andrei Sandu <54316454+sandreim@users.noreply.github.com> Date: Fri, 23 Feb 2024 23:35:48 +0700 Subject: [PATCH 122/133] Runtime: allow backing multiple candidates of same parachain on different cores (#3231) Fixes https://github.com/paritytech/polkadot-sdk/issues/3144 Builds on top of https://github.com/paritytech/polkadot-sdk/pull/3229 ### Summary Some preparations for Runtime to support elastic scaling, guarded by config node features bit `FeatureIndex::ElasticScalingMVP`. This PR introduces a per-candidate `CoreIndex` but does it in a hacky way to avoid changing `CandidateCommitments`, `CandidateReceipts` primitives and networking protocols. #### Including `CoreIndex` in `BackedCandidate` If the `ElasticScalingMVP` feature bit is enabled then `BackedCandidate::validator_indices` is extended by 8 bits. The value stored in these bits represents the assumed core index for the candidate. It is temporary solution which works by creating a mapping from `BackedCandidate` to `CoreIndex` by assuming the `CoreIndex` can be discovered by checking in which validator group the validator that signed the statement is. TODO: - [x] fix tests - [x] add new tests - [x] Bump runtime API for Kusama, so we have that node features thing! -> https://github.com/polkadot-fellows/runtimes/pull/194 --------- Signed-off-by: Andrei Sandu Signed-off-by: alindima Co-authored-by: alindima --- .gitlab/pipeline/zombienet/polkadot.yml | 8 + Cargo.lock | 37 ++ polkadot/node/core/backing/Cargo.toml | 1 + polkadot/node/core/backing/src/lib.rs | 19 +- polkadot/node/core/backing/src/tests/mod.rs | 70 +- polkadot/node/core/provisioner/src/lib.rs | 2 +- polkadot/node/core/provisioner/src/tests.rs | 70 +- polkadot/primitives/src/v6/mod.rs | 208 +++++- polkadot/runtime/parachains/Cargo.toml | 1 + polkadot/runtime/parachains/src/builder.rs | 7 +- .../runtime/parachains/src/inclusion/mod.rs | 91 +-- .../runtime/parachains/src/inclusion/tests.rs | 423 +++++++++--- .../src/paras_inherent/benchmarking.rs | 4 +- .../parachains/src/paras_inherent/mod.rs | 288 ++++++--- .../parachains/src/paras_inherent/tests.rs | 604 ++++++++++++++++-- .../parachains/src/paras_inherent/weights.rs | 4 +- .../functional/0012-elastic-scaling-mvp.toml | 38 ++ .../functional/0012-elastic-scaling-mvp.zndsl | 28 + .../functional/0012-enable-node-feature.js | 37 ++ .../functional/0012-register-para.js | 37 ++ prdoc/pr_3231.prdoc | 11 + 21 files changed, 1640 insertions(+), 348 deletions(-) create mode 100644 polkadot/zombienet_tests/functional/0012-elastic-scaling-mvp.toml create mode 100644 polkadot/zombienet_tests/functional/0012-elastic-scaling-mvp.zndsl create mode 100644 polkadot/zombienet_tests/functional/0012-enable-node-feature.js create mode 100644 polkadot/zombienet_tests/functional/0012-register-para.js create mode 100644 prdoc/pr_3231.prdoc diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml index 54eb6db48ca..97572f029d0 100644 --- a/.gitlab/pipeline/zombienet/polkadot.yml +++ b/.gitlab/pipeline/zombienet/polkadot.yml @@ -158,6 +158,14 @@ zombienet-polkadot-functional-0011-async-backing-6-seconds-rate: --local-dir="${LOCAL_DIR}/functional" --test="0011-async-backing-6-seconds-rate.zndsl" +zombienet-polkadot-functional-0012-elastic-scaling-mvp: + extends: + - .zombienet-polkadot-common + script: + - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh + --local-dir="${LOCAL_DIR}/functional" + --test="0012-elastic-scaling-mvp.zndsl" + zombienet-polkadot-smoke-0001-parachains-smoke-test: extends: - .zombienet-polkadot-common diff --git a/Cargo.lock b/Cargo.lock index fbe2729acba..a23be53b34f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12512,6 +12512,7 @@ dependencies = [ "polkadot-primitives", "polkadot-primitives-test-helpers", "polkadot-statement-table", + "rstest", "sc-keystore", "schnellru", "sp-application-crypto", @@ -13350,6 +13351,7 @@ dependencies = [ "polkadot-runtime-metrics", "rand", "rand_chacha 0.3.1", + "rstest", "rustc-hex", "sc-keystore", "scale-info", @@ -14790,6 +14792,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "relative-path" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e898588f33fdd5b9420719948f9f2a32c922a246964576f71ba7f24f80610fbc" + [[package]] name = "remote-ext-tests-bags-list" version = "1.0.0" @@ -15172,6 +15180,35 @@ dependencies = [ "winapi", ] +[[package]] +name = "rstest" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version 0.4.0", +] + +[[package]] +name = "rstest_macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" +dependencies = [ + "cfg-if", + "glob", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version 0.4.0", + "syn 2.0.50", + "unicode-ident", +] + [[package]] name = "rtnetlink" version = "0.10.1" diff --git a/polkadot/node/core/backing/Cargo.toml b/polkadot/node/core/backing/Cargo.toml index f71b8df80dd..d0c1f9aa483 100644 --- a/polkadot/node/core/backing/Cargo.toml +++ b/polkadot/node/core/backing/Cargo.toml @@ -32,5 +32,6 @@ sc-keystore = { path = "../../../../substrate/client/keystore" } sp-tracing = { path = "../../../../substrate/primitives/tracing" } futures = { version = "0.3.21", features = ["thread-pool"] } assert_matches = "1.4.0" +rstest = "0.18.2" polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" } test-helpers = { package = "polkadot-primitives-test-helpers", path = "../../../primitives/test-helpers" } diff --git a/polkadot/node/core/backing/src/lib.rs b/polkadot/node/core/backing/src/lib.rs index 26f20a6d48e..69bf2e956a0 100644 --- a/polkadot/node/core/backing/src/lib.rs +++ b/polkadot/node/core/backing/src/lib.rs @@ -70,7 +70,7 @@ use std::{ sync::Arc, }; -use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec}; +use bitvec::vec::BitVec; use futures::{ channel::{mpsc, oneshot}, future::BoxFuture, @@ -494,20 +494,15 @@ fn table_attested_to_backed( } vote_positions.sort_by_key(|(_orig, pos_in_group)| *pos_in_group); - if inject_core_index { - let core_index_to_inject: BitVec = - BitVec::from_vec(vec![core_index.0 as u8]); - validator_indices.extend(core_index_to_inject); - } - - Some(BackedCandidate { + Some(BackedCandidate::new( candidate, - validity_votes: vote_positions + vote_positions .into_iter() .map(|(pos_in_votes, _pos_in_group)| validity_votes[pos_in_votes].clone()) .collect(), validator_indices, - }) + inject_core_index.then_some(core_index), + )) } async fn store_available_data( @@ -1775,7 +1770,7 @@ async fn post_import_statement_actions( &rp_state.table_context, rp_state.inject_core_index, ) { - let para_id = backed.candidate.descriptor.para_id; + let para_id = backed.candidate().descriptor.para_id; gum::debug!( target: LOG_TARGET, candidate_hash = ?candidate_hash, @@ -1796,7 +1791,7 @@ async fn post_import_statement_actions( // notify collator protocol. ctx.send_message(CollatorProtocolMessage::Backed { para_id, - para_head: backed.candidate.descriptor.para_head, + para_head: backed.candidate().descriptor.para_head, }) .await; // Notify statement distribution of backed candidate. diff --git a/polkadot/node/core/backing/src/tests/mod.rs b/polkadot/node/core/backing/src/tests/mod.rs index 7223f1e1dfb..e3cc5727435 100644 --- a/polkadot/node/core/backing/src/tests/mod.rs +++ b/polkadot/node/core/backing/src/tests/mod.rs @@ -33,9 +33,10 @@ use polkadot_node_subsystem::{ }; use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_primitives::{ - CandidateDescriptor, GroupRotationInfo, HeadData, PersistedValidationData, PvfExecKind, - ScheduledCore, SessionIndex, LEGACY_MIN_BACKING_VOTES, + vstaging::node_features, CandidateDescriptor, GroupRotationInfo, HeadData, + PersistedValidationData, PvfExecKind, ScheduledCore, SessionIndex, LEGACY_MIN_BACKING_VOTES, }; +use rstest::rstest; use sp_application_crypto::AppCrypto; use sp_keyring::Sr25519Keyring; use sp_keystore::Keystore; @@ -79,6 +80,7 @@ pub(crate) struct TestState { relay_parent: Hash, minimum_backing_votes: u32, disabled_validators: Vec, + node_features: NodeFeatures, } impl TestState { @@ -157,6 +159,7 @@ impl Default for TestState { relay_parent, minimum_backing_votes: LEGACY_MIN_BACKING_VOTES, disabled_validators: Vec::new(), + node_features: Default::default(), } } } @@ -298,7 +301,7 @@ async fn test_startup(virtual_overseer: &mut VirtualOverseer, test_state: &TestS AllMessages::RuntimeApi( RuntimeApiMessage::Request(_parent, RuntimeApiRequest::NodeFeatures(_session_index, tx)) ) => { - tx.send(Ok(Default::default())).unwrap(); + tx.send(Ok(test_state.node_features.clone())).unwrap(); } ); @@ -494,9 +497,20 @@ fn backing_second_works() { } // Test that the candidate reaches quorum successfully. -#[test] -fn backing_works() { - let test_state = TestState::default(); +#[rstest] +#[case(true)] +#[case(false)] +fn backing_works(#[case] elastic_scaling_mvp: bool) { + let mut test_state = TestState::default(); + if elastic_scaling_mvp { + test_state + .node_features + .resize((node_features::FeatureIndex::ElasticScalingMVP as u8 + 1) as usize, false); + test_state + .node_features + .set(node_features::FeatureIndex::ElasticScalingMVP as u8 as usize, true); + } + test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { test_startup(&mut virtual_overseer, &test_state).await; @@ -647,6 +661,31 @@ fn backing_works() { virtual_overseer.send(FromOrchestra::Communication { msg: statement }).await; + let (tx, rx) = oneshot::channel(); + let msg = CandidateBackingMessage::GetBackedCandidates( + vec![(candidate_a_hash, test_state.relay_parent)], + tx, + ); + + virtual_overseer.send(FromOrchestra::Communication { msg }).await; + + let candidates = rx.await.unwrap(); + assert_eq!(1, candidates.len()); + assert_eq!(candidates[0].validity_votes().len(), 3); + + let (validator_indices, maybe_core_index) = + candidates[0].validator_indices_and_core_index(elastic_scaling_mvp); + if elastic_scaling_mvp { + assert_eq!(maybe_core_index.unwrap(), CoreIndex(0)); + } else { + assert!(maybe_core_index.is_none()); + } + + assert_eq!( + validator_indices, + bitvec::bitvec![u8, bitvec::order::Lsb0; 1, 1, 0, 1].as_bitslice() + ); + virtual_overseer .send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves( ActiveLeavesUpdate::stop_work(test_state.relay_parent), @@ -919,20 +958,20 @@ fn backing_works_while_validation_ongoing() { let candidates = rx.await.unwrap(); assert_eq!(1, candidates.len()); - assert_eq!(candidates[0].validity_votes.len(), 3); + assert_eq!(candidates[0].validity_votes().len(), 3); assert!(candidates[0] - .validity_votes + .validity_votes() .contains(&ValidityAttestation::Implicit(signed_a.signature().clone()))); assert!(candidates[0] - .validity_votes + .validity_votes() .contains(&ValidityAttestation::Explicit(signed_b.signature().clone()))); assert!(candidates[0] - .validity_votes + .validity_votes() .contains(&ValidityAttestation::Explicit(signed_c.signature().clone()))); assert_eq!( - candidates[0].validator_indices, - bitvec::bitvec![u8, bitvec::order::Lsb0; 1, 0, 1, 1], + candidates[0].validator_indices_and_core_index(false), + (bitvec::bitvec![u8, bitvec::order::Lsb0; 1, 0, 1, 1].as_bitslice(), None) ); virtual_overseer @@ -1604,8 +1643,11 @@ fn candidate_backing_reorders_votes() { let expected_attestations = vec![fake_attestation(1).into(), fake_attestation(3).into(), fake_attestation(5).into()]; - assert_eq!(backed.validator_indices, expected_bitvec); - assert_eq!(backed.validity_votes, expected_attestations); + assert_eq!( + backed.validator_indices_and_core_index(false), + (expected_bitvec.as_bitslice(), None) + ); + assert_eq!(backed.validity_votes(), expected_attestations); } // Test whether we retry on failed PoV fetching. diff --git a/polkadot/node/core/provisioner/src/lib.rs b/polkadot/node/core/provisioner/src/lib.rs index d98f6ebfe42..a29cf72afb1 100644 --- a/polkadot/node/core/provisioner/src/lib.rs +++ b/polkadot/node/core/provisioner/src/lib.rs @@ -766,7 +766,7 @@ async fn select_candidates( // keep only one candidate with validation code. let mut with_validation_code = false; candidates.retain(|c| { - if c.candidate.commitments.new_validation_code.is_some() { + if c.candidate().commitments.new_validation_code.is_some() { if with_validation_code { return false } diff --git a/polkadot/node/core/provisioner/src/tests.rs b/polkadot/node/core/provisioner/src/tests.rs index b26df8ddb91..87c0e7a65d3 100644 --- a/polkadot/node/core/provisioner/src/tests.rs +++ b/polkadot/node/core/provisioner/src/tests.rs @@ -460,13 +460,16 @@ mod select_candidates { let expected_backed = expected_candidates .iter() - .map(|c| BackedCandidate { - candidate: CommittedCandidateReceipt { - descriptor: c.descriptor.clone(), - commitments: Default::default(), - }, - validity_votes: Vec::new(), - validator_indices: default_bitvec(MOCK_GROUP_SIZE), + .map(|c| { + BackedCandidate::new( + CommittedCandidateReceipt { + descriptor: c.descriptor().clone(), + commitments: Default::default(), + }, + Vec::new(), + default_bitvec(MOCK_GROUP_SIZE), + None, + ) }) .collect(); @@ -486,7 +489,7 @@ mod select_candidates { result.into_iter().for_each(|c| { assert!( - expected_candidates.iter().any(|c2| c.candidate.corresponds_to(c2)), + expected_candidates.iter().any(|c2| c.candidate().corresponds_to(c2)), "Failed to find candidate: {:?}", c, ) @@ -532,10 +535,13 @@ mod select_candidates { // Build possible outputs from select_candidates let backed_candidates: Vec<_> = committed_receipts .iter() - .map(|committed_receipt| BackedCandidate { - candidate: committed_receipt.clone(), - validity_votes: Vec::new(), - validator_indices: default_bitvec(MOCK_GROUP_SIZE), + .map(|committed_receipt| { + BackedCandidate::new( + committed_receipt.clone(), + Vec::new(), + default_bitvec(MOCK_GROUP_SIZE), + None, + ) }) .collect(); @@ -566,7 +572,7 @@ mod select_candidates { result.into_iter().for_each(|c| { assert!( - expected_backed_filtered.iter().any(|c2| c.candidate.corresponds_to(c2)), + expected_backed_filtered.iter().any(|c2| c.candidate().corresponds_to(c2)), "Failed to find candidate: {:?}", c, ) @@ -605,13 +611,16 @@ mod select_candidates { let expected_backed = expected_candidates .iter() - .map(|c| BackedCandidate { - candidate: CommittedCandidateReceipt { - descriptor: c.descriptor.clone(), - commitments: Default::default(), - }, - validity_votes: Vec::new(), - validator_indices: default_bitvec(MOCK_GROUP_SIZE), + .map(|c| { + BackedCandidate::new( + CommittedCandidateReceipt { + descriptor: c.descriptor.clone(), + commitments: Default::default(), + }, + Vec::new(), + default_bitvec(MOCK_GROUP_SIZE), + None, + ) }) .collect(); @@ -631,7 +640,7 @@ mod select_candidates { result.into_iter().for_each(|c| { assert!( - expected_candidates.iter().any(|c2| c.candidate.corresponds_to(c2)), + expected_candidates.iter().any(|c2| c.candidate().corresponds_to(c2)), "Failed to find candidate: {:?}", c, ) @@ -671,13 +680,16 @@ mod select_candidates { let expected_backed = expected_candidates .iter() - .map(|c| BackedCandidate { - candidate: CommittedCandidateReceipt { - descriptor: c.descriptor.clone(), - commitments: Default::default(), - }, - validity_votes: Vec::new(), - validator_indices: default_bitvec(MOCK_GROUP_SIZE), + .map(|c| { + BackedCandidate::new( + CommittedCandidateReceipt { + descriptor: c.descriptor().clone(), + commitments: Default::default(), + }, + Vec::new(), + default_bitvec(MOCK_GROUP_SIZE), + None, + ) }) .collect(); @@ -697,7 +709,7 @@ mod select_candidates { result.into_iter().for_each(|c| { assert!( - expected_candidates.iter().any(|c2| c.candidate.corresponds_to(c2)), + expected_candidates.iter().any(|c2| c.candidate().corresponds_to(c2)), "Failed to find candidate: {:?}", c, ) diff --git a/polkadot/primitives/src/v6/mod.rs b/polkadot/primitives/src/v6/mod.rs index 538eb385584..89431f7801f 100644 --- a/polkadot/primitives/src/v6/mod.rs +++ b/polkadot/primitives/src/v6/mod.rs @@ -16,7 +16,7 @@ //! `V6` Primitives. -use bitvec::vec::BitVec; +use bitvec::{field::BitField, slice::BitSlice, vec::BitVec}; use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_std::{ @@ -707,19 +707,50 @@ pub type UncheckedSignedAvailabilityBitfields = Vec { /// The candidate referred to. - pub candidate: CommittedCandidateReceipt, + candidate: CommittedCandidateReceipt, /// The validity votes themselves, expressed as signatures. - pub validity_votes: Vec, - /// The indices of the validators within the group, expressed as a bitfield. - pub validator_indices: BitVec, + validity_votes: Vec, + /// The indices of the validators within the group, expressed as a bitfield. May be extended + /// beyond the backing group size to contain the assigned core index, if ElasticScalingMVP is + /// enabled. + validator_indices: BitVec, } impl BackedCandidate { - /// Get a reference to the descriptor of the para. + /// Constructor + pub fn new( + candidate: CommittedCandidateReceipt, + validity_votes: Vec, + validator_indices: BitVec, + core_index: Option, + ) -> Self { + let mut instance = Self { candidate, validity_votes, validator_indices }; + if let Some(core_index) = core_index { + instance.inject_core_index(core_index); + } + instance + } + + /// Get a reference to the descriptor of the candidate. pub fn descriptor(&self) -> &CandidateDescriptor { &self.candidate.descriptor } + /// Get a reference to the committed candidate receipt of the candidate. + pub fn candidate(&self) -> &CommittedCandidateReceipt { + &self.candidate + } + + /// Get a reference to the validity votes of the candidate. + pub fn validity_votes(&self) -> &[ValidityAttestation] { + &self.validity_votes + } + + /// Get a mutable reference to validity votes of the para. + pub fn validity_votes_mut(&mut self) -> &mut Vec { + &mut self.validity_votes + } + /// Compute this candidate's hash. pub fn hash(&self) -> CandidateHash where @@ -735,6 +766,48 @@ impl BackedCandidate { { self.candidate.to_plain() } + + /// Get a copy of the validator indices and the assumed core index, if any. + pub fn validator_indices_and_core_index( + &self, + core_index_enabled: bool, + ) -> (&BitSlice, Option) { + // This flag tells us if the block producers must enable Elastic Scaling MVP hack. + // It extends `BackedCandidate::validity_indices` to store a 8 bit core index. + if core_index_enabled { + let core_idx_offset = self.validator_indices.len().saturating_sub(8); + if core_idx_offset > 0 { + let (validator_indices_slice, core_idx_slice) = + self.validator_indices.split_at(core_idx_offset); + return ( + validator_indices_slice, + Some(CoreIndex(core_idx_slice.load::() as u32)), + ); + } + } + + (&self.validator_indices, None) + } + + /// Inject a core index in the validator_indices bitvec. + fn inject_core_index(&mut self, core_index: CoreIndex) { + let core_index_to_inject: BitVec = + BitVec::from_vec(vec![core_index.0 as u8]); + self.validator_indices.extend(core_index_to_inject); + } + + /// Update the validator indices and core index in the candidate. + pub fn set_validator_indices_and_core_index( + &mut self, + new_indices: BitVec, + maybe_core_index: Option, + ) { + self.validator_indices = new_indices; + + if let Some(core_index) = maybe_core_index { + self.inject_core_index(core_index); + } + } } /// Verify the backing of the given candidate. @@ -748,44 +821,42 @@ impl BackedCandidate { /// Returns either an error, indicating that one of the signatures was invalid or that the index /// was out-of-bounds, or the number of signatures checked. pub fn check_candidate_backing + Clone + Encode + core::fmt::Debug>( - backed: &BackedCandidate, + candidate_hash: CandidateHash, + validity_votes: &[ValidityAttestation], + validator_indices: &BitSlice, signing_context: &SigningContext, group_len: usize, validator_lookup: impl Fn(usize) -> Option, ) -> Result { - if backed.validator_indices.len() != group_len { + if validator_indices.len() != group_len { log::debug!( target: LOG_TARGET, "Check candidate backing: indices mismatch: group_len = {} , indices_len = {}", group_len, - backed.validator_indices.len(), + validator_indices.len(), ); return Err(()) } - if backed.validity_votes.len() > group_len { + if validity_votes.len() > group_len { log::debug!( target: LOG_TARGET, "Check candidate backing: Too many votes, expected: {}, found: {}", group_len, - backed.validity_votes.len(), + validity_votes.len(), ); return Err(()) } - // this is known, even in runtime, to be blake2-256. - let hash = backed.candidate.hash(); - let mut signed = 0; - for ((val_in_group_idx, _), attestation) in backed - .validator_indices + for ((val_in_group_idx, _), attestation) in validator_indices .iter() .enumerate() .filter(|(_, signed)| **signed) - .zip(backed.validity_votes.iter()) + .zip(validity_votes.iter()) { let validator_id = validator_lookup(val_in_group_idx).ok_or(())?; - let payload = attestation.signed_payload(hash, signing_context); + let payload = attestation.signed_payload(candidate_hash, signing_context); let sig = attestation.signature(); if sig.verify(&payload[..], &validator_id) { @@ -801,11 +872,11 @@ pub fn check_candidate_backing + Clone + Encode + core::fmt::Debu } } - if signed != backed.validity_votes.len() { + if signed != validity_votes.len() { log::error!( target: LOG_TARGET, "Check candidate backing: Too many signatures, expected = {}, found = {}", - backed.validity_votes.len() , + validity_votes.len(), signed, ); return Err(()) @@ -1884,6 +1955,34 @@ pub enum PvfExecKind { #[cfg(test)] mod tests { use super::*; + use bitvec::bitvec; + use primitives::sr25519; + + pub fn dummy_committed_candidate_receipt() -> CommittedCandidateReceipt { + let zeros = Hash::zero(); + + CommittedCandidateReceipt { + descriptor: CandidateDescriptor { + para_id: 0.into(), + relay_parent: zeros, + collator: CollatorId::from(sr25519::Public::from_raw([0; 32])), + persisted_validation_data_hash: zeros, + pov_hash: zeros, + erasure_root: zeros, + signature: CollatorSignature::from(sr25519::Signature([0u8; 64])), + para_head: zeros, + validation_code_hash: ValidationCode(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]).hash(), + }, + commitments: CandidateCommitments { + head_data: HeadData(vec![]), + upward_messages: vec![].try_into().expect("empty vec fits within bounds"), + new_validation_code: None, + horizontal_messages: vec![].try_into().expect("empty vec fits within bounds"), + processed_downward_messages: 0, + hrmp_watermark: 0_u32, + }, + } + } #[test] fn group_rotation_info_calculations() { @@ -1958,4 +2057,73 @@ mod tests { assert!(zero_b.leading_zeros() >= zero_u.leading_zeros()); } + + #[test] + fn test_backed_candidate_injected_core_index() { + let initial_validator_indices = bitvec![u8, bitvec::order::Lsb0; 0, 1, 0, 1]; + let mut candidate = BackedCandidate::new( + dummy_committed_candidate_receipt(), + vec![], + initial_validator_indices.clone(), + None, + ); + + // No core index supplied, ElasticScalingMVP is off. + let (validator_indices, core_index) = candidate.validator_indices_and_core_index(false); + assert_eq!(validator_indices, initial_validator_indices.as_bitslice()); + assert!(core_index.is_none()); + + // No core index supplied, ElasticScalingMVP is on. Still, decoding will be ok if backing + // group size is <= 8, to give a chance to parachains that don't have multiple cores + // assigned. + let (validator_indices, core_index) = candidate.validator_indices_and_core_index(true); + assert_eq!(validator_indices, initial_validator_indices.as_bitslice()); + assert!(core_index.is_none()); + + let encoded_validator_indices = candidate.validator_indices.clone(); + candidate.set_validator_indices_and_core_index(validator_indices.into(), core_index); + assert_eq!(candidate.validator_indices, encoded_validator_indices); + + // No core index supplied, ElasticScalingMVP is on. Decoding is corrupted if backing group + // size larger than 8. + let candidate = BackedCandidate::new( + dummy_committed_candidate_receipt(), + vec![], + bitvec![u8, bitvec::order::Lsb0; 0, 1, 0, 1, 0, 1, 0, 1, 0], + None, + ); + let (validator_indices, core_index) = candidate.validator_indices_and_core_index(true); + assert_eq!(validator_indices, bitvec![u8, bitvec::order::Lsb0; 0].as_bitslice()); + assert!(core_index.is_some()); + + // Core index supplied, ElasticScalingMVP is off. Core index will be treated as normal + // validator indices. Runtime will check against this. + let candidate = BackedCandidate::new( + dummy_committed_candidate_receipt(), + vec![], + bitvec![u8, bitvec::order::Lsb0; 0, 1, 0, 1], + Some(CoreIndex(10)), + ); + let (validator_indices, core_index) = candidate.validator_indices_and_core_index(false); + assert_eq!( + validator_indices, + bitvec![u8, bitvec::order::Lsb0; 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0] + ); + assert!(core_index.is_none()); + + // Core index supplied, ElasticScalingMVP is on. + let mut candidate = BackedCandidate::new( + dummy_committed_candidate_receipt(), + vec![], + bitvec![u8, bitvec::order::Lsb0; 0, 1, 0, 1], + Some(CoreIndex(10)), + ); + let (validator_indices, core_index) = candidate.validator_indices_and_core_index(true); + assert_eq!(validator_indices, bitvec![u8, bitvec::order::Lsb0; 0, 1, 0, 1]); + assert_eq!(core_index, Some(CoreIndex(10))); + + let encoded_validator_indices = candidate.validator_indices.clone(); + candidate.set_validator_indices_and_core_index(validator_indices.into(), core_index); + assert_eq!(candidate.validator_indices, encoded_validator_indices); + } } diff --git a/polkadot/runtime/parachains/Cargo.toml b/polkadot/runtime/parachains/Cargo.toml index 311a62b6c91..61040145476 100644 --- a/polkadot/runtime/parachains/Cargo.toml +++ b/polkadot/runtime/parachains/Cargo.toml @@ -69,6 +69,7 @@ sp-tracing = { path = "../../../substrate/primitives/tracing" } sp-crypto-hashing = { path = "../../../substrate/primitives/crypto/hashing" } thousands = "0.2.0" assert_matches = "1" +rstest = "0.18.2" serde_json = { workspace = true, default-features = true } [features] diff --git a/polkadot/runtime/parachains/src/builder.rs b/polkadot/runtime/parachains/src/builder.rs index 016b3fca589..500bc70cfa7 100644 --- a/polkadot/runtime/parachains/src/builder.rs +++ b/polkadot/runtime/parachains/src/builder.rs @@ -587,11 +587,12 @@ impl BenchBuilder { }) .collect(); - BackedCandidate:: { + BackedCandidate::::new( candidate, validity_votes, - validator_indices: bitvec::bitvec![u8, bitvec::order::Lsb0; 1; group_validators.len()], - } + bitvec::bitvec![u8, bitvec::order::Lsb0; 1; group_validators.len()], + None, + ) }) .collect() } diff --git a/polkadot/runtime/parachains/src/inclusion/mod.rs b/polkadot/runtime/parachains/src/inclusion/mod.rs index 90af9cde00a..16e2e93b561 100644 --- a/polkadot/runtime/parachains/src/inclusion/mod.rs +++ b/polkadot/runtime/parachains/src/inclusion/mod.rs @@ -47,10 +47,7 @@ use scale_info::TypeInfo; use sp_runtime::{traits::One, DispatchError, SaturatedConversion, Saturating}; #[cfg(feature = "std")] use sp_std::fmt; -use sp_std::{ - collections::{btree_map::BTreeMap, btree_set::BTreeSet}, - prelude::*, -}; +use sp_std::{collections::btree_set::BTreeSet, prelude::*}; pub use pallet::*; @@ -601,18 +598,16 @@ impl Pallet { /// scheduled cores. If these conditions are not met, the execution of the function fails. pub(crate) fn process_candidates( allowed_relay_parents: &AllowedRelayParentsTracker>, - candidates: Vec>, - scheduled: &BTreeMap, + candidates: Vec<(BackedCandidate, CoreIndex)>, group_validators: GV, + core_index_enabled: bool, ) -> Result, DispatchError> where GV: Fn(GroupIndex) -> Option>, { let now = >::block_number(); - ensure!(candidates.len() <= scheduled.len(), Error::::UnscheduledCandidate); - - if scheduled.is_empty() { + if candidates.is_empty() { return Ok(ProcessedCandidates::default()) } @@ -648,7 +643,7 @@ impl Pallet { // // In the meantime, we do certain sanity checks on the candidates and on the scheduled // list. - for (candidate_idx, backed_candidate) in candidates.iter().enumerate() { + for (candidate_idx, (backed_candidate, core_index)) in candidates.iter().enumerate() { let relay_parent_hash = backed_candidate.descriptor().relay_parent; let para_id = backed_candidate.descriptor().para_id; @@ -663,7 +658,7 @@ impl Pallet { let relay_parent_number = match check_ctx.verify_backed_candidate( &allowed_relay_parents, candidate_idx, - backed_candidate, + backed_candidate.candidate(), )? { Err(FailedToCreatePVD) => { log::debug!( @@ -679,11 +674,22 @@ impl Pallet { Ok(rpn) => rpn, }; - let para_id = backed_candidate.descriptor().para_id; + let (validator_indices, _) = + backed_candidate.validator_indices_and_core_index(core_index_enabled); + + log::debug!( + target: LOG_TARGET, + "Candidate {:?} on {:?}, + core_index_enabled = {}", + backed_candidate.hash(), + core_index, + core_index_enabled + ); + + check_assignment_in_order(core_index)?; + let mut backers = bitvec::bitvec![u8, BitOrderLsb0; 0; validators.len()]; - let core_idx = *scheduled.get(¶_id).ok_or(Error::::UnscheduledCandidate)?; - check_assignment_in_order(core_idx)?; ensure!( >::get(¶_id).is_none() && >::get(¶_id).is_none(), @@ -694,7 +700,7 @@ impl Pallet { // assigned to core at block `N + 1`. Thus, `relay_parent_number + 1` // will always land in the current session. let group_idx = >::group_assigned_to_core( - core_idx, + *core_index, relay_parent_number + One::one(), ) .ok_or_else(|| { @@ -711,7 +717,9 @@ impl Pallet { // check the signatures in the backing and that it is a majority. { let maybe_amount_validated = primitives::check_candidate_backing( - &backed_candidate, + backed_candidate.candidate().hash(), + backed_candidate.validity_votes(), + validator_indices, &signing_context, group_vals.len(), |intra_group_vi| { @@ -738,16 +746,15 @@ impl Pallet { let mut backer_idx_and_attestation = Vec::<(ValidatorIndex, ValidityAttestation)>::with_capacity( - backed_candidate.validator_indices.count_ones(), + validator_indices.count_ones(), ); let candidate_receipt = backed_candidate.receipt(); - for ((bit_idx, _), attestation) in backed_candidate - .validator_indices + for ((bit_idx, _), attestation) in validator_indices .iter() .enumerate() .filter(|(_, signed)| **signed) - .zip(backed_candidate.validity_votes.iter().cloned()) + .zip(backed_candidate.validity_votes().iter().cloned()) { let val_idx = group_vals.get(bit_idx).expect("this query succeeded above; qed"); @@ -760,7 +767,7 @@ impl Pallet { } core_indices_and_backers.push(( - (core_idx, para_id), + (*core_index, para_id), backers, group_idx, relay_parent_number, @@ -772,7 +779,7 @@ impl Pallet { // one more sweep for actually writing to storage. let core_indices = core_indices_and_backers.iter().map(|(c, ..)| *c).collect(); - for (candidate, (core, backers, group, relay_parent_number)) in + for ((candidate, _), (core, backers, group, relay_parent_number)) in candidates.into_iter().zip(core_indices_and_backers) { let para_id = candidate.descriptor().para_id; @@ -782,16 +789,18 @@ impl Pallet { bitvec::bitvec![u8, BitOrderLsb0; 0; validators.len()]; Self::deposit_event(Event::::CandidateBacked( - candidate.candidate.to_plain(), - candidate.candidate.commitments.head_data.clone(), + candidate.candidate().to_plain(), + candidate.candidate().commitments.head_data.clone(), core.0, group, )); - let candidate_hash = candidate.candidate.hash(); + let candidate_hash = candidate.candidate().hash(); - let (descriptor, commitments) = - (candidate.candidate.descriptor, candidate.candidate.commitments); + let (descriptor, commitments) = ( + candidate.candidate().descriptor.clone(), + candidate.candidate().commitments.clone(), + ); >::insert( ¶_id, @@ -1195,10 +1204,10 @@ impl CandidateCheckContext { &self, allowed_relay_parents: &AllowedRelayParentsTracker>, candidate_idx: usize, - backed_candidate: &BackedCandidate<::Hash>, + backed_candidate_receipt: &CommittedCandidateReceipt<::Hash>, ) -> Result, FailedToCreatePVD>, Error> { - let para_id = backed_candidate.descriptor().para_id; - let relay_parent = backed_candidate.descriptor().relay_parent; + let para_id = backed_candidate_receipt.descriptor().para_id; + let relay_parent = backed_candidate_receipt.descriptor().relay_parent; // Check that the relay-parent is one of the allowed relay-parents. let (relay_parent_storage_root, relay_parent_number) = { @@ -1223,13 +1232,13 @@ impl CandidateCheckContext { let expected = persisted_validation_data.hash(); ensure!( - expected == backed_candidate.descriptor().persisted_validation_data_hash, + expected == backed_candidate_receipt.descriptor().persisted_validation_data_hash, Error::::ValidationDataHashMismatch, ); } ensure!( - backed_candidate.descriptor().check_collator_signature().is_ok(), + backed_candidate_receipt.descriptor().check_collator_signature().is_ok(), Error::::NotCollatorSigned, ); @@ -1237,25 +1246,25 @@ impl CandidateCheckContext { // A candidate for a parachain without current validation code is not scheduled. .ok_or_else(|| Error::::UnscheduledCandidate)?; ensure!( - backed_candidate.descriptor().validation_code_hash == validation_code_hash, + backed_candidate_receipt.descriptor().validation_code_hash == validation_code_hash, Error::::InvalidValidationCodeHash, ); ensure!( - backed_candidate.descriptor().para_head == - backed_candidate.candidate.commitments.head_data.hash(), + backed_candidate_receipt.descriptor().para_head == + backed_candidate_receipt.commitments.head_data.hash(), Error::::ParaHeadMismatch, ); if let Err(err) = self.check_validation_outputs( para_id, relay_parent_number, - &backed_candidate.candidate.commitments.head_data, - &backed_candidate.candidate.commitments.new_validation_code, - backed_candidate.candidate.commitments.processed_downward_messages, - &backed_candidate.candidate.commitments.upward_messages, - BlockNumberFor::::from(backed_candidate.candidate.commitments.hrmp_watermark), - &backed_candidate.candidate.commitments.horizontal_messages, + &backed_candidate_receipt.commitments.head_data, + &backed_candidate_receipt.commitments.new_validation_code, + backed_candidate_receipt.commitments.processed_downward_messages, + &backed_candidate_receipt.commitments.upward_messages, + BlockNumberFor::::from(backed_candidate_receipt.commitments.hrmp_watermark), + &backed_candidate_receipt.commitments.horizontal_messages, ) { log::debug!( target: LOG_TARGET, diff --git a/polkadot/runtime/parachains/src/inclusion/tests.rs b/polkadot/runtime/parachains/src/inclusion/tests.rs index 557b7b71a9e..d2b5a67c3e4 100644 --- a/polkadot/runtime/parachains/src/inclusion/tests.rs +++ b/polkadot/runtime/parachains/src/inclusion/tests.rs @@ -120,6 +120,7 @@ pub(crate) fn back_candidate( keystore: &KeystorePtr, signing_context: &SigningContext, kind: BackingKind, + core_index: Option, ) -> BackedCandidate { let mut validator_indices = bitvec::bitvec![u8, BitOrderLsb0; 0; group.len()]; let threshold = effective_minimum_backing_votes( @@ -155,15 +156,20 @@ pub(crate) fn back_candidate( validity_votes.push(ValidityAttestation::Explicit(signature).into()); } - let backed = BackedCandidate { candidate, validity_votes, validator_indices }; + let backed = + BackedCandidate::new(candidate, validity_votes, validator_indices.clone(), core_index); - let successfully_backed = - primitives::check_candidate_backing(&backed, signing_context, group.len(), |i| { - Some(validators[group[i].0 as usize].public().into()) - }) - .ok() - .unwrap_or(0) >= - threshold; + let successfully_backed = primitives::check_candidate_backing( + backed.candidate().hash(), + backed.validity_votes(), + validator_indices.as_bitslice(), + signing_context, + group.len(), + |i| Some(validators[group[i].0 as usize].public().into()), + ) + .ok() + .unwrap_or(0) >= + threshold; match kind { BackingKind::Unanimous | BackingKind::Threshold => assert!(successfully_backed), @@ -919,38 +925,16 @@ fn candidate_checks() { let thread_a_assignment = (thread_a, CoreIndex::from(2)); let allowed_relay_parents = default_allowed_relay_parent_tracker(); - // unscheduled candidate. - { - let mut candidate = TestCandidateBuilder { - para_id: chain_a, - relay_parent: System::parent_hash(), - pov_hash: Hash::repeat_byte(1), - persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(), - hrmp_watermark: RELAY_PARENT_NUM, - ..Default::default() - } - .build(); - collator_sign_candidate(Sr25519Keyring::One, &mut candidate); - - let backed = back_candidate( - candidate, - &validators, - group_validators(GroupIndex::from(0)).unwrap().as_ref(), - &keystore, - &signing_context, - BackingKind::Threshold, - ); - - assert_noop!( - ParaInclusion::process_candidates( - &allowed_relay_parents, - vec![backed], - &[chain_b_assignment].into_iter().collect(), - &group_validators, - ), - Error::::UnscheduledCandidate - ); - } + // no candidates. + assert_eq!( + ParaInclusion::process_candidates( + &allowed_relay_parents, + vec![], + &group_validators, + false + ), + Ok(ProcessedCandidates::default()) + ); // candidates out of order. { @@ -984,6 +968,7 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); let backed_b = back_candidate( @@ -993,15 +978,16 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); // out-of-order manifests as unscheduled. assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed_b, backed_a], - &[chain_a_assignment, chain_b_assignment].into_iter().collect(), + vec![(backed_b, chain_b_assignment.1), (backed_a, chain_a_assignment.1)], &group_validators, + false ), Error::::ScheduledOutOfOrder ); @@ -1027,14 +1013,15 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Lacking, + None, ); assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed], - &[chain_a_assignment].into_iter().collect(), + vec![(backed, chain_a_assignment.1)], &group_validators, + false ), Error::::InsufficientBacking ); @@ -1075,6 +1062,7 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); let backed_b = back_candidate( @@ -1084,14 +1072,15 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed_b, backed_a], - &[chain_a_assignment, chain_b_assignment].into_iter().collect(), + vec![(backed_b, chain_b_assignment.1), (backed_a, chain_a_assignment.1)], &group_validators, + false ), Error::::DisallowedRelayParent ); @@ -1122,14 +1111,15 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed], - &[thread_a_assignment].into_iter().collect(), + vec![(backed, thread_a_assignment.1)], &group_validators, + false ), Error::::NotCollatorSigned ); @@ -1156,6 +1146,7 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); let candidate = TestCandidateBuilder::default().build(); @@ -1177,9 +1168,9 @@ fn candidate_checks() { assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed], - &[chain_a_assignment].into_iter().collect(), + vec![(backed, chain_a_assignment.1)], &group_validators, + false ), Error::::CandidateScheduledBeforeParaFree ); @@ -1212,14 +1203,15 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed], - &[chain_a_assignment].into_iter().collect(), + vec![(backed, chain_a_assignment.1)], &group_validators, + false ), Error::::CandidateScheduledBeforeParaFree ); @@ -1249,6 +1241,7 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); { @@ -1267,9 +1260,9 @@ fn candidate_checks() { assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed], - &[chain_a_assignment].into_iter().collect(), + vec![(backed, chain_a_assignment.1)], &group_validators, + false ), Error::::PrematureCodeUpgrade ); @@ -1296,14 +1289,15 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); assert_eq!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed], - &[chain_a_assignment].into_iter().collect(), + vec![(backed, chain_a_assignment.1)], &group_validators, + false ), Err(Error::::ValidationDataHashMismatch.into()), ); @@ -1331,14 +1325,15 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed], - &[chain_a_assignment].into_iter().collect(), + vec![(backed, chain_a_assignment.1)], &group_validators, + false ), Error::::InvalidValidationCodeHash ); @@ -1366,14 +1361,15 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed], - &[chain_a_assignment].into_iter().collect(), + vec![(backed, chain_a_assignment.1)], &group_validators, + false ), Error::::ParaHeadMismatch ); @@ -1486,6 +1482,7 @@ fn backing_works() { &keystore, &signing_context, BackingKind::Threshold, + None, ); let backed_b = back_candidate( @@ -1495,6 +1492,7 @@ fn backing_works() { &keystore, &signing_context, BackingKind::Threshold, + None, ); let backed_c = back_candidate( @@ -1504,15 +1502,20 @@ fn backing_works() { &keystore, &signing_context, BackingKind::Threshold, + None, ); - let backed_candidates = vec![backed_a.clone(), backed_b.clone(), backed_c]; + let backed_candidates = vec![ + (backed_a.clone(), chain_a_assignment.1), + (backed_b.clone(), chain_b_assignment.1), + (backed_c, thread_a_assignment.1), + ]; let get_backing_group_idx = { // the order defines the group implicitly for this test case let backed_candidates_with_groups = backed_candidates .iter() .enumerate() - .map(|(idx, backed_candidate)| (backed_candidate.hash(), GroupIndex(idx as _))) + .map(|(idx, (backed_candidate, _))| (backed_candidate.hash(), GroupIndex(idx as _))) .collect::>(); move |candidate_hash_x: CandidateHash| -> Option { @@ -1532,10 +1535,8 @@ fn backing_works() { } = ParaInclusion::process_candidates( &allowed_relay_parents, backed_candidates.clone(), - &[chain_a_assignment, chain_b_assignment, thread_a_assignment] - .into_iter() - .collect(), &group_validators, + false, ) .expect("candidates scheduled, in order, and backed"); @@ -1554,22 +1555,22 @@ fn backing_works() { CandidateHash, (CandidateReceipt, Vec<(ValidatorIndex, ValidityAttestation)>), >::new(); - backed_candidates.into_iter().for_each(|backed_candidate| { + backed_candidates.into_iter().for_each(|(backed_candidate, _)| { let candidate_receipt_with_backers = intermediate .entry(backed_candidate.hash()) .or_insert_with(|| (backed_candidate.receipt(), Vec::new())); - - assert_eq!( - backed_candidate.validity_votes.len(), - backed_candidate.validator_indices.count_ones() - ); + let (validator_indices, None) = + backed_candidate.validator_indices_and_core_index(false) + else { + panic!("Expected no injected core index") + }; + assert_eq!(backed_candidate.validity_votes().len(), validator_indices.count_ones()); candidate_receipt_with_backers.1.extend( - backed_candidate - .validator_indices + validator_indices .iter() .enumerate() .filter(|(_, signed)| **signed) - .zip(backed_candidate.validity_votes.iter().cloned()) + .zip(backed_candidate.validity_votes().iter().cloned()) .filter_map(|((validator_index_within_group, _), attestation)| { let grp_idx = get_backing_group_idx(backed_candidate.hash()).unwrap(); group_validators(grp_idx).map(|validator_indices| { @@ -1666,6 +1667,257 @@ fn backing_works() { }); } +#[test] +fn backing_works_with_elastic_scaling_mvp() { + let chain_a = ParaId::from(1_u32); + let chain_b = ParaId::from(2_u32); + let thread_a = ParaId::from(3_u32); + + // The block number of the relay-parent for testing. + const RELAY_PARENT_NUM: BlockNumber = 4; + + let paras = vec![ + (chain_a, ParaKind::Parachain), + (chain_b, ParaKind::Parachain), + (thread_a, ParaKind::Parathread), + ]; + let validators = vec![ + Sr25519Keyring::Alice, + Sr25519Keyring::Bob, + Sr25519Keyring::Charlie, + Sr25519Keyring::Dave, + Sr25519Keyring::Ferdie, + ]; + let keystore: KeystorePtr = Arc::new(LocalKeystore::in_memory()); + for validator in validators.iter() { + Keystore::sr25519_generate_new( + &*keystore, + PARACHAIN_KEY_TYPE_ID, + Some(&validator.to_seed()), + ) + .unwrap(); + } + let validator_public = validator_pubkeys(&validators); + + new_test_ext(genesis_config(paras)).execute_with(|| { + shared::Pallet::::set_active_validators_ascending(validator_public.clone()); + shared::Pallet::::set_session_index(5); + + run_to_block(5, |_| None); + + let signing_context = + SigningContext { parent_hash: System::parent_hash(), session_index: 5 }; + + let group_validators = |group_index: GroupIndex| { + match group_index { + group_index if group_index == GroupIndex::from(0) => Some(vec![0, 1]), + group_index if group_index == GroupIndex::from(1) => Some(vec![2, 3]), + group_index if group_index == GroupIndex::from(2) => Some(vec![4]), + _ => panic!("Group index out of bounds for 2 parachains and 1 parathread core"), + } + .map(|vs| vs.into_iter().map(ValidatorIndex).collect::>()) + }; + + // When processing candidates, we compute the group index from scheduler. + let validator_groups = vec![ + vec![ValidatorIndex(0), ValidatorIndex(1)], + vec![ValidatorIndex(2), ValidatorIndex(3)], + vec![ValidatorIndex(4)], + ]; + Scheduler::set_validator_groups(validator_groups); + + let allowed_relay_parents = default_allowed_relay_parent_tracker(); + + let mut candidate_a = TestCandidateBuilder { + para_id: chain_a, + relay_parent: System::parent_hash(), + pov_hash: Hash::repeat_byte(1), + persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + collator_sign_candidate(Sr25519Keyring::One, &mut candidate_a); + + let mut candidate_b_1 = TestCandidateBuilder { + para_id: chain_b, + relay_parent: System::parent_hash(), + pov_hash: Hash::repeat_byte(2), + persisted_validation_data_hash: make_vdata_hash(chain_b).unwrap(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + collator_sign_candidate(Sr25519Keyring::One, &mut candidate_b_1); + + let mut candidate_b_2 = TestCandidateBuilder { + para_id: chain_b, + relay_parent: System::parent_hash(), + pov_hash: Hash::repeat_byte(3), + persisted_validation_data_hash: make_vdata_hash(chain_b).unwrap(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + collator_sign_candidate(Sr25519Keyring::One, &mut candidate_b_2); + + let backed_a = back_candidate( + candidate_a.clone(), + &validators, + group_validators(GroupIndex::from(0)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + None, + ); + + let backed_b_1 = back_candidate( + candidate_b_1.clone(), + &validators, + group_validators(GroupIndex::from(1)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + Some(CoreIndex(1)), + ); + + let backed_b_2 = back_candidate( + candidate_b_2.clone(), + &validators, + group_validators(GroupIndex::from(2)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + Some(CoreIndex(2)), + ); + + let backed_candidates = vec![ + (backed_a.clone(), CoreIndex(0)), + (backed_b_1.clone(), CoreIndex(1)), + (backed_b_2.clone(), CoreIndex(2)), + ]; + let get_backing_group_idx = { + // the order defines the group implicitly for this test case + let backed_candidates_with_groups = backed_candidates + .iter() + .enumerate() + .map(|(idx, (backed_candidate, _))| (backed_candidate.hash(), GroupIndex(idx as _))) + .collect::>(); + + move |candidate_hash_x: CandidateHash| -> Option { + backed_candidates_with_groups.iter().find_map(|(candidate_hash, grp)| { + if *candidate_hash == candidate_hash_x { + Some(*grp) + } else { + None + } + }) + } + }; + + let ProcessedCandidates { + core_indices: occupied_cores, + candidate_receipt_with_backing_validator_indices, + } = ParaInclusion::process_candidates( + &allowed_relay_parents, + backed_candidates.clone(), + &group_validators, + true, + ) + .expect("candidates scheduled, in order, and backed"); + + // Both b candidates will be backed. However, only one will be recorded on-chain and proceed + // with being made available. + assert_eq!( + occupied_cores, + vec![ + (CoreIndex::from(0), chain_a), + (CoreIndex::from(1), chain_b), + (CoreIndex::from(2), chain_b), + ] + ); + + // Transform the votes into the setup we expect + let mut expected = std::collections::HashMap::< + CandidateHash, + (CandidateReceipt, Vec<(ValidatorIndex, ValidityAttestation)>), + >::new(); + backed_candidates.into_iter().for_each(|(backed_candidate, _)| { + let candidate_receipt_with_backers = expected + .entry(backed_candidate.hash()) + .or_insert_with(|| (backed_candidate.receipt(), Vec::new())); + let (validator_indices, _maybe_core_index) = + backed_candidate.validator_indices_and_core_index(true); + assert_eq!(backed_candidate.validity_votes().len(), validator_indices.count_ones()); + candidate_receipt_with_backers.1.extend( + validator_indices + .iter() + .enumerate() + .filter(|(_, signed)| **signed) + .zip(backed_candidate.validity_votes().iter().cloned()) + .filter_map(|((validator_index_within_group, _), attestation)| { + let grp_idx = get_backing_group_idx(backed_candidate.hash()).unwrap(); + group_validators(grp_idx).map(|validator_indices| { + (validator_indices[validator_index_within_group], attestation) + }) + }), + ); + }); + + assert_eq!( + expected, + candidate_receipt_with_backing_validator_indices + .into_iter() + .map(|c| (c.0.hash(), c)) + .collect() + ); + + let backers = { + let num_backers = effective_minimum_backing_votes( + group_validators(GroupIndex(0)).unwrap().len(), + configuration::Pallet::::config().minimum_backing_votes, + ); + backing_bitfield(&(0..num_backers).collect::>()) + }; + assert_eq!( + >::get(&chain_a), + Some(CandidatePendingAvailability { + core: CoreIndex::from(0), + hash: candidate_a.hash(), + descriptor: candidate_a.descriptor, + availability_votes: default_availability_votes(), + relay_parent_number: System::block_number() - 1, + backed_in_number: System::block_number(), + backers, + backing_group: GroupIndex::from(0), + }) + ); + assert_eq!( + >::get(&chain_a), + Some(candidate_a.commitments), + ); + + // Only one candidate for b will be recorded on chain. + assert_eq!( + >::get(&chain_b), + Some(CandidatePendingAvailability { + core: CoreIndex::from(2), + hash: candidate_b_2.hash(), + descriptor: candidate_b_2.descriptor, + availability_votes: default_availability_votes(), + relay_parent_number: System::block_number() - 1, + backed_in_number: System::block_number(), + backers: backing_bitfield(&[4]), + backing_group: GroupIndex::from(2), + }) + ); + assert_eq!( + >::get(&chain_b), + Some(candidate_b_2.commitments), + ); + }); +} + #[test] fn can_include_candidate_with_ok_code_upgrade() { let chain_a = ParaId::from(1_u32); @@ -1740,14 +1992,15 @@ fn can_include_candidate_with_ok_code_upgrade() { &keystore, &signing_context, BackingKind::Threshold, + None, ); let ProcessedCandidates { core_indices: occupied_cores, .. } = ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed_a], - &[chain_a_assignment].into_iter().collect(), + vec![(backed_a, chain_a_assignment.1)], &group_validators, + false, ) .expect("candidates scheduled, in order, and backed"); @@ -1932,6 +2185,7 @@ fn check_allowed_relay_parents() { &keystore, &signing_context_a, BackingKind::Threshold, + None, ); let backed_b = back_candidate( @@ -1941,6 +2195,7 @@ fn check_allowed_relay_parents() { &keystore, &signing_context_b, BackingKind::Threshold, + None, ); let backed_c = back_candidate( @@ -1950,17 +2205,20 @@ fn check_allowed_relay_parents() { &keystore, &signing_context_c, BackingKind::Threshold, + None, ); - let backed_candidates = vec![backed_a, backed_b, backed_c]; + let backed_candidates = vec![ + (backed_a, chain_a_assignment.1), + (backed_b, chain_b_assignment.1), + (backed_c, thread_a_assignment.1), + ]; ParaInclusion::process_candidates( &allowed_relay_parents, backed_candidates.clone(), - &[chain_a_assignment, chain_b_assignment, thread_a_assignment] - .into_iter() - .collect(), &group_validators, + false, ) .expect("candidates scheduled, in order, and backed"); }); @@ -2189,14 +2447,15 @@ fn para_upgrade_delay_scheduled_from_inclusion() { &keystore, &signing_context, BackingKind::Threshold, + None, ); let ProcessedCandidates { core_indices: occupied_cores, .. } = ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed_a], - &[chain_a_assignment].into_iter().collect(), + vec![(backed_a, chain_a_assignment.1)], &group_validators, + false, ) .expect("candidates scheduled, in order, and backed"); diff --git a/polkadot/runtime/parachains/src/paras_inherent/benchmarking.rs b/polkadot/runtime/parachains/src/paras_inherent/benchmarking.rs index 0f6b23ae1b3..ad3fa8e0dc7 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/benchmarking.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/benchmarking.rs @@ -120,7 +120,7 @@ benchmarks! { // with `v` validity votes. // let votes = v as usize; let votes = min(scheduler::Pallet::::group_validators(GroupIndex::from(0)).unwrap().len(), v as usize); - assert_eq!(benchmark.backed_candidates.get(0).unwrap().validity_votes.len(), votes); + assert_eq!(benchmark.backed_candidates.get(0).unwrap().validity_votes().len(), votes); benchmark.bitfields.clear(); benchmark.disputes.clear(); @@ -177,7 +177,7 @@ benchmarks! { // There is 1 backed assert_eq!(benchmark.backed_candidates.len(), 1); assert_eq!( - benchmark.backed_candidates.get(0).unwrap().validity_votes.len(), + benchmark.backed_candidates.get(0).unwrap().validity_votes().len(), votes, ); diff --git a/polkadot/runtime/parachains/src/paras_inherent/mod.rs b/polkadot/runtime/parachains/src/paras_inherent/mod.rs index 81e092f0a99..cebf858c24a 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/mod.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/mod.rs @@ -43,15 +43,14 @@ use frame_support::{ use frame_system::pallet_prelude::*; use pallet_babe::{self, ParentBlockRandomness}; use primitives::{ - effective_minimum_backing_votes, BackedCandidate, CandidateHash, CandidateReceipt, - CheckedDisputeStatementSet, CheckedMultiDisputeStatementSet, CoreIndex, DisputeStatementSet, - InherentData as ParachainsInherentData, MultiDisputeStatementSet, ScrapedOnChainVotes, - SessionIndex, SignedAvailabilityBitfields, SigningContext, UncheckedSignedAvailabilityBitfield, - UncheckedSignedAvailabilityBitfields, ValidatorId, ValidatorIndex, ValidityAttestation, - PARACHAINS_INHERENT_IDENTIFIER, + effective_minimum_backing_votes, vstaging::node_features::FeatureIndex, BackedCandidate, + CandidateHash, CandidateReceipt, CheckedDisputeStatementSet, CheckedMultiDisputeStatementSet, + CoreIndex, DisputeStatementSet, InherentData as ParachainsInherentData, + MultiDisputeStatementSet, ScrapedOnChainVotes, SessionIndex, SignedAvailabilityBitfields, + SigningContext, UncheckedSignedAvailabilityBitfield, UncheckedSignedAvailabilityBitfields, + ValidatorId, ValidatorIndex, ValidityAttestation, PARACHAINS_INHERENT_IDENTIFIER, }; use rand::{seq::SliceRandom, SeedableRng}; - use scale_info::TypeInfo; use sp_runtime::traits::{Header as HeaderT, One}; use sp_std::{ @@ -145,6 +144,10 @@ pub mod pallet { DisputeInvalid, /// A candidate was backed by a disabled validator BackedByDisabled, + /// A candidate was backed even though the paraid was not scheduled. + BackedOnUnscheduledCore, + /// Too many candidates supplied. + UnscheduledCandidate, } /// Whether the paras inherent was included within this block. @@ -585,25 +588,39 @@ impl Pallet { let freed = collect_all_freed_cores::(freed_concluded.iter().cloned()); >::free_cores_and_fill_claimqueue(freed, now); - let scheduled = >::scheduled_paras() - .map(|(core_idx, para_id)| (para_id, core_idx)) - .collect(); METRICS.on_candidates_processed_total(backed_candidates.len() as u64); - let SanitizedBackedCandidates { backed_candidates, votes_from_disabled_were_dropped } = - sanitize_backed_candidates::( - backed_candidates, - &allowed_relay_parents, - |candidate_idx: usize, - backed_candidate: &BackedCandidate<::Hash>| - -> bool { - let para_id = backed_candidate.descriptor().para_id; - let prev_context = >::para_most_recent_context(para_id); - let check_ctx = CandidateCheckContext::::new(prev_context); - - // never include a concluded-invalid candidate - current_concluded_invalid_disputes.contains(&backed_candidate.hash()) || + let core_index_enabled = configuration::Pallet::::config() + .node_features + .get(FeatureIndex::ElasticScalingMVP as usize) + .map(|b| *b) + .unwrap_or(false); + + let mut scheduled: BTreeMap> = BTreeMap::new(); + let mut total_scheduled_cores = 0; + + for (core_idx, para_id) in >::scheduled_paras() { + total_scheduled_cores += 1; + scheduled.entry(para_id).or_default().insert(core_idx); + } + + let SanitizedBackedCandidates { + backed_candidates_with_core, + votes_from_disabled_were_dropped, + dropped_unscheduled_candidates, + } = sanitize_backed_candidates::( + backed_candidates, + &allowed_relay_parents, + |candidate_idx: usize, + backed_candidate: &BackedCandidate<::Hash>| + -> bool { + let para_id = backed_candidate.descriptor().para_id; + let prev_context = >::para_most_recent_context(para_id); + let check_ctx = CandidateCheckContext::::new(prev_context); + + // never include a concluded-invalid candidate + current_concluded_invalid_disputes.contains(&backed_candidate.hash()) || // Instead of checking the candidates with code upgrades twice // move the checking up here and skip it in the training wheels fallback. // That way we avoid possible duplicate checks while assuring all @@ -611,13 +628,19 @@ impl Pallet { // // NOTE: this is the only place where we check the relay-parent. check_ctx - .verify_backed_candidate(&allowed_relay_parents, candidate_idx, backed_candidate) + .verify_backed_candidate(&allowed_relay_parents, candidate_idx, backed_candidate.candidate()) .is_err() - }, - &scheduled, - ); + }, + scheduled, + core_index_enabled, + ); + + ensure!( + backed_candidates_with_core.len() <= total_scheduled_cores, + Error::::UnscheduledCandidate + ); - METRICS.on_candidates_sanitized(backed_candidates.len() as u64); + METRICS.on_candidates_sanitized(backed_candidates_with_core.len() as u64); // In `Enter` context (invoked during execution) there should be no backing votes from // disabled validators because they should have been filtered out during inherent data @@ -626,15 +649,22 @@ impl Pallet { ensure!(!votes_from_disabled_were_dropped, Error::::BackedByDisabled); } + // In `Enter` context (invoked during execution) we shouldn't have filtered any candidates + // due to a para not being scheduled. They have been filtered during inherent data + // preparation (`ProvideInherent` context). Abort in such cases. + if context == ProcessInherentDataContext::Enter { + ensure!(!dropped_unscheduled_candidates, Error::::BackedOnUnscheduledCore); + } + // Process backed candidates according to scheduled cores. let inclusion::ProcessedCandidates::< as HeaderT>::Hash> { core_indices: occupied, candidate_receipt_with_backing_validator_indices, } = >::process_candidates( &allowed_relay_parents, - backed_candidates.clone(), - &scheduled, + backed_candidates_with_core.clone(), >::group_validators, + core_index_enabled, )?; // Note which of the scheduled cores were actually occupied by a backed candidate. >::occupied(occupied.into_iter().map(|e| (e.0, e.1)).collect()); @@ -651,8 +681,15 @@ impl Pallet { let bitfields = bitfields.into_iter().map(|v| v.into_unchecked()).collect(); - let processed = - ParachainsInherentData { bitfields, backed_candidates, disputes, parent_header }; + let processed = ParachainsInherentData { + bitfields, + backed_candidates: backed_candidates_with_core + .into_iter() + .map(|(candidate, _)| candidate) + .collect(), + disputes, + parent_header, + }; Ok((processed, Some(all_weight_after).into())) } } @@ -774,7 +811,7 @@ fn apply_weight_limit( .iter() .enumerate() .filter_map(|(idx, candidate)| { - candidate.candidate.commitments.new_validation_code.as_ref().map(|_code| idx) + candidate.candidate().commitments.new_validation_code.as_ref().map(|_code| idx) }) .collect::>(); @@ -916,16 +953,22 @@ pub(crate) fn sanitize_bitfields( // Result from `sanitize_backed_candidates` #[derive(Debug, PartialEq)] struct SanitizedBackedCandidates { - // Sanitized backed candidates. The `Vec` is sorted according to the occupied core index. - backed_candidates: Vec>, + // Sanitized backed candidates along with the assigned core. The `Vec` is sorted according to + // the occupied core index. + backed_candidates_with_core: Vec<(BackedCandidate, CoreIndex)>, // Set to true if any votes from disabled validators were dropped from the input. votes_from_disabled_were_dropped: bool, + // Set to true if any candidates were dropped due to filtering done in + // `map_candidates_to_cores` + dropped_unscheduled_candidates: bool, } /// Filter out: /// 1. any candidates that have a concluded invalid dispute -/// 2. all backing votes from disabled validators -/// 3. any candidates that end up with less than `effective_minimum_backing_votes` backing votes +/// 2. any unscheduled candidates, as well as candidates whose paraid has multiple cores assigned +/// but have no injected core index. +/// 3. all backing votes from disabled validators +/// 4. any candidates that end up with less than `effective_minimum_backing_votes` backing votes /// /// `scheduled` follows the same naming scheme as provided in the /// guide: Currently `free` but might become `occupied`. @@ -944,7 +987,8 @@ fn sanitize_backed_candidates< mut backed_candidates: Vec>, allowed_relay_parents: &AllowedRelayParentsTracker>, mut candidate_has_concluded_invalid_dispute_or_is_invalid: F, - scheduled: &BTreeMap, + scheduled: BTreeMap>, + core_index_enabled: bool, ) -> SanitizedBackedCandidates { // Remove any candidates that were concluded invalid. // This does not assume sorting. @@ -952,22 +996,23 @@ fn sanitize_backed_candidates< !candidate_has_concluded_invalid_dispute_or_is_invalid(candidate_idx, backed_candidate) }); - // Assure the backed candidate's `ParaId`'s core is free. - // This holds under the assumption that `Scheduler::schedule` is called _before_. - // We don't check the relay-parent because this is done in the closure when - // constructing the inherent and during actual processing otherwise. - - backed_candidates.retain(|backed_candidate| { - let desc = backed_candidate.descriptor(); + let initial_candidate_count = backed_candidates.len(); + // Map candidates to scheduled cores. Filter out any unscheduled candidates. + let mut backed_candidates_with_core = map_candidates_to_cores::( + &allowed_relay_parents, + scheduled, + core_index_enabled, + backed_candidates, + ); - scheduled.get(&desc.para_id).is_some() - }); + let dropped_unscheduled_candidates = + initial_candidate_count != backed_candidates_with_core.len(); // Filter out backing statements from disabled validators - let dropped_disabled = filter_backed_statements_from_disabled_validators::( - &mut backed_candidates, + let votes_from_disabled_were_dropped = filter_backed_statements_from_disabled_validators::( + &mut backed_candidates_with_core, &allowed_relay_parents, - scheduled, + core_index_enabled, ); // Sort the `Vec` last, once there is a guarantee that these @@ -975,14 +1020,12 @@ fn sanitize_backed_candidates< // but more importantly are scheduled for a free core. // This both avoids extra work for obviously invalid candidates, // but also allows this to be done in place. - backed_candidates.sort_by(|x, y| { - // Never panics, since we filtered all panic arguments out in the previous `fn retain`. - scheduled[&x.descriptor().para_id].cmp(&scheduled[&y.descriptor().para_id]) - }); + backed_candidates_with_core.sort_by(|(_x, core_x), (_y, core_y)| core_x.cmp(&core_y)); SanitizedBackedCandidates { - backed_candidates, - votes_from_disabled_were_dropped: dropped_disabled, + dropped_unscheduled_candidates, + votes_from_disabled_were_dropped, + backed_candidates_with_core, } } @@ -1071,9 +1114,12 @@ fn limit_and_sanitize_disputes< // few more sanity checks. Returns `true` if at least one statement is removed and `false` // otherwise. fn filter_backed_statements_from_disabled_validators( - backed_candidates: &mut Vec::Hash>>, + backed_candidates_with_core: &mut Vec<( + BackedCandidate<::Hash>, + CoreIndex, + )>, allowed_relay_parents: &AllowedRelayParentsTracker>, - scheduled: &BTreeMap, + core_index_enabled: bool, ) -> bool { let disabled_validators = BTreeSet::<_>::from_iter(shared::Pallet::::disabled_validators().into_iter()); @@ -1083,7 +1129,7 @@ fn filter_backed_statements_from_disabled_validators *core_idx, - None => { - log::debug!(target: LOG_TARGET, "Can't get core idx of a backed candidate for para id {:?}. Dropping the candidate.", bc.descriptor().para_id); - return false - } - }; + backed_candidates_with_core.retain_mut(|(bc, core_idx)| { + let (validator_indices, maybe_core_index) = bc.validator_indices_and_core_index(core_index_enabled); + let mut validator_indices = BitVec::<_>::from(validator_indices); // Get relay parent block number of the candidate. We need this to get the group index assigned to this core at this block number let relay_parent_block_number = match allowed_relay_parents @@ -1116,7 +1156,7 @@ fn filter_backed_statements_from_disabled_validators>::group_assigned_to_core( - core_idx, + *core_idx, relay_parent_block_number + One::one(), ) { Some(group_idx) => group_idx, @@ -1138,12 +1178,15 @@ fn filter_backed_statements_from_disabled_validators::from_iter(validator_group.iter().map(|idx| disabled_validators.contains(idx))); // The indices of statements from disabled validators in `BackedCandidate`. We have to drop these. - let indices_to_drop = disabled_indices.clone() & &bc.validator_indices; + let indices_to_drop = disabled_indices.clone() & &validator_indices; // Apply the bitmask to drop the disabled validator from `validator_indices` - bc.validator_indices &= !disabled_indices; + validator_indices &= !disabled_indices; + // Update the backed candidate + bc.set_validator_indices_and_core_index(validator_indices, maybe_core_index); + // Remove the corresponding votes from `validity_votes` for idx in indices_to_drop.iter_ones().rev() { - bc.validity_votes.remove(idx); + bc.validity_votes_mut().remove(idx); } // If at least one statement was dropped we need to return `true` @@ -1154,10 +1197,9 @@ fn filter_backed_statements_from_disabled_validators( + allowed_relay_parents: &AllowedRelayParentsTracker>, + mut scheduled: BTreeMap>, + core_index_enabled: bool, + candidates: Vec>, +) -> Vec<(BackedCandidate, CoreIndex)> { + let mut backed_candidates_with_core = Vec::with_capacity(candidates.len()); + + // We keep a candidate if the parachain has only one core assigned or if + // a core index is provided by block author and it's indeed scheduled. + for backed_candidate in candidates { + let maybe_injected_core_index = get_injected_core_index::( + allowed_relay_parents, + &backed_candidate, + core_index_enabled, + ); + + let scheduled_cores = scheduled.get_mut(&backed_candidate.descriptor().para_id); + // Candidates without scheduled cores are silently filtered out. + if let Some(scheduled_cores) = scheduled_cores { + if let Some(core_idx) = maybe_injected_core_index { + if scheduled_cores.contains(&core_idx) { + scheduled_cores.remove(&core_idx); + backed_candidates_with_core.push((backed_candidate, core_idx)); + } + } else if scheduled_cores.len() == 1 { + backed_candidates_with_core + .push((backed_candidate, scheduled_cores.pop_first().expect("Length is 1"))); + } + } + } + + backed_candidates_with_core +} + +fn get_injected_core_index( + allowed_relay_parents: &AllowedRelayParentsTracker>, + candidate: &BackedCandidate, + core_index_enabled: bool, +) -> Option { + // After stripping the 8 bit extensions, the `validator_indices` field length is expected + // to be equal to backing group size. If these don't match, the `CoreIndex` is badly encoded, + // or not supported. + let (validator_indices, maybe_core_idx) = + candidate.validator_indices_and_core_index(core_index_enabled); + + let Some(core_idx) = maybe_core_idx else { return None }; + + let relay_parent_block_number = + match allowed_relay_parents.acquire_info(candidate.descriptor().relay_parent, None) { + Some((_, block_num)) => block_num, + None => { + log::debug!( + target: LOG_TARGET, + "Relay parent {:?} for candidate {:?} is not in the allowed relay parents. Dropping the candidate.", + candidate.descriptor().relay_parent, + candidate.candidate().hash(), + ); + return None + }, + }; + + // Get the backing group of the candidate backed at `core_idx`. + let group_idx = match >::group_assigned_to_core( + core_idx, + relay_parent_block_number + One::one(), + ) { + Some(group_idx) => group_idx, + None => { + log::debug!( + target: LOG_TARGET, + "Can't get the group index for core idx {:?}. Dropping the candidate {:?}.", + core_idx, + candidate.candidate().hash(), + ); + return None + }, + }; + + let group_validators = match >::group_validators(group_idx) { + Some(validators) => validators, + None => return None, + }; + + if group_validators.len() == validator_indices.len() { + Some(core_idx) + } else { + None + } } diff --git a/polkadot/runtime/parachains/src/paras_inherent/tests.rs b/polkadot/runtime/parachains/src/paras_inherent/tests.rs index 6f3eac35685..defb2f4404f 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/tests.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/tests.rs @@ -26,7 +26,10 @@ mod enter { use crate::{ builder::{Bench, BenchBuilder}, mock::{mock_assigner, new_test_ext, BlockLength, BlockWeights, MockGenesisConfig, Test}, - scheduler::common::Assignment, + scheduler::{ + common::{Assignment, AssignmentProvider, AssignmentProviderConfig}, + ParasEntry, + }, }; use assert_matches::assert_matches; use frame_support::assert_ok; @@ -697,6 +700,25 @@ mod enter { 2 ); + // One core was scheduled. We should put the assignment back, before calling enter(). + let now = >::block_number() + 1; + let used_cores = 5; + let cores = (0..used_cores) + .into_iter() + .map(|i| { + let AssignmentProviderConfig { ttl, .. } = + scheduler::Pallet::::assignment_provider_config(CoreIndex(i)); + // Load an assignment into provider so that one is present to pop + let assignment = + ::AssignmentProvider::get_mock_assignment( + CoreIndex(i), + ParaId::from(i), + ); + (CoreIndex(i), [ParasEntry::new(assignment, now + ttl)].into()) + }) + .collect(); + scheduler::ClaimQueue::::set(cores); + assert_ok!(Pallet::::enter( frame_system::RawOrigin::None.into(), limit_inherent_data, @@ -980,6 +1002,7 @@ mod sanitizers { AvailabilityBitfield, GroupIndex, Hash, Id as ParaId, SignedAvailabilityBitfield, ValidatorIndex, }; + use rstest::rstest; use sp_core::crypto::UncheckedFrom; use crate::mock::Test; @@ -1238,12 +1261,13 @@ mod sanitizers { // Backed candidates and scheduled parachains used for `sanitize_backed_candidates` testing struct TestData { backed_candidates: Vec, - scheduled_paras: BTreeMap, + all_backed_candidates_with_core: Vec<(BackedCandidate, CoreIndex)>, + scheduled_paras: BTreeMap>, } // Generate test data for the candidates and assert that the evnironment is set as expected // (check the comments for details) - fn get_test_data() -> TestData { + fn get_test_data(core_index_enabled: bool) -> TestData { const RELAY_PARENT_NUM: u32 = 3; // Add the relay parent to `shared` pallet. Otherwise some code (e.g. filtering backing @@ -1285,9 +1309,14 @@ mod sanitizers { shared::Pallet::::set_active_validators_ascending(validator_ids); // Two scheduled parachains - ParaId(1) on CoreIndex(0) and ParaId(2) on CoreIndex(1) - let scheduled = (0_usize..2) + let scheduled: BTreeMap> = (0_usize..2) .into_iter() - .map(|idx| (ParaId::from(1_u32 + idx as u32), CoreIndex::from(idx as u32))) + .map(|idx| { + ( + ParaId::from(1_u32 + idx as u32), + [CoreIndex::from(idx as u32)].into_iter().collect(), + ) + }) .collect::>(); // Set the validator groups in `scheduler` @@ -1301,7 +1330,7 @@ mod sanitizers { ( CoreIndex::from(0), VecDeque::from([ParasEntry::new( - Assignment::Pool { para_id: 1.into(), core_index: CoreIndex(1) }, + Assignment::Pool { para_id: 1.into(), core_index: CoreIndex(0) }, RELAY_PARENT_NUM, )]), ), @@ -1319,12 +1348,12 @@ mod sanitizers { match group_index { group_index if group_index == GroupIndex::from(0) => Some(vec![0, 1]), group_index if group_index == GroupIndex::from(1) => Some(vec![2, 3]), - _ => panic!("Group index out of bounds for 2 parachains and 1 parathread core"), + _ => panic!("Group index out of bounds"), } .map(|m| m.into_iter().map(ValidatorIndex).collect::>()) }; - // Two backed candidates from each parachain + // One backed candidate from each parachain let backed_candidates = (0_usize..2) .into_iter() .map(|idx0| { @@ -1348,6 +1377,7 @@ mod sanitizers { &keystore, &signing_context, BackingKind::Threshold, + core_index_enabled.then_some(CoreIndex(idx0 as u32)), ); backed }) @@ -1369,13 +1399,373 @@ mod sanitizers { ] ); - TestData { backed_candidates, scheduled_paras: scheduled } + let all_backed_candidates_with_core = backed_candidates + .iter() + .map(|candidate| { + // Only one entry for this test data. + ( + candidate.clone(), + scheduled + .get(&candidate.descriptor().para_id) + .unwrap() + .first() + .copied() + .unwrap(), + ) + }) + .collect(); + + TestData { + backed_candidates, + scheduled_paras: scheduled, + all_backed_candidates_with_core, + } } - #[test] - fn happy_path() { + // Generate test data for the candidates and assert that the evnironment is set as expected + // (check the comments for details) + // Para 1 scheduled on core 0 and core 1. Two candidates are supplied. + // Para 2 scheduled on cores 2 and 3. One candidate supplied. + // Para 3 scheduled on core 4. One candidate supplied. + // Para 4 scheduled on core 5. Two candidates supplied. + // Para 5 scheduled on core 6. No candidates supplied. + fn get_test_data_multiple_cores_per_para(core_index_enabled: bool) -> TestData { + const RELAY_PARENT_NUM: u32 = 3; + + // Add the relay parent to `shared` pallet. Otherwise some code (e.g. filtering backing + // votes) won't behave correctly + shared::Pallet::::add_allowed_relay_parent( + default_header().hash(), + Default::default(), + RELAY_PARENT_NUM, + 1, + ); + + let header = default_header(); + let relay_parent = header.hash(); + let session_index = SessionIndex::from(0_u32); + + let keystore = LocalKeystore::in_memory(); + let keystore = Arc::new(keystore) as KeystorePtr; + let signing_context = SigningContext { parent_hash: relay_parent, session_index }; + + let validators = vec![ + keyring::Sr25519Keyring::Alice, + keyring::Sr25519Keyring::Bob, + keyring::Sr25519Keyring::Charlie, + keyring::Sr25519Keyring::Dave, + keyring::Sr25519Keyring::Eve, + keyring::Sr25519Keyring::Ferdie, + keyring::Sr25519Keyring::One, + ]; + for validator in validators.iter() { + Keystore::sr25519_generate_new( + &*keystore, + PARACHAIN_KEY_TYPE_ID, + Some(&validator.to_seed()), + ) + .unwrap(); + } + + // Set active validators in `shared` pallet + let validator_ids = + validators.iter().map(|v| v.public().into()).collect::>(); + shared::Pallet::::set_active_validators_ascending(validator_ids); + + // Set the validator groups in `scheduler` + scheduler::Pallet::::set_validator_groups(vec![ + vec![ValidatorIndex(0)], + vec![ValidatorIndex(1)], + vec![ValidatorIndex(2)], + vec![ValidatorIndex(3)], + vec![ValidatorIndex(4)], + vec![ValidatorIndex(5)], + vec![ValidatorIndex(6)], + ]); + + // Update scheduler's claimqueue with the parachains + scheduler::Pallet::::set_claimqueue(BTreeMap::from([ + ( + CoreIndex::from(0), + VecDeque::from([ParasEntry::new( + Assignment::Pool { para_id: 1.into(), core_index: CoreIndex(0) }, + RELAY_PARENT_NUM, + )]), + ), + ( + CoreIndex::from(1), + VecDeque::from([ParasEntry::new( + Assignment::Pool { para_id: 1.into(), core_index: CoreIndex(1) }, + RELAY_PARENT_NUM, + )]), + ), + ( + CoreIndex::from(2), + VecDeque::from([ParasEntry::new( + Assignment::Pool { para_id: 2.into(), core_index: CoreIndex(2) }, + RELAY_PARENT_NUM, + )]), + ), + ( + CoreIndex::from(3), + VecDeque::from([ParasEntry::new( + Assignment::Pool { para_id: 2.into(), core_index: CoreIndex(3) }, + RELAY_PARENT_NUM, + )]), + ), + ( + CoreIndex::from(4), + VecDeque::from([ParasEntry::new( + Assignment::Pool { para_id: 3.into(), core_index: CoreIndex(4) }, + RELAY_PARENT_NUM, + )]), + ), + ( + CoreIndex::from(5), + VecDeque::from([ParasEntry::new( + Assignment::Pool { para_id: 4.into(), core_index: CoreIndex(5) }, + RELAY_PARENT_NUM, + )]), + ), + ( + CoreIndex::from(6), + VecDeque::from([ParasEntry::new( + Assignment::Pool { para_id: 5.into(), core_index: CoreIndex(6) }, + RELAY_PARENT_NUM, + )]), + ), + ])); + + // Callback used for backing candidates + let group_validators = |group_index: GroupIndex| { + match group_index { + group_index if group_index == GroupIndex::from(0) => Some(vec![0]), + group_index if group_index == GroupIndex::from(1) => Some(vec![1]), + group_index if group_index == GroupIndex::from(2) => Some(vec![2]), + group_index if group_index == GroupIndex::from(3) => Some(vec![3]), + group_index if group_index == GroupIndex::from(4) => Some(vec![4]), + group_index if group_index == GroupIndex::from(5) => Some(vec![5]), + group_index if group_index == GroupIndex::from(6) => Some(vec![6]), + + _ => panic!("Group index out of bounds"), + } + .map(|m| m.into_iter().map(ValidatorIndex).collect::>()) + }; + + let mut backed_candidates = vec![]; + let mut all_backed_candidates_with_core = vec![]; + + // Para 1 + { + let mut candidate = TestCandidateBuilder { + para_id: ParaId::from(1), + relay_parent, + pov_hash: Hash::repeat_byte(1 as u8), + persisted_validation_data_hash: [42u8; 32].into(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + + collator_sign_candidate(Sr25519Keyring::One, &mut candidate); + + let backed: BackedCandidate = back_candidate( + candidate, + &validators, + group_validators(GroupIndex::from(0 as u32)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + core_index_enabled.then_some(CoreIndex(0 as u32)), + ); + backed_candidates.push(backed.clone()); + if core_index_enabled { + all_backed_candidates_with_core.push((backed, CoreIndex(0))); + } + + let mut candidate = TestCandidateBuilder { + para_id: ParaId::from(1), + relay_parent, + pov_hash: Hash::repeat_byte(2 as u8), + persisted_validation_data_hash: [42u8; 32].into(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + + collator_sign_candidate(Sr25519Keyring::One, &mut candidate); + + let backed = back_candidate( + candidate, + &validators, + group_validators(GroupIndex::from(1 as u32)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + core_index_enabled.then_some(CoreIndex(1 as u32)), + ); + backed_candidates.push(backed.clone()); + if core_index_enabled { + all_backed_candidates_with_core.push((backed, CoreIndex(1))); + } + } + + // Para 2 + { + let mut candidate = TestCandidateBuilder { + para_id: ParaId::from(2), + relay_parent, + pov_hash: Hash::repeat_byte(3 as u8), + persisted_validation_data_hash: [42u8; 32].into(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + + collator_sign_candidate(Sr25519Keyring::One, &mut candidate); + + let backed = back_candidate( + candidate, + &validators, + group_validators(GroupIndex::from(2 as u32)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + core_index_enabled.then_some(CoreIndex(2 as u32)), + ); + backed_candidates.push(backed.clone()); + if core_index_enabled { + all_backed_candidates_with_core.push((backed, CoreIndex(2))); + } + } + + // Para 3 + { + let mut candidate = TestCandidateBuilder { + para_id: ParaId::from(3), + relay_parent, + pov_hash: Hash::repeat_byte(4 as u8), + persisted_validation_data_hash: [42u8; 32].into(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + + collator_sign_candidate(Sr25519Keyring::One, &mut candidate); + + let backed = back_candidate( + candidate, + &validators, + group_validators(GroupIndex::from(4 as u32)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + core_index_enabled.then_some(CoreIndex(4 as u32)), + ); + backed_candidates.push(backed.clone()); + all_backed_candidates_with_core.push((backed, CoreIndex(4))); + } + + // Para 4 + { + let mut candidate = TestCandidateBuilder { + para_id: ParaId::from(4), + relay_parent, + pov_hash: Hash::repeat_byte(5 as u8), + persisted_validation_data_hash: [42u8; 32].into(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + + collator_sign_candidate(Sr25519Keyring::One, &mut candidate); + + let backed = back_candidate( + candidate, + &validators, + group_validators(GroupIndex::from(5 as u32)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + None, + ); + backed_candidates.push(backed.clone()); + all_backed_candidates_with_core.push((backed, CoreIndex(5))); + + let mut candidate = TestCandidateBuilder { + para_id: ParaId::from(4), + relay_parent, + pov_hash: Hash::repeat_byte(6 as u8), + persisted_validation_data_hash: [42u8; 32].into(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + + collator_sign_candidate(Sr25519Keyring::One, &mut candidate); + + let backed = back_candidate( + candidate, + &validators, + group_validators(GroupIndex::from(5 as u32)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + core_index_enabled.then_some(CoreIndex(5 as u32)), + ); + backed_candidates.push(backed.clone()); + } + + // No candidate for para 5. + + // State sanity checks + assert_eq!( + >::scheduled_paras().collect::>(), + vec![ + (CoreIndex(0), ParaId::from(1)), + (CoreIndex(1), ParaId::from(1)), + (CoreIndex(2), ParaId::from(2)), + (CoreIndex(3), ParaId::from(2)), + (CoreIndex(4), ParaId::from(3)), + (CoreIndex(5), ParaId::from(4)), + (CoreIndex(6), ParaId::from(5)), + ] + ); + let mut scheduled: BTreeMap> = BTreeMap::new(); + for (core_idx, para_id) in >::scheduled_paras() { + scheduled.entry(para_id).or_default().insert(core_idx); + } + + assert_eq!( + shared::Pallet::::active_validator_indices(), + vec![ + ValidatorIndex(0), + ValidatorIndex(1), + ValidatorIndex(2), + ValidatorIndex(3), + ValidatorIndex(4), + ValidatorIndex(5), + ValidatorIndex(6), + ] + ); + + TestData { + backed_candidates, + scheduled_paras: scheduled, + all_backed_candidates_with_core, + } + } + + #[rstest] + #[case(false)] + #[case(true)] + fn happy_path(#[case] core_index_enabled: bool) { new_test_ext(MockGenesisConfig::default()).execute_with(|| { - let TestData { backed_candidates, scheduled_paras: scheduled } = get_test_data(); + let TestData { + backed_candidates, + all_backed_candidates_with_core, + scheduled_paras: scheduled, + } = get_test_data(core_index_enabled); let has_concluded_invalid = |_idx: usize, _backed_candidate: &BackedCandidate| -> bool { false }; @@ -1385,47 +1775,95 @@ mod sanitizers { backed_candidates.clone(), &>::allowed_relay_parents(), has_concluded_invalid, - &scheduled + scheduled, + core_index_enabled ), SanitizedBackedCandidates { - backed_candidates, - votes_from_disabled_were_dropped: false + backed_candidates_with_core: all_backed_candidates_with_core, + votes_from_disabled_were_dropped: false, + dropped_unscheduled_candidates: false } ); + }); + } + + #[rstest] + #[case(false)] + #[case(true)] + fn test_with_multiple_cores_per_para(#[case] core_index_enabled: bool) { + new_test_ext(MockGenesisConfig::default()).execute_with(|| { + let TestData { + backed_candidates, + all_backed_candidates_with_core: expected_all_backed_candidates_with_core, + scheduled_paras: scheduled, + } = get_test_data_multiple_cores_per_para(core_index_enabled); + + let has_concluded_invalid = + |_idx: usize, _backed_candidate: &BackedCandidate| -> bool { false }; - {} + assert_eq!( + sanitize_backed_candidates::( + backed_candidates.clone(), + &>::allowed_relay_parents(), + has_concluded_invalid, + scheduled, + core_index_enabled + ), + SanitizedBackedCandidates { + backed_candidates_with_core: expected_all_backed_candidates_with_core, + votes_from_disabled_were_dropped: false, + dropped_unscheduled_candidates: true + } + ); }); } // nothing is scheduled, so no paraids match, thus all backed candidates are skipped - #[test] - fn nothing_scheduled() { + #[rstest] + #[case(false, false)] + #[case(true, true)] + #[case(false, true)] + #[case(true, false)] + fn nothing_scheduled( + #[case] core_index_enabled: bool, + #[case] multiple_cores_per_para: bool, + ) { new_test_ext(MockGenesisConfig::default()).execute_with(|| { - let TestData { backed_candidates, scheduled_paras: _ } = get_test_data(); - let scheduled = &BTreeMap::new(); + let TestData { backed_candidates, .. } = if multiple_cores_per_para { + get_test_data_multiple_cores_per_para(core_index_enabled) + } else { + get_test_data(core_index_enabled) + }; + let scheduled = BTreeMap::new(); let has_concluded_invalid = |_idx: usize, _backed_candidate: &BackedCandidate| -> bool { false }; let SanitizedBackedCandidates { - backed_candidates: sanitized_backed_candidates, + backed_candidates_with_core: sanitized_backed_candidates, votes_from_disabled_were_dropped, + dropped_unscheduled_candidates, } = sanitize_backed_candidates::( backed_candidates.clone(), &>::allowed_relay_parents(), has_concluded_invalid, - &scheduled, + scheduled, + core_index_enabled, ); assert!(sanitized_backed_candidates.is_empty()); assert!(!votes_from_disabled_were_dropped); + assert!(dropped_unscheduled_candidates); }); } // candidates that have concluded as invalid are filtered out - #[test] - fn invalid_are_filtered_out() { + #[rstest] + #[case(false)] + #[case(true)] + fn invalid_are_filtered_out(#[case] core_index_enabled: bool) { new_test_ext(MockGenesisConfig::default()).execute_with(|| { - let TestData { backed_candidates, scheduled_paras: scheduled } = get_test_data(); + let TestData { backed_candidates, scheduled_paras: scheduled, .. } = + get_test_data(core_index_enabled); // mark every second one as concluded invalid let set = { @@ -1440,45 +1878,55 @@ mod sanitizers { let has_concluded_invalid = |_idx: usize, candidate: &BackedCandidate| set.contains(&candidate.hash()); let SanitizedBackedCandidates { - backed_candidates: sanitized_backed_candidates, + backed_candidates_with_core: sanitized_backed_candidates, votes_from_disabled_were_dropped, + dropped_unscheduled_candidates, } = sanitize_backed_candidates::( backed_candidates.clone(), &>::allowed_relay_parents(), has_concluded_invalid, - &scheduled, + scheduled, + core_index_enabled, ); assert_eq!(sanitized_backed_candidates.len(), backed_candidates.len() / 2); assert!(!votes_from_disabled_were_dropped); + assert!(!dropped_unscheduled_candidates); }); } - #[test] - fn disabled_non_signing_validator_doesnt_get_filtered() { + #[rstest] + #[case(false)] + #[case(true)] + fn disabled_non_signing_validator_doesnt_get_filtered(#[case] core_index_enabled: bool) { new_test_ext(MockGenesisConfig::default()).execute_with(|| { - let TestData { mut backed_candidates, scheduled_paras } = get_test_data(); + let TestData { mut all_backed_candidates_with_core, .. } = + get_test_data(core_index_enabled); // Disable Eve set_disabled_validators(vec![4]); - let before = backed_candidates.clone(); + let before = all_backed_candidates_with_core.clone(); // Eve is disabled but no backing statement is signed by it so nothing should be // filtered assert!(!filter_backed_statements_from_disabled_validators::( - &mut backed_candidates, + &mut all_backed_candidates_with_core, &>::allowed_relay_parents(), - &scheduled_paras + core_index_enabled )); - assert_eq!(backed_candidates, before); + assert_eq!(all_backed_candidates_with_core, before); }); } - - #[test] - fn drop_statements_from_disabled_without_dropping_candidate() { + #[rstest] + #[case(false)] + #[case(true)] + fn drop_statements_from_disabled_without_dropping_candidate( + #[case] core_index_enabled: bool, + ) { new_test_ext(MockGenesisConfig::default()).execute_with(|| { - let TestData { mut backed_candidates, scheduled_paras } = get_test_data(); + let TestData { mut all_backed_candidates_with_core, .. } = + get_test_data(core_index_enabled); // Disable Alice set_disabled_validators(vec![0]); @@ -1491,61 +1939,83 @@ mod sanitizers { configuration::Pallet::::force_set_active_config(hc); // Verify the initial state is as expected - assert_eq!(backed_candidates.get(0).unwrap().validity_votes.len(), 2); assert_eq!( - backed_candidates.get(0).unwrap().validator_indices.get(0).unwrap(), - true + all_backed_candidates_with_core.get(0).unwrap().0.validity_votes().len(), + 2 ); - assert_eq!( - backed_candidates.get(0).unwrap().validator_indices.get(1).unwrap(), - true - ); - let untouched = backed_candidates.get(1).unwrap().clone(); + let (validator_indices, maybe_core_index) = all_backed_candidates_with_core + .get(0) + .unwrap() + .0 + .validator_indices_and_core_index(core_index_enabled); + if core_index_enabled { + assert!(maybe_core_index.is_some()); + } else { + assert!(maybe_core_index.is_none()); + } + + assert_eq!(validator_indices.get(0).unwrap(), true); + assert_eq!(validator_indices.get(1).unwrap(), true); + let untouched = all_backed_candidates_with_core.get(1).unwrap().0.clone(); assert!(filter_backed_statements_from_disabled_validators::( - &mut backed_candidates, + &mut all_backed_candidates_with_core, &>::allowed_relay_parents(), - &scheduled_paras + core_index_enabled )); + let (validator_indices, maybe_core_index) = all_backed_candidates_with_core + .get(0) + .unwrap() + .0 + .validator_indices_and_core_index(core_index_enabled); + if core_index_enabled { + assert!(maybe_core_index.is_some()); + } else { + assert!(maybe_core_index.is_none()); + } + // there should still be two backed candidates - assert_eq!(backed_candidates.len(), 2); + assert_eq!(all_backed_candidates_with_core.len(), 2); // but the first one should have only one validity vote - assert_eq!(backed_candidates.get(0).unwrap().validity_votes.len(), 1); - // Validator 0 vote should be dropped, validator 1 - retained assert_eq!( - backed_candidates.get(0).unwrap().validator_indices.get(0).unwrap(), - false - ); - assert_eq!( - backed_candidates.get(0).unwrap().validator_indices.get(1).unwrap(), - true + all_backed_candidates_with_core.get(0).unwrap().0.validity_votes().len(), + 1 ); + // Validator 0 vote should be dropped, validator 1 - retained + assert_eq!(validator_indices.get(0).unwrap(), false); + assert_eq!(validator_indices.get(1).unwrap(), true); // the second candidate shouldn't be modified - assert_eq!(*backed_candidates.get(1).unwrap(), untouched); + assert_eq!(all_backed_candidates_with_core.get(1).unwrap().0, untouched); }); } - #[test] - fn drop_candidate_if_all_statements_are_from_disabled() { + #[rstest] + #[case(false)] + #[case(true)] + fn drop_candidate_if_all_statements_are_from_disabled(#[case] core_index_enabled: bool) { new_test_ext(MockGenesisConfig::default()).execute_with(|| { - let TestData { mut backed_candidates, scheduled_paras } = get_test_data(); + let TestData { mut all_backed_candidates_with_core, .. } = + get_test_data(core_index_enabled); // Disable Alice and Bob set_disabled_validators(vec![0, 1]); // Verify the initial state is as expected - assert_eq!(backed_candidates.get(0).unwrap().validity_votes.len(), 2); - let untouched = backed_candidates.get(1).unwrap().clone(); + assert_eq!( + all_backed_candidates_with_core.get(0).unwrap().0.validity_votes().len(), + 2 + ); + let untouched = all_backed_candidates_with_core.get(1).unwrap().0.clone(); assert!(filter_backed_statements_from_disabled_validators::( - &mut backed_candidates, + &mut all_backed_candidates_with_core, &>::allowed_relay_parents(), - &scheduled_paras + core_index_enabled )); - assert_eq!(backed_candidates.len(), 1); - assert_eq!(*backed_candidates.get(0).unwrap(), untouched); + assert_eq!(all_backed_candidates_with_core.len(), 1); + assert_eq!(all_backed_candidates_with_core.get(0).unwrap().0, untouched); }); } } diff --git a/polkadot/runtime/parachains/src/paras_inherent/weights.rs b/polkadot/runtime/parachains/src/paras_inherent/weights.rs index 05cc53fae04..0f4e5be572a 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/weights.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/weights.rs @@ -149,11 +149,11 @@ pub fn backed_candidate_weight( candidate: &BackedCandidate, ) -> Weight { set_proof_size_to_tx_size( - if candidate.candidate.commitments.new_validation_code.is_some() { + if candidate.candidate().commitments.new_validation_code.is_some() { <::WeightInfo as WeightInfo>::enter_backed_candidate_code_upgrade() } else { <::WeightInfo as WeightInfo>::enter_backed_candidates_variable( - candidate.validity_votes.len() as u32, + candidate.validity_votes().len() as u32, ) }, candidate, diff --git a/polkadot/zombienet_tests/functional/0012-elastic-scaling-mvp.toml b/polkadot/zombienet_tests/functional/0012-elastic-scaling-mvp.toml new file mode 100644 index 00000000000..0dfd814e10a --- /dev/null +++ b/polkadot/zombienet_tests/functional/0012-elastic-scaling-mvp.toml @@ -0,0 +1,38 @@ +[settings] +timeout = 1000 +bootnode = true + +[relaychain.genesis.runtimeGenesis.patch.configuration.config] + max_validators_per_core = 2 + needed_approvals = 4 + coretime_cores = 2 + +[relaychain] +default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" +chain = "rococo-local" +default_command = "polkadot" + +[relaychain.default_resources] +limits = { memory = "4G", cpu = "2" } +requests = { memory = "2G", cpu = "1" } + + [[relaychain.nodes]] + name = "alice" + validator = "true" + + [[relaychain.node_groups]] + name = "validator" + count = 3 + args = [ "-lparachain=debug,runtime=debug"] + +[[parachains]] +id = 2000 +default_command = "polkadot-parachain" +add_to_genesis = false +register_para = true +onboard_as_parachain = false + + [parachains.collator] + name = "collator2000" + command = "polkadot-parachain" + args = [ "-lparachain=debug" ] diff --git a/polkadot/zombienet_tests/functional/0012-elastic-scaling-mvp.zndsl b/polkadot/zombienet_tests/functional/0012-elastic-scaling-mvp.zndsl new file mode 100644 index 00000000000..a7193c9282b --- /dev/null +++ b/polkadot/zombienet_tests/functional/0012-elastic-scaling-mvp.zndsl @@ -0,0 +1,28 @@ +Description: Test that a paraid acquiring multiple cores does not brick itself if ElasticScalingMVP feature is enabled +Network: ./0012-elastic-scaling-mvp.toml +Creds: config + +# Check authority status. +validator: reports node_roles is 4 + +validator: reports substrate_block_height{status="finalized"} is at least 10 within 100 seconds + +# Ensure parachain was able to make progress. +validator: parachain 2000 block height is at least 10 within 200 seconds + +# Register the second core assigned to this parachain. +alice: js-script ./0012-register-para.js return is 0 within 600 seconds + +validator: reports substrate_block_height{status="finalized"} is at least 35 within 100 seconds + +# Parachain will now be stalled +validator: parachain 2000 block height is lower than 20 within 300 seconds + +# Enable the ElasticScalingMVP node feature. +alice: js-script ./0012-enable-node-feature.js with "1" return is 0 within 600 seconds + +# Wait two sessions for the config to be updated. +sleep 120 seconds + +# Ensure parachain is now making progress. +validator: parachain 2000 block height is at least 30 within 200 seconds diff --git a/polkadot/zombienet_tests/functional/0012-enable-node-feature.js b/polkadot/zombienet_tests/functional/0012-enable-node-feature.js new file mode 100644 index 00000000000..4822e1f6644 --- /dev/null +++ b/polkadot/zombienet_tests/functional/0012-enable-node-feature.js @@ -0,0 +1,37 @@ +async function run(nodeName, networkInfo, index) { + const { wsUri, userDefinedTypes } = networkInfo.nodesByName[nodeName]; + const api = await zombie.connect(wsUri, userDefinedTypes); + + await zombie.util.cryptoWaitReady(); + + // account to submit tx + const keyring = new zombie.Keyring({ type: "sr25519" }); + const alice = keyring.addFromUri("//Alice"); + + await new Promise(async (resolve, reject) => { + const unsub = await api.tx.sudo + .sudo(api.tx.configuration.setNodeFeature(Number(index), true)) + .signAndSend(alice, ({ status, isError }) => { + if (status.isInBlock) { + console.log( + `Transaction included at blockhash ${status.asInBlock}`, + ); + } else if (status.isFinalized) { + console.log( + `Transaction finalized at blockHash ${status.asFinalized}`, + ); + unsub(); + return resolve(); + } else if (isError) { + console.log(`Transaction error`); + reject(`Transaction error`); + } + }); + }); + + + + return 0; +} + +module.exports = { run }; diff --git a/polkadot/zombienet_tests/functional/0012-register-para.js b/polkadot/zombienet_tests/functional/0012-register-para.js new file mode 100644 index 00000000000..25c7e4f5ffd --- /dev/null +++ b/polkadot/zombienet_tests/functional/0012-register-para.js @@ -0,0 +1,37 @@ +async function run(nodeName, networkInfo, _jsArgs) { + const { wsUri, userDefinedTypes } = networkInfo.nodesByName[nodeName]; + const api = await zombie.connect(wsUri, userDefinedTypes); + + await zombie.util.cryptoWaitReady(); + + // account to submit tx + const keyring = new zombie.Keyring({ type: "sr25519" }); + const alice = keyring.addFromUri("//Alice"); + + await new Promise(async (resolve, reject) => { + const unsub = await api.tx.sudo + .sudo(api.tx.coretime.assignCore(0, 35, [[{ task: 2000 }, 57600]], null)) + .signAndSend(alice, ({ status, isError }) => { + if (status.isInBlock) { + console.log( + `Transaction included at blockhash ${status.asInBlock}`, + ); + } else if (status.isFinalized) { + console.log( + `Transaction finalized at blockHash ${status.asFinalized}`, + ); + unsub(); + return resolve(); + } else if (isError) { + console.log(`Transaction error`); + reject(`Transaction error`); + } + }); + }); + + + + return 0; +} + +module.exports = { run }; diff --git a/prdoc/pr_3231.prdoc b/prdoc/pr_3231.prdoc new file mode 100644 index 00000000000..26e96d3635b --- /dev/null +++ b/prdoc/pr_3231.prdoc @@ -0,0 +1,11 @@ +title: Allow parachain which acquires multiple coretime cores to make progress + +doc: + - audience: Node Operator + description: | + Adds the needed changes so that parachains which acquire multiple coretime cores can still make progress. + Only one of the cores will be able to be occupied at a time. + Only works if the ElasticScalingMVP node feature is enabled in the runtime and the block author validator is + updated to include this change. + +crates: [ ] -- GitLab From de6d02591b57d03f70ed8db0c674f045ad2ea029 Mon Sep 17 00:00:00 2001 From: tmpolaczyk <44604217+tmpolaczyk@users.noreply.github.com> Date: Sat, 24 Feb 2024 11:34:05 +0100 Subject: [PATCH 123/133] Use generic hash for runtime wasm in resolve_state_version_from_wasm (#3447) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes the runtime hash algorithm used in `resolve_state_version_from_wasm` from `DefaultHasher` to a caller-provided one (usually `HashingFor`), to match the one used elsewhere. This fixes an issue where the runtime wasm is compiled 3 times when starting the `tanssi-node` with `--dev`. With this fix, the runtime wasm is only compiled 2 times. The other redundant compilation is caused by the `GenesisConfigBuilderRuntimeCaller` struct, which ignores the runtime cache. --------- Co-authored-by: Bastian Köcher --- prdoc/pr_3447.prdoc | 13 +++++++++++++ .../client/chain-spec/src/genesis_block.rs | 18 ++++++++---------- substrate/client/service/src/client/client.rs | 6 ++++-- 3 files changed, 25 insertions(+), 12 deletions(-) create mode 100644 prdoc/pr_3447.prdoc diff --git a/prdoc/pr_3447.prdoc b/prdoc/pr_3447.prdoc new file mode 100644 index 00000000000..1d8d4f409f7 --- /dev/null +++ b/prdoc/pr_3447.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Use generic hash for runtime wasm in resolve_state_version_from_wasm + +doc: + - audience: Node Dev + description: | + Changes the runtime hash algorithm used in resolve_state_version_from_wasm from DefaultHasher to a caller-provided + one (usually HashingFor). Fixes a bug where the runtime wasm was being compiled again when it was not + needed, because the hash did not match +crates: + - name: sc-chain-spec diff --git a/substrate/client/chain-spec/src/genesis_block.rs b/substrate/client/chain-spec/src/genesis_block.rs index 6aa156a620a..3c7b9f64dcd 100644 --- a/substrate/client/chain-spec/src/genesis_block.rs +++ b/substrate/client/chain-spec/src/genesis_block.rs @@ -18,23 +18,25 @@ //! Tool for creating the genesis block. -use std::{collections::hash_map::DefaultHasher, marker::PhantomData, sync::Arc}; +use std::{marker::PhantomData, sync::Arc}; +use codec::Encode; use sc_client_api::{backend::Backend, BlockImportOperation}; use sc_executor::RuntimeVersionOf; use sp_core::storage::{well_known_keys, StateVersion, Storage}; use sp_runtime::{ - traits::{Block as BlockT, Hash as HashT, Header as HeaderT, Zero}, + traits::{Block as BlockT, Hash as HashT, HashingFor, Header as HeaderT, Zero}, BuildStorage, }; /// Return the state version given the genesis storage and executor. -pub fn resolve_state_version_from_wasm( +pub fn resolve_state_version_from_wasm( storage: &Storage, executor: &E, ) -> sp_blockchain::Result where E: RuntimeVersionOf, + H: HashT, { if let Some(wasm) = storage.top.get(well_known_keys::CODE) { let mut ext = sp_state_machine::BasicExternalities::new_empty(); // just to read runtime version. @@ -43,12 +45,7 @@ where let runtime_code = sp_core::traits::RuntimeCode { code_fetcher: &code_fetcher, heap_pages: None, - hash: { - use std::hash::{Hash, Hasher}; - let mut state = DefaultHasher::new(); - wasm.hash(&mut state); - state.finish().to_le_bytes().to_vec() - }, + hash: ::hash(wasm).encode(), }; let runtime_version = RuntimeVersionOf::runtime_version(executor, &mut ext, &runtime_code) .map_err(|e| sp_blockchain::Error::VersionInvalid(e.to_string()))?; @@ -129,7 +126,8 @@ impl, E: RuntimeVersionOf> BuildGenesisBlock sp_blockchain::Result<(Block, Self::BlockImportOperation)> { let Self { genesis_storage, commit_genesis_state, backend, executor, _phantom } = self; - let genesis_state_version = resolve_state_version_from_wasm(&genesis_storage, &executor)?; + let genesis_state_version = + resolve_state_version_from_wasm::<_, HashingFor>(&genesis_storage, &executor)?; let mut op = backend.begin_operation()?; let state_root = op.set_genesis_state(genesis_storage, commit_genesis_state, genesis_state_version)?; diff --git a/substrate/client/service/src/client/client.rs b/substrate/client/service/src/client/client.rs index aa9c1b80a29..108c258595a 100644 --- a/substrate/client/service/src/client/client.rs +++ b/substrate/client/service/src/client/client.rs @@ -675,8 +675,10 @@ where // This is use by fast sync for runtime version to be resolvable from // changes. - let state_version = - resolve_state_version_from_wasm(&storage, &self.executor)?; + let state_version = resolve_state_version_from_wasm::<_, HashingFor>( + &storage, + &self.executor, + )?; let state_root = operation.op.reset_storage(storage, state_version)?; if state_root != *import_headers.post().state_root() { // State root mismatch when importing state. This should not happen in -- GitLab From ce5de99540eb1ceaed4b0a5d085983c4a273fdda Mon Sep 17 00:00:00 2001 From: eskimor Date: Mon, 26 Feb 2024 07:23:28 +0100 Subject: [PATCH 124/133] Remove redundant parachains assigner pallet. (#3457) from Westend and Rococo. --------- Co-authored-by: eskimor Co-authored-by: command-bot <> --- polkadot/runtime/rococo/src/lib.rs | 8 ++------ polkadot/runtime/westend/src/lib.rs | 8 ++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 03e96ab388b..1566911fc22 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -46,9 +46,8 @@ use sp_std::{cmp::Ordering, collections::btree_map::BTreeMap, prelude::*}; use runtime_parachains::{ assigner_coretime as parachains_assigner_coretime, - assigner_on_demand as parachains_assigner_on_demand, - assigner_parachains as parachains_assigner_parachains, - configuration as parachains_configuration, coretime, disputes as parachains_disputes, + assigner_on_demand as parachains_assigner_on_demand, configuration as parachains_configuration, + coretime, disputes as parachains_disputes, disputes::slashing as parachains_slashing, dmp as parachains_dmp, hrmp as parachains_hrmp, inclusion as parachains_inclusion, inclusion::{AggregateMessageOrigin, UmpQueueId}, @@ -1038,8 +1037,6 @@ impl parachains_assigner_on_demand::Config for Runtime { type WeightInfo = weights::runtime_parachains_assigner_on_demand::WeightInfo; } -impl parachains_assigner_parachains::Config for Runtime {} - impl parachains_assigner_coretime::Config for Runtime {} impl parachains_initializer::Config for Runtime { @@ -1401,7 +1398,6 @@ construct_runtime! { ParasSlashing: parachains_slashing = 63, MessageQueue: pallet_message_queue = 64, OnDemandAssignmentProvider: parachains_assigner_on_demand = 66, - ParachainsAssignmentProvider: parachains_assigner_parachains = 67, CoretimeAssignmentProvider: parachains_assigner_coretime = 68, // Parachain Onboarding Pallets. Start indices at 70 to leave room. diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index bbb010f60bf..b0b70bff8de 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -68,9 +68,8 @@ use runtime_common::{ }; use runtime_parachains::{ assigner_coretime as parachains_assigner_coretime, - assigner_on_demand as parachains_assigner_on_demand, - assigner_parachains as parachains_assigner_parachains, - configuration as parachains_configuration, coretime, disputes as parachains_disputes, + assigner_on_demand as parachains_assigner_on_demand, configuration as parachains_configuration, + coretime, disputes as parachains_disputes, disputes::slashing as parachains_slashing, dmp as parachains_dmp, hrmp as parachains_hrmp, inclusion as parachains_inclusion, inclusion::{AggregateMessageOrigin, UmpQueueId}, @@ -1243,8 +1242,6 @@ impl parachains_assigner_on_demand::Config for Runtime { type WeightInfo = weights::runtime_parachains_assigner_on_demand::WeightInfo; } -impl parachains_assigner_parachains::Config for Runtime {} - impl parachains_assigner_coretime::Config for Runtime {} impl parachains_initializer::Config for Runtime { @@ -1505,7 +1502,6 @@ construct_runtime! { ParaSessionInfo: parachains_session_info = 52, ParasDisputes: parachains_disputes = 53, ParasSlashing: parachains_slashing = 54, - ParachainsAssignmentProvider: parachains_assigner_parachains = 55, OnDemandAssignmentProvider: parachains_assigner_on_demand = 56, CoretimeAssignmentProvider: parachains_assigner_coretime = 57, -- GitLab From b9c792a4b8adb4617cafe7e0b8949b88f5a9d247 Mon Sep 17 00:00:00 2001 From: Alexandru Gheorghe <49718502+alexggh@users.noreply.github.com> Date: Mon, 26 Feb 2024 10:06:21 +0200 Subject: [PATCH 125/133] Add more debug logs to understand if statement-distribution misbehaving (#3419) Add more debug logs to understand if statement-distribution is in a bad state, should be useful for debugging https://github.com/paritytech/polkadot-sdk/issues/3314 on production networks. Additionally, increase the number of parallel requests should make, since I notice that requests take around 100ms on kusama, and the 5 parallel request was picked mostly random, no reason why we can do more than that. --------- Signed-off-by: Alexandru Gheorghe Co-authored-by: ordian --- .../statement-distribution/src/v2/cluster.rs | 25 ++++++++++++++++++- .../statement-distribution/src/v2/mod.rs | 17 ++++++++++++- .../statement-distribution/src/v2/requests.rs | 14 ++++++++++- 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/polkadot/node/network/statement-distribution/src/v2/cluster.rs b/polkadot/node/network/statement-distribution/src/v2/cluster.rs index 619114de967..c09916e5620 100644 --- a/polkadot/node/network/statement-distribution/src/v2/cluster.rs +++ b/polkadot/node/network/statement-distribution/src/v2/cluster.rs @@ -55,8 +55,9 @@ //! and to keep track of what we have sent to other validators in the group and what we may //! continue to send them. -use polkadot_primitives::{CandidateHash, CompactStatement, ValidatorIndex}; +use polkadot_primitives::{CandidateHash, CompactStatement, Hash, ValidatorIndex}; +use crate::LOG_TARGET; use std::collections::{HashMap, HashSet}; #[derive(Hash, PartialEq, Eq)] @@ -424,6 +425,28 @@ impl ClusterTracker { fn is_in_group(&self, validator: ValidatorIndex) -> bool { self.validators.contains(&validator) } + + /// Dumps pending statement for this cluster. + /// + /// Normally we should not have pending statements to validators in our cluster, + /// but if we do for all validators in our cluster, then we don't participate + /// in backing. Ocasional pending statements are expected if two authorities + /// can't detect each otehr or after restart, where it takes a while to discover + /// the whole network. + + pub fn warn_if_too_many_pending_statements(&self, parent_hash: Hash) { + if self.pending.iter().filter(|pending| !pending.1.is_empty()).count() >= + self.validators.len() + { + gum::warn!( + target: LOG_TARGET, + pending_statements = ?self.pending, + ?parent_hash, + "Cluster has too many pending statements, something wrong with our connection to our group peers \n + Restart might be needed if validator gets 0 backing rewards for more than 3-4 consecutive sessions" + ); + } + } } /// Incoming statement was accepted. diff --git a/polkadot/node/network/statement-distribution/src/v2/mod.rs b/polkadot/node/network/statement-distribution/src/v2/mod.rs index dc29c19a48e..2c9cdba4ea8 100644 --- a/polkadot/node/network/statement-distribution/src/v2/mod.rs +++ b/polkadot/node/network/statement-distribution/src/v2/mod.rs @@ -251,6 +251,13 @@ impl PerSessionState { if local_index.is_some() { self.local_validator.get_or_insert(LocalValidatorIndex::Inactive); } + + gum::info!( + target: LOG_TARGET, + index_in_gossip_topology = ?local_index, + index_in_parachain_authorities = ?self.local_validator, + "Node uses the following topology indices" + ); } /// Returns `true` if local is neither active or inactive validator node. @@ -768,7 +775,15 @@ pub(crate) fn handle_deactivate_leaves(state: &mut State, leaves: &[Hash]) { let pruned = state.implicit_view.deactivate_leaf(*leaf); for pruned_rp in pruned { // clean up per-relay-parent data based on everything removed. - state.per_relay_parent.remove(&pruned_rp); + state + .per_relay_parent + .remove(&pruned_rp) + .as_ref() + .and_then(|pruned| pruned.active_validator_state()) + .map(|active_state| { + active_state.cluster_tracker.warn_if_too_many_pending_statements(pruned_rp) + }); + // clean up requests related to this relay parent. state.request_manager.remove_by_relay_parent(*leaf); } diff --git a/polkadot/node/network/statement-distribution/src/v2/requests.rs b/polkadot/node/network/statement-distribution/src/v2/requests.rs index bed3d5c18ae..bbcb268415e 100644 --- a/polkadot/node/network/statement-distribution/src/v2/requests.rs +++ b/polkadot/node/network/statement-distribution/src/v2/requests.rs @@ -315,7 +315,16 @@ impl RequestManager { request_props: impl Fn(&CandidateIdentifier) -> Option, peer_advertised: impl Fn(&CandidateIdentifier, &PeerId) -> Option, ) -> Option> { - if response_manager.len() >= MAX_PARALLEL_ATTESTED_CANDIDATE_REQUESTS as usize { + // The number of parallel requests a node can answer is limited by + // `MAX_PARALLEL_ATTESTED_CANDIDATE_REQUESTS`, however there is no + // need for the current node to limit itself to the same amount the + // requests, because the requests are going to different nodes anyways. + // While looking at https://github.com/paritytech/polkadot-sdk/issues/3314, + // found out that this requests take around 100ms to fullfill, so it + // would make sense to try to request things as early as we can, given + // we would need to request it for each candidate, around 25 right now + // on kusama. + if response_manager.len() >= 2 * MAX_PARALLEL_ATTESTED_CANDIDATE_REQUESTS as usize { return None } @@ -1027,6 +1036,7 @@ mod tests { let peer_advertised = |_identifier: &CandidateIdentifier, _peer: &_| { Some(StatementFilter::full(group_size)) }; + let outgoing = request_manager .next_request(&mut response_manager, request_props, peer_advertised) .unwrap(); @@ -1148,6 +1158,7 @@ mod tests { { let request_props = |_identifier: &CandidateIdentifier| Some((&request_properties).clone()); + let outgoing = request_manager .next_request(&mut response_manager, request_props, peer_advertised) .unwrap(); @@ -1230,6 +1241,7 @@ mod tests { { let request_props = |_identifier: &CandidateIdentifier| Some((&request_properties).clone()); + let outgoing = request_manager .next_request(&mut response_manager, request_props, peer_advertised) .unwrap(); -- GitLab From 3d9439f64652e60d1dee85ca98fd865775d9ca45 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Mon, 26 Feb 2024 09:12:28 +0100 Subject: [PATCH 126/133] [pallet-xcm] Adjust benchmarks (teleport_assets/reserve_transfer_assets) not relying on ED (#3464) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem During the bumping of the `polkadot-fellows` repository to `polkadot-sdk@1.6.0`, I encountered a situation where the benchmarks `teleport_assets` and `reserve_transfer_assets` in AssetHubKusama started to fail. This issue arose due to a decreased ED balance for AssetHubs introduced [here](https://github.com/polkadot-fellows/runtimes/pull/158/files#diff-80668ff8e793b64f36a9a3ec512df5cbca4ad448c157a5d81abda1b15f35f1daR213), and also because of a [missing CI pipeline](https://github.com/polkadot-fellows/runtimes/issues/197) to check the benchmarks, which went unnoticed. These benchmarks expect the `caller` to have enough: 1. balance to transfer (BTT) 2. balance for paying delivery (BFPD). So the initial balance was calculated as `ED * 100`, which seems reasonable: ``` const ED_MULTIPLIER: u32 = 100; let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into());` ``` The problem arises when the price for delivery is 100 times higher than the existential deposit. In other words, when `ED * 100` does not cover `BTT` + `BFPD`. I check AHR/AHW/AHK/AHP and this problem has only AssetHubKusama ``` ED: 3333333 calculated price to parent delivery: 1031666634 (from xcm logs from the benchmark) --- 3333333 * 100 - BTT(3333333) - BFPD(1031666634) = −701666667 ``` which results in the error; ``` 2024-02-23 09:19:42 Unable to charge fee with error Module(ModuleError { index: 31, error: [17, 0, 0, 0], message: Some("FeesNotMet") }) Error: Input("Benchmark pallet_xcm::reserve_transfer_assets failed: FeesNotMet") ``` ## Solution The benchmarks `teleport_assets` and `reserve_transfer_assets` were fixed by removing `ED * 100` and replacing it with `DeliveryHelper` logic, which calculates the (almost real) price for delivery and sets it along with the existential deposit as the initial balance for the account used in the benchmark. ## TODO - [ ] patch for 1.6 - https://github.com/paritytech/polkadot-sdk/pull/3466 - [ ] patch for 1.7 - https://github.com/paritytech/polkadot-sdk/pull/3465 - [ ] patch for 1.8 - TODO: PR --------- Co-authored-by: Francisco Aguirre --- Cargo.lock | 3 +- cumulus/pallets/parachain-system/Cargo.toml | 4 + cumulus/pallets/parachain-system/src/lib.rs | 9 +++ .../assets/asset-hub-rococo/src/lib.rs | 42 +++++++---- .../assets/asset-hub-westend/src/lib.rs | 42 +++++++---- .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 8 +- .../bridge-hubs/bridge-hub-westend/src/lib.rs | 8 +- .../collectives-westend/src/lib.rs | 15 +++- .../contracts/contracts-rococo/src/lib.rs | 23 +++++- .../coretime/coretime-rococo/src/lib.rs | 8 +- .../coretime/coretime-westend/src/lib.rs | 8 +- .../runtimes/people/people-rococo/src/lib.rs | 8 +- .../runtimes/people/people-westend/src/lib.rs | 8 +- cumulus/primitives/utility/Cargo.toml | 3 - cumulus/primitives/utility/src/lib.rs | 11 ++- polkadot/runtime/common/Cargo.toml | 4 - polkadot/runtime/common/src/xcm_sender.rs | 15 +++- polkadot/runtime/rococo/src/lib.rs | 28 +++++-- polkadot/runtime/westend/src/lib.rs | 46 ++++++++---- polkadot/runtime/westend/src/xcm_config.rs | 2 +- polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs | 32 +------- polkadot/xcm/pallet-xcm/src/benchmarking.rs | 73 ++++++++++++------- polkadot/xcm/pallet-xcm/src/mock.rs | 22 ++++++ .../xcm/xcm-builder/src/currency_adapter.rs | 2 +- .../xcm/xcm-builder/src/fungible_adapter.rs | 2 +- .../xcm/xcm-builder/src/fungibles_adapter.rs | 2 +- polkadot/xcm/xcm-builder/src/lib.rs | 2 +- polkadot/xcm/xcm-builder/src/routing.rs | 35 +++++++++ 28 files changed, 329 insertions(+), 136 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a23be53b34f..fae9eacdab9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3874,6 +3874,7 @@ dependencies = [ "pallet-message-queue", "parity-scale-codec", "polkadot-parachain-primitives", + "polkadot-runtime-common", "polkadot-runtime-parachains", "rand", "sc-client-api", @@ -4086,7 +4087,6 @@ dependencies = [ "frame-support", "log", "pallet-asset-conversion", - "pallet-xcm-benchmarks", "parity-scale-codec", "polkadot-runtime-common", "polkadot-runtime-parachains", @@ -13277,7 +13277,6 @@ dependencies = [ "pallet-transaction-payment", "pallet-treasury", "pallet-vesting", - "pallet-xcm-benchmarks", "parity-scale-codec", "polkadot-primitives", "polkadot-primitives-test-helpers", diff --git a/cumulus/pallets/parachain-system/Cargo.toml b/cumulus/pallets/parachain-system/Cargo.toml index 86647290563..7e0442f0b58 100644 --- a/cumulus/pallets/parachain-system/Cargo.toml +++ b/cumulus/pallets/parachain-system/Cargo.toml @@ -36,6 +36,7 @@ sp-version = { path = "../../../substrate/primitives/version", default-features # Polkadot polkadot-parachain-primitives = { path = "../../../polkadot/parachain", default-features = false, features = ["wasm-api"] } polkadot-runtime-parachains = { path = "../../../polkadot/runtime/parachains", default-features = false } +polkadot-runtime-common = { path = "../../../polkadot/runtime/common", default-features = false, optional = true } xcm = { package = "staging-xcm", path = "../../../polkadot/xcm", default-features = false } # Cumulus @@ -79,6 +80,7 @@ std = [ "log/std", "pallet-message-queue/std", "polkadot-parachain-primitives/std", + "polkadot-runtime-common/std", "polkadot-runtime-parachains/std", "scale-info/std", "sp-core/std", @@ -102,6 +104,7 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", + "polkadot-runtime-common/runtime-benchmarks", "polkadot-runtime-parachains/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] @@ -110,6 +113,7 @@ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", "pallet-message-queue/try-runtime", + "polkadot-runtime-common?/try-runtime", "polkadot-runtime-parachains/try-runtime", "sp-runtime/try-runtime", ] diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index 5a0fa57fb17..d86a67e58f4 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -1610,6 +1610,15 @@ impl UpwardMessageSender for Pallet { } } +#[cfg(feature = "runtime-benchmarks")] +impl polkadot_runtime_common::xcm_sender::EnsureForParachain for Pallet { + fn ensure(para_id: ParaId) { + if let ChannelStatus::Closed = Self::get_channel_status(para_id) { + Self::open_outbound_hrmp_channel_for_benchmarks_or_tests(para_id) + } + } +} + /// Something that can check the inherents of a block. #[cfg_attr( feature = "parameterized-consensus-hook", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index 7e550c32917..81bb66a56a2 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -1355,8 +1355,31 @@ impl_runtime_apis! { Config as XcmBridgeHubRouterConfig, }; + parameter_types! { + pub ExistentialDepositAsset: Option = Some(( + TokenLocation::get(), + ExistentialDeposit::get() + ).into()); + pub const RandomParaId: ParaId = ParaId::new(43211234); + } + use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = ( + cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >, + polkadot_runtime_common::xcm_sender::ToParachainDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + PriceForSiblingParachainDelivery, + RandomParaId, + ParachainSystem, + > + ); + fn reachable_dest() -> Option { Some(Parent.into()) } @@ -1365,7 +1388,7 @@ impl_runtime_apis! { // Relay/native token can be teleported between AH and Relay. Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Parent.into()) }, Parent.into(), @@ -1373,17 +1396,13 @@ impl_runtime_apis! { } fn reserve_transferable_asset_and_dest() -> Option<(Asset, Location)> { - // AH can reserve transfer native token to some random parachain. - let random_para_id = 43211234; - ParachainSystem::open_outbound_hrmp_channel_for_benchmarks_or_tests( - random_para_id.into() - ); Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Parent.into()) }, - ParentThen(Parachain(random_para_id).into()).into(), + // AH can reserve transfer native token to some random parachain. + ParentThen(Parachain(RandomParaId::get().into()).into()).into(), )) } @@ -1469,13 +1488,6 @@ impl_runtime_apis! { use xcm_config::{TokenLocation, MaxAssetsIntoHolding}; use pallet_xcm_benchmarks::asset_instance_from; - parameter_types! { - pub ExistentialDepositAsset: Option = Some(( - TokenLocation::get(), - ExistentialDeposit::get() - ).into()); - } - impl pallet_xcm_benchmarks::Config for Runtime { type XcmConfig = xcm_config::XcmConfig; type AccountIdConverter = xcm_config::LocationToAccountId; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index a3166106073..3f58c679eee 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -1427,8 +1427,31 @@ impl_runtime_apis! { use cumulus_pallet_session_benchmarking::Pallet as SessionBench; impl cumulus_pallet_session_benchmarking::Config for Runtime {} + parameter_types! { + pub ExistentialDepositAsset: Option = Some(( + WestendLocation::get(), + ExistentialDeposit::get() + ).into()); + pub const RandomParaId: ParaId = ParaId::new(43211234); + } + use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = ( + cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >, + polkadot_runtime_common::xcm_sender::ToParachainDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + PriceForSiblingParachainDelivery, + RandomParaId, + ParachainSystem, + > + ); + fn reachable_dest() -> Option { Some(Parent.into()) } @@ -1437,7 +1460,7 @@ impl_runtime_apis! { // Relay/native token can be teleported between AH and Relay. Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Parent.into()) }, Parent.into(), @@ -1445,17 +1468,13 @@ impl_runtime_apis! { } fn reserve_transferable_asset_and_dest() -> Option<(Asset, Location)> { - // AH can reserve transfer native token to some random parachain. - let random_para_id = 43211234; - ParachainSystem::open_outbound_hrmp_channel_for_benchmarks_or_tests( - random_para_id.into() - ); Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Parent.into()) }, - ParentThen(Parachain(random_para_id).into()).into(), + // AH can reserve transfer native token to some random parachain. + ParentThen(Parachain(RandomParaId::get().into()).into()).into(), )) } @@ -1546,13 +1565,6 @@ impl_runtime_apis! { use xcm_config::{MaxAssetsIntoHolding, WestendLocation}; use pallet_xcm_benchmarks::asset_instance_from; - parameter_types! { - pub ExistentialDepositAsset: Option = Some(( - WestendLocation::get(), - ExistentialDeposit::get() - ).into()); - } - impl pallet_xcm_benchmarks::Config for Runtime { type XcmConfig = xcm_config::XcmConfig; type AccountIdConverter = xcm_config::LocationToAccountId; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index ae50d2a93cb..15cf045a995 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -1109,6 +1109,12 @@ impl_runtime_apis! { use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + fn reachable_dest() -> Option { Some(Parent.into()) } @@ -1117,7 +1123,7 @@ impl_runtime_apis! { // Relay/native token can be teleported between BH and Relay. Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Parent.into()) }, Parent.into(), diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 997320ffcf2..bd42a33370d 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -806,6 +806,12 @@ impl_runtime_apis! { use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + fn reachable_dest() -> Option { Some(Parent.into()) } @@ -814,7 +820,7 @@ impl_runtime_apis! { // Relay/native token can be teleported between BH and Relay. Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Parent.into()) }, Parent.into(), diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs index 4299e8bb3ec..36eb0e07f87 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs @@ -986,8 +986,21 @@ impl_runtime_apis! { use cumulus_pallet_session_benchmarking::Pallet as SessionBench; impl cumulus_pallet_session_benchmarking::Config for Runtime {} + parameter_types! { + pub ExistentialDepositAsset: Option = Some(( + xcm_config::WndLocation::get(), + ExistentialDeposit::get() + ).into()); + } + use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + fn reachable_dest() -> Option { Some(Parent.into()) } @@ -996,7 +1009,7 @@ impl_runtime_apis! { // Relay/native token can be teleported between Collectives and Relay. Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Parent.into()) }.into(), Parent.into(), diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs index 94cb0e502e4..541978098ca 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs @@ -50,7 +50,7 @@ use frame_support::{ dispatch::DispatchClass, genesis_builder_helper::{build_config, create_default_config}, parameter_types, - traits::{ConstBool, ConstU128, ConstU16, ConstU32, ConstU64, ConstU8}, + traits::{ConstBool, ConstU16, ConstU32, ConstU64, ConstU8}, weights::{ConstantMultiplier, Weight}, PalletId, }; @@ -205,6 +205,10 @@ impl pallet_authorship::Config for Runtime { type EventHandler = (CollatorSelection,); } +parameter_types! { + pub const ExistentialDeposit: Balance = EXISTENTIAL_DEPOSIT; +} + impl pallet_balances::Config for Runtime { type MaxLocks = ConstU32<50>; /// The type for recording an account's balance. @@ -212,7 +216,7 @@ impl pallet_balances::Config for Runtime { /// The ubiquitous event type. type RuntimeEvent = RuntimeEvent; type DustRemoval = (); - type ExistentialDeposit = ConstU128; + type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; type WeightInfo = pallet_balances::weights::SubstrateWeight; type MaxReserves = ConstU32<50>; @@ -713,9 +717,22 @@ impl_runtime_apis! { use cumulus_pallet_session_benchmarking::Pallet as SessionBench; impl cumulus_pallet_session_benchmarking::Config for Runtime {} + parameter_types! { + pub ExistentialDepositAsset: Option = Some(( + xcm_config::RelayLocation::get(), + ExistentialDeposit::get() + ).into()); + } + use xcm::latest::prelude::*; use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + fn reachable_dest() -> Option { Some(Parent.into()) } @@ -724,7 +741,7 @@ impl_runtime_apis! { // Relay/native token can be teleported between Contracts-System-Para and Relay. Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Parent.into()) }, Parent.into(), diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs index f07bac8b2ef..fcd7576b1e1 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs @@ -715,6 +715,12 @@ impl_runtime_apis! { use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + fn reachable_dest() -> Option { Some(Parent.into()) } @@ -723,7 +729,7 @@ impl_runtime_apis! { // Relay/native token can be teleported between AH and Relay. Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Parent.into()) }, Parent.into(), diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs index 85d70d2f17b..80eb7863803 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs @@ -706,6 +706,12 @@ impl_runtime_apis! { use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + fn reachable_dest() -> Option { Some(Parent.into()) } @@ -714,7 +720,7 @@ impl_runtime_apis! { // Relay/native token can be teleported between AH and Relay. Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Parent.into()) }, Parent.into(), diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs index 570dc0fa12c..90c39891769 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -686,6 +686,12 @@ impl_runtime_apis! { use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsiscsBenchmark; impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + fn reachable_dest() -> Option { Some(Parent.into()) } @@ -694,7 +700,7 @@ impl_runtime_apis! { // Relay/native token can be teleported between People and Relay. Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Parent.into()) }, Parent.into(), diff --git a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs index a47df66f503..a904f7c3521 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs @@ -686,6 +686,12 @@ impl_runtime_apis! { use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsiscsBenchmark; impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + fn reachable_dest() -> Option { Some(Parent.into()) } @@ -694,7 +700,7 @@ impl_runtime_apis! { // Relay/native token can be teleported between People and Relay. Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Parent.into()) }, Parent.into(), diff --git a/cumulus/primitives/utility/Cargo.toml b/cumulus/primitives/utility/Cargo.toml index 45c0e667094..1e2c300b9ba 100644 --- a/cumulus/primitives/utility/Cargo.toml +++ b/cumulus/primitives/utility/Cargo.toml @@ -26,7 +26,6 @@ polkadot-runtime-parachains = { path = "../../../polkadot/runtime/parachains", d xcm = { package = "staging-xcm", path = "../../../polkadot/xcm", default-features = false } xcm-executor = { package = "staging-xcm-executor", path = "../../../polkadot/xcm/xcm-executor", default-features = false } xcm-builder = { package = "staging-xcm-builder", path = "../../../polkadot/xcm/xcm-builder", default-features = false } -pallet-xcm-benchmarks = { path = "../../../polkadot/xcm/pallet-xcm-benchmarks", default-features = false } # Cumulus cumulus-primitives-core = { path = "../core", default-features = false } @@ -39,7 +38,6 @@ std = [ "frame-support/std", "log/std", "pallet-asset-conversion/std", - "pallet-xcm-benchmarks/std", "polkadot-runtime-common/std", "polkadot-runtime-parachains/std", "sp-io/std", @@ -54,7 +52,6 @@ runtime-benchmarks = [ "cumulus-primitives-core/runtime-benchmarks", "frame-support/runtime-benchmarks", "pallet-asset-conversion/runtime-benchmarks", - "pallet-xcm-benchmarks/runtime-benchmarks", "polkadot-runtime-common/runtime-benchmarks", "polkadot-runtime-parachains/runtime-benchmarks", "sp-runtime/runtime-benchmarks", diff --git a/cumulus/primitives/utility/src/lib.rs b/cumulus/primitives/utility/src/lib.rs index 0d892122742..abc391bdcb8 100644 --- a/cumulus/primitives/utility/src/lib.rs +++ b/cumulus/primitives/utility/src/lib.rs @@ -760,7 +760,7 @@ mod test_trader { } } -/// Implementation of `pallet_xcm_benchmarks::EnsureDelivery` which helps to ensure delivery to the +/// Implementation of `xcm_builder::EnsureDelivery` which helps to ensure delivery to the /// parent relay chain. Deposits existential deposit for origin (if needed). /// Deposits estimated fee to the origin account (if needed). /// Allows to trigger additional logic for specific `ParaId` (e.g. open HRMP channel) (if neeeded). @@ -774,17 +774,22 @@ impl< XcmConfig: xcm_executor::Config, ExistentialDeposit: Get>, PriceForDelivery: PriceForMessageDelivery, - > pallet_xcm_benchmarks::EnsureDelivery + > xcm_builder::EnsureDelivery for ToParentDeliveryHelper { fn ensure_successful_delivery( origin_ref: &Location, - _dest: &Location, + dest: &Location, fee_reason: xcm_executor::traits::FeeReason, ) -> (Option, Option) { use xcm::latest::{MAX_INSTRUCTIONS_TO_DECODE, MAX_ITEMS_IN_ASSETS}; use xcm_executor::{traits::FeeManager, FeesMode}; + // check if the destination is relay/parent + if dest.ne(&Location::parent()) { + return (None, None); + } + let mut fees_mode = None; if !XcmConfig::FeeManager::is_waived(Some(origin_ref), fee_reason) { // if not waived, we need to set up accounts for paying and receiving fees diff --git a/polkadot/runtime/common/Cargo.toml b/polkadot/runtime/common/Cargo.toml index 2467d123f02..eae5d4fb2ef 100644 --- a/polkadot/runtime/common/Cargo.toml +++ b/polkadot/runtime/common/Cargo.toml @@ -58,8 +58,6 @@ runtime-parachains = { package = "polkadot-runtime-parachains", path = "../parac slot-range-helper = { path = "slot_range_helper", default-features = false } xcm = { package = "staging-xcm", path = "../../xcm", default-features = false } xcm-executor = { package = "staging-xcm-executor", path = "../../xcm/xcm-executor", default-features = false, optional = true } - -pallet-xcm-benchmarks = { path = "../../xcm/pallet-xcm-benchmarks", default-features = false, optional = true } xcm-builder = { package = "staging-xcm-builder", path = "../../xcm/xcm-builder", default-features = false } [dev-dependencies] @@ -99,7 +97,6 @@ std = [ "pallet-transaction-payment/std", "pallet-treasury/std", "pallet-vesting/std", - "pallet-xcm-benchmarks/std", "parity-scale-codec/std", "primitives/std", "runtime-parachains/std", @@ -137,7 +134,6 @@ runtime-benchmarks = [ "pallet-timestamp/runtime-benchmarks", "pallet-treasury/runtime-benchmarks", "pallet-vesting/runtime-benchmarks", - "pallet-xcm-benchmarks/runtime-benchmarks", "primitives/runtime-benchmarks", "runtime-parachains/runtime-benchmarks", "sp-runtime/runtime-benchmarks", diff --git a/polkadot/runtime/common/src/xcm_sender.rs b/polkadot/runtime/common/src/xcm_sender.rs index 7f1100a1361..46d09afcfb2 100644 --- a/polkadot/runtime/common/src/xcm_sender.rs +++ b/polkadot/runtime/common/src/xcm_sender.rs @@ -136,7 +136,7 @@ where } } -/// Implementation of `pallet_xcm_benchmarks::EnsureDelivery` which helps to ensure delivery to the +/// Implementation of `xcm_builder::EnsureDelivery` which helps to ensure delivery to the /// `ParaId` parachain (sibling or child). Deposits existential deposit for origin (if needed). /// Deposits estimated fee to the origin account (if needed). /// Allows to trigger additional logic for specific `ParaId` (e.g. open HRMP channel) (if neeeded). @@ -164,7 +164,7 @@ impl< PriceForDelivery: PriceForMessageDelivery, Parachain: Get, ToParachainHelper: EnsureForParachain, - > pallet_xcm_benchmarks::EnsureDelivery + > xcm_builder::EnsureDelivery for ToParachainDeliveryHelper< XcmConfig, ExistentialDeposit, @@ -175,7 +175,7 @@ impl< { fn ensure_successful_delivery( origin_ref: &Location, - _dest: &Location, + dest: &Location, fee_reason: xcm_executor::traits::FeeReason, ) -> (Option, Option) { use xcm_executor::{ @@ -183,6 +183,15 @@ impl< FeesMode, }; + // check if the destination matches the expected `Parachain`. + if let Some(Parachain(para_id)) = dest.first_interior() { + if ParaId::from(*para_id) != Parachain::get().into() { + return (None, None) + } + } else { + return (None, None) + } + let mut fees_mode = None; if !XcmConfig::FeeManager::is_waived(Some(origin_ref), fee_reason) { // if not waived, we need to set up accounts for paying and receiving fees diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 1566911fc22..bbc79a13d37 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -2272,12 +2272,30 @@ sp_api::impl_runtime_apis! { TokenLocation::get(), ExistentialDeposit::get() ).into()); - pub ToParachain: ParaId = rococo_runtime_constants::system_parachain::ASSET_HUB_ID.into(); + pub AssetHubParaId: ParaId = rococo_runtime_constants::system_parachain::ASSET_HUB_ID.into(); + pub const RandomParaId: ParaId = ParaId::new(43211234); } impl frame_system_benchmarking::Config for Runtime {} impl frame_benchmarking::baseline::Config for Runtime {} impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = ( + runtime_common::xcm_sender::ToParachainDeliveryHelper< + XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForChildParachainDelivery, + AssetHubParaId, + (), + >, + runtime_common::xcm_sender::ToParachainDeliveryHelper< + XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForChildParachainDelivery, + RandomParaId, + (), + > + ); + fn reachable_dest() -> Option { Some(crate::xcm_config::AssetHub::get()) } @@ -2286,7 +2304,7 @@ sp_api::impl_runtime_apis! { // Relay/native token can be teleported to/from AH. Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Here.into()) }, crate::xcm_config::AssetHub::get(), @@ -2297,10 +2315,10 @@ sp_api::impl_runtime_apis! { // Relay can reserve transfer native token to some random parachain. Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Here.into()) }, - Parachain(43211234).into(), + Parachain(RandomParaId::get().into()).into(), )) } @@ -2325,7 +2343,7 @@ sp_api::impl_runtime_apis! { XcmConfig, ExistentialDepositAsset, xcm_config::PriceForChildParachainDelivery, - ToParachain, + AssetHubParaId, (), >; fn valid_destination() -> Result { diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index b0b70bff8de..574710b3110 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -2344,7 +2344,36 @@ sp_api::impl_runtime_apis! { impl pallet_session_benchmarking::Config for Runtime {} impl pallet_offences_benchmarking::Config for Runtime {} impl pallet_election_provider_support_benchmarking::Config for Runtime {} + + use xcm_config::{AssetHub, TokenLocation}; + + parameter_types! { + pub ExistentialDepositAsset: Option = Some(( + TokenLocation::get(), + ExistentialDeposit::get() + ).into()); + pub AssetHubParaId: ParaId = westend_runtime_constants::system_parachain::ASSET_HUB_ID.into(); + pub const RandomParaId: ParaId = ParaId::new(43211234); + } + impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = ( + runtime_common::xcm_sender::ToParachainDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForChildParachainDelivery, + AssetHubParaId, + (), + >, + runtime_common::xcm_sender::ToParachainDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForChildParachainDelivery, + RandomParaId, + (), + > + ); + fn reachable_dest() -> Option { Some(crate::xcm_config::AssetHub::get()) } @@ -2352,7 +2381,7 @@ sp_api::impl_runtime_apis! { fn teleportable_asset_and_dest() -> Option<(Asset, Location)> { // Relay/native token can be teleported to/from AH. Some(( - Asset { fun: Fungible(EXISTENTIAL_DEPOSIT), id: AssetId(Here.into()) }, + Asset { fun: Fungible(ExistentialDeposit::get()), id: AssetId(Here.into()) }, crate::xcm_config::AssetHub::get(), )) } @@ -2361,10 +2390,10 @@ sp_api::impl_runtime_apis! { // Relay can reserve transfer native token to some random parachain. Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Here.into()) }, - crate::Junction::Parachain(43211234).into(), + crate::Junction::Parachain(RandomParaId::get().into()).into(), )) } @@ -2391,15 +2420,6 @@ sp_api::impl_runtime_apis! { AssetId, Fungibility::*, InteriorLocation, Junction, Junctions::*, Asset, Assets, Location, NetworkId, Response, }; - use xcm_config::{AssetHub, TokenLocation}; - - parameter_types! { - pub ExistentialDepositAsset: Option = Some(( - TokenLocation::get(), - ExistentialDeposit::get() - ).into()); - pub ToParachain: ParaId = westend_runtime_constants::system_parachain::ASSET_HUB_ID.into(); - } impl pallet_xcm_benchmarks::Config for Runtime { type XcmConfig = xcm_config::XcmConfig; @@ -2408,7 +2428,7 @@ sp_api::impl_runtime_apis! { xcm_config::XcmConfig, ExistentialDepositAsset, xcm_config::PriceForChildParachainDelivery, - ToParachain, + AssetHubParaId, (), >; fn valid_destination() -> Result { diff --git a/polkadot/runtime/westend/src/xcm_config.rs b/polkadot/runtime/westend/src/xcm_config.rs index 06124d896fb..66c56171af0 100644 --- a/polkadot/runtime/westend/src/xcm_config.rs +++ b/polkadot/runtime/westend/src/xcm_config.rs @@ -262,7 +262,7 @@ pub type LocalPalletOriginToLocation = ( StakingAdminToPlurality, // FellowshipAdmin origin to be used in XCM as a corresponding Plurality `Location` value. FellowshipAdminToPlurality, - // `Treasurer` origin to be used in XCM as a corresponding Plurality `MultiLocation` value. + // `Treasurer` origin to be used in XCM as a corresponding Plurality `Location` value. TreasurerToPlurality, ); diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs index 6ce8d3e99e8..63ed0ac0ca7 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs @@ -22,10 +22,8 @@ use codec::Encode; use frame_benchmarking::{account, BenchmarkError}; use sp_std::prelude::*; use xcm::latest::prelude::*; -use xcm_executor::{ - traits::{ConvertLocation, FeeReason}, - Config as XcmConfig, FeesMode, -}; +use xcm_builder::EnsureDelivery; +use xcm_executor::{traits::ConvertLocation, Config as XcmConfig}; pub mod fungible; pub mod generic; @@ -114,29 +112,3 @@ pub fn account_and_location(index: u32) -> (T::AccountId, Location) { (account, location) } - -/// Trait for a type which ensures all requirements for successful delivery with XCM transport -/// layers. -pub trait EnsureDelivery { - /// Prepare all requirements for successful `XcmSender: SendXcm` passing (accounts, balances, - /// channels ...). Returns: - /// - possible `FeesMode` which is expected to be set to executor - /// - possible `Assets` which are expected to be subsume to the Holding Register - fn ensure_successful_delivery( - origin_ref: &Location, - dest: &Location, - fee_reason: FeeReason, - ) -> (Option, Option); -} - -/// `()` implementation does nothing which means no special requirements for environment. -impl EnsureDelivery for () { - fn ensure_successful_delivery( - _origin_ref: &Location, - _dest: &Location, - _fee_reason: FeeReason, - ) -> (Option, Option) { - // doing nothing - (None, None) - } -} diff --git a/polkadot/xcm/pallet-xcm/src/benchmarking.rs b/polkadot/xcm/pallet-xcm/src/benchmarking.rs index c7d8fb24e9d..e3ea2fb8c06 100644 --- a/polkadot/xcm/pallet-xcm/src/benchmarking.rs +++ b/polkadot/xcm/pallet-xcm/src/benchmarking.rs @@ -17,21 +17,26 @@ use super::*; use bounded_collections::{ConstU32, WeakBoundedVec}; use frame_benchmarking::{benchmarks, whitelisted_caller, BenchmarkError, BenchmarkResult}; -use frame_support::{traits::Currency, weights::Weight}; +use frame_support::{ + traits::fungible::{Inspect, Mutate}, + weights::Weight, +}; use frame_system::RawOrigin; use sp_std::prelude::*; use xcm::{latest::prelude::*, v2}; +use xcm_builder::EnsureDelivery; +use xcm_executor::traits::FeeReason; type RuntimeOrigin = ::RuntimeOrigin; -// existential deposit multiplier -const ED_MULTIPLIER: u32 = 100; - /// Pallet we're benchmarking here. pub struct Pallet(crate::Pallet); /// Trait that must be implemented by runtime to be able to benchmark pallet properly. pub trait Config: crate::Config { + /// Helper that ensures successful delivery for extrinsics/benchmarks which need `SendXcm`. + type DeliveryHelper: EnsureDelivery; + /// A `Location` that can be reached via `XcmRouter`. Used only in benchmarks. /// /// If `None`, the benchmarks that depend on a reachable destination will be skipped. @@ -107,23 +112,29 @@ benchmarks! { }.into(); let assets: Assets = asset.into(); - let existential_deposit = T::ExistentialDeposit::get(); - let caller = whitelisted_caller(); - - // Give some multiple of the existential deposit - let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); - assert!(balance >= transferred_amount); - let _ = as Currency<_>>::make_free_balance_be(&caller, balance); - // verify initial balance - assert_eq!(pallet_balances::Pallet::::free_balance(&caller), balance); - + let caller: T::AccountId = whitelisted_caller(); let send_origin = RawOrigin::Signed(caller.clone()); let origin_location = T::ExecuteXcmOrigin::try_origin(send_origin.clone().into()) .map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; - if !T::XcmTeleportFilter::contains(&(origin_location, assets.clone().into_inner())) { + if !T::XcmTeleportFilter::contains(&(origin_location.clone(), assets.clone().into_inner())) { return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))) } + // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) + let (_, _) = T::DeliveryHelper::ensure_successful_delivery( + &origin_location, + &destination, + FeeReason::ChargeFees, + ); + + // Actual balance (e.g. `ensure_successful_delivery` could drip delivery fees, ...) + let balance = as Inspect<_>>::balance(&caller); + // Add transferred_amount to origin + as Mutate<_>>::mint_into(&caller, transferred_amount)?; + // verify initial balance + let balance = balance + transferred_amount; + assert_eq!( as Inspect<_>>::balance(&caller), balance); + let recipient = [0u8; 32]; let versioned_dest: VersionedLocation = destination.into(); let versioned_beneficiary: VersionedLocation = @@ -132,7 +143,7 @@ benchmarks! { }: _>(send_origin.into(), Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0) verify { // verify balance after transfer, decreased by transferred amount (+ maybe XCM delivery fees) - assert!(pallet_balances::Pallet::::free_balance(&caller) <= balance - transferred_amount); + assert!( as Inspect<_>>::balance(&caller) <= balance - transferred_amount); } reserve_transfer_assets { @@ -146,23 +157,29 @@ benchmarks! { }.into(); let assets: Assets = asset.into(); - let existential_deposit = T::ExistentialDeposit::get(); - let caller = whitelisted_caller(); - - // Give some multiple of the existential deposit - let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); - assert!(balance >= transferred_amount); - let _ = as Currency<_>>::make_free_balance_be(&caller, balance); - // verify initial balance - assert_eq!(pallet_balances::Pallet::::free_balance(&caller), balance); - + let caller: T::AccountId = whitelisted_caller(); let send_origin = RawOrigin::Signed(caller.clone()); let origin_location = T::ExecuteXcmOrigin::try_origin(send_origin.clone().into()) .map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; - if !T::XcmReserveTransferFilter::contains(&(origin_location, assets.clone().into_inner())) { + if !T::XcmReserveTransferFilter::contains(&(origin_location.clone(), assets.clone().into_inner())) { return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))) } + // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) + let (_, _) = T::DeliveryHelper::ensure_successful_delivery( + &origin_location, + &destination, + FeeReason::ChargeFees, + ); + + // Actual balance (e.g. `ensure_successful_delivery` could drip delivery fees, ...) + let balance = as Inspect<_>>::balance(&caller); + // Add transferred_amount to origin + as Mutate<_>>::mint_into(&caller, transferred_amount)?; + // verify initial balance + let balance = balance + transferred_amount; + assert_eq!( as Inspect<_>>::balance(&caller), balance); + let recipient = [0u8; 32]; let versioned_dest: VersionedLocation = destination.into(); let versioned_beneficiary: VersionedLocation = @@ -171,7 +188,7 @@ benchmarks! { }: _>(send_origin.into(), Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0) verify { // verify balance after transfer, decreased by transferred amount (+ maybe XCM delivery fees) - assert!(pallet_balances::Pallet::::free_balance(&caller) <= balance - transferred_amount); + assert!( as Inspect<_>>::balance(&caller) <= balance - transferred_amount); } transfer_assets { diff --git a/polkadot/xcm/pallet-xcm/src/mock.rs b/polkadot/xcm/pallet-xcm/src/mock.rs index 004a73ca6ab..17c181387dd 100644 --- a/polkadot/xcm/pallet-xcm/src/mock.rs +++ b/polkadot/xcm/pallet-xcm/src/mock.rs @@ -563,8 +563,30 @@ impl pallet_test_notifier::Config for Test { type RuntimeCall = RuntimeCall; } +#[cfg(feature = "runtime-benchmarks")] +pub struct TestDeliveryHelper; +#[cfg(feature = "runtime-benchmarks")] +impl xcm_builder::EnsureDelivery for TestDeliveryHelper { + fn ensure_successful_delivery( + origin_ref: &Location, + _dest: &Location, + _fee_reason: xcm_executor::traits::FeeReason, + ) -> (Option, Option) { + use xcm_executor::traits::ConvertLocation; + let account = SovereignAccountOf::convert_location(origin_ref).expect("Valid location"); + // Give the existential deposit at least + let balance = ExistentialDeposit::get(); + let _ = >::make_free_balance_be( + &account, balance, + ); + (None, None) + } +} + #[cfg(feature = "runtime-benchmarks")] impl super::benchmarking::Config for Test { + type DeliveryHelper = TestDeliveryHelper; + fn reachable_dest() -> Option { Some(Parachain(1000).into()) } diff --git a/polkadot/xcm/xcm-builder/src/currency_adapter.rs b/polkadot/xcm/xcm-builder/src/currency_adapter.rs index fe26b7319bb..24261ac0658 100644 --- a/polkadot/xcm/xcm-builder/src/currency_adapter.rs +++ b/polkadot/xcm/xcm-builder/src/currency_adapter.rs @@ -170,7 +170,7 @@ impl< } fn can_check_out(_dest: &Location, what: &Asset, _context: &XcmContext) -> Result { - log::trace!(target: "xcm::currency_adapter", "check_out dest: {:?}, what: {:?}", _dest, what); + log::trace!(target: "xcm::currency_adapter", "can_check_out dest: {:?}, what: {:?}", _dest, what); let amount = Matcher::matches_fungible(what).ok_or(Error::AssetNotHandled)?; match CheckedAccount::get() { Some((checked_account, MintLocation::Local)) => diff --git a/polkadot/xcm/xcm-builder/src/fungible_adapter.rs b/polkadot/xcm/xcm-builder/src/fungible_adapter.rs index 33b766c8560..21c828922b3 100644 --- a/polkadot/xcm/xcm-builder/src/fungible_adapter.rs +++ b/polkadot/xcm/xcm-builder/src/fungible_adapter.rs @@ -151,7 +151,7 @@ impl< fn can_check_out(_dest: &Location, what: &Asset, _context: &XcmContext) -> XcmResult { log::trace!( target: "xcm::fungible_adapter", - "check_out dest: {:?}, what: {:?}", + "can_check_out dest: {:?}, what: {:?}", _dest, what ); diff --git a/polkadot/xcm/xcm-builder/src/fungibles_adapter.rs b/polkadot/xcm/xcm-builder/src/fungibles_adapter.rs index 4574d5ed4c6..b4c418ebf1c 100644 --- a/polkadot/xcm/xcm-builder/src/fungibles_adapter.rs +++ b/polkadot/xcm/xcm-builder/src/fungibles_adapter.rs @@ -235,7 +235,7 @@ impl< fn can_check_out(_origin: &Location, what: &Asset, _context: &XcmContext) -> XcmResult { log::trace!( target: "xcm::fungibles_adapter", - "can_check_in origin: {:?}, what: {:?}", + "can_check_out origin: {:?}, what: {:?}", _origin, what ); // Check we handle this asset. diff --git a/polkadot/xcm/xcm-builder/src/lib.rs b/polkadot/xcm/xcm-builder/src/lib.rs index 42522c64d8a..e2af8187136 100644 --- a/polkadot/xcm/xcm-builder/src/lib.rs +++ b/polkadot/xcm/xcm-builder/src/lib.rs @@ -117,7 +117,7 @@ mod process_xcm_message; pub use process_xcm_message::ProcessXcmMessage; mod routing; -pub use routing::{WithTopicSource, WithUniqueTopic}; +pub use routing::{EnsureDelivery, WithTopicSource, WithUniqueTopic}; mod transactional; pub use transactional::FrameTransactionalProcessor; diff --git a/polkadot/xcm/xcm-builder/src/routing.rs b/polkadot/xcm/xcm-builder/src/routing.rs index 9c0302baee0..529ef80c15f 100644 --- a/polkadot/xcm/xcm-builder/src/routing.rs +++ b/polkadot/xcm/xcm-builder/src/routing.rs @@ -20,6 +20,7 @@ use frame_system::unique; use parity_scale_codec::Encode; use sp_std::{marker::PhantomData, result::Result}; use xcm::prelude::*; +use xcm_executor::{traits::FeeReason, FeesMode}; /// Wrapper router which, if the message does not already end with a `SetTopic` instruction, /// appends one to the message filled with a universally unique ID. This ID is returned from a @@ -104,3 +105,37 @@ impl SendXcm for WithTopicSource (Option, Option); +} + +/// Tuple implementation for `EnsureDelivery`. +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl EnsureDelivery for Tuple { + fn ensure_successful_delivery( + origin_ref: &Location, + dest: &Location, + fee_reason: FeeReason, + ) -> (Option, Option) { + for_tuples!( #( + // If the implementation returns something, we're done; if not, let others try. + match Tuple::ensure_successful_delivery(origin_ref, dest, fee_reason.clone()) { + r @ (Some(_), Some(_)) | r @ (Some(_), None) | r @ (None, Some(_)) => return r, + (None, None) => (), + } + )* ); + // doing nothing + (None, None) + } +} -- GitLab From 49266f102e5b5c07fbc3ef45db29c2fd053a0555 Mon Sep 17 00:00:00 2001 From: brenzi Date: Mon, 26 Feb 2024 09:15:44 +0100 Subject: [PATCH 127/133] add Encointer as trusted teleporter for Westend (#3411) with the deprecation of Rococo, Encointer needs a new staging environment. Paseo will be Polkadot-focused and westend Kusama-focused, so we propose to use Westend --- polkadot/runtime/westend/constants/src/lib.rs | 2 ++ polkadot/runtime/westend/src/xcm_config.rs | 3 +++ prdoc/pr_3411.prdoc | 11 +++++++++++ 3 files changed, 16 insertions(+) create mode 100644 prdoc/pr_3411.prdoc diff --git a/polkadot/runtime/westend/constants/src/lib.rs b/polkadot/runtime/westend/constants/src/lib.rs index 848cccd559d..3bc27177d7a 100644 --- a/polkadot/runtime/westend/constants/src/lib.rs +++ b/polkadot/runtime/westend/constants/src/lib.rs @@ -107,6 +107,8 @@ pub mod system_parachain { pub const COLLECTIVES_ID: u32 = 1001; /// BridgeHub parachain ID. pub const BRIDGE_HUB_ID: u32 = 1002; + /// Encointer parachain ID. + pub const ENCOINTER_ID: u32 = 1003; /// People Chain parachain ID. pub const PEOPLE_ID: u32 = 1004; /// Brokerage parachain ID. diff --git a/polkadot/runtime/westend/src/xcm_config.rs b/polkadot/runtime/westend/src/xcm_config.rs index 66c56171af0..c3aa75a86a4 100644 --- a/polkadot/runtime/westend/src/xcm_config.rs +++ b/polkadot/runtime/westend/src/xcm_config.rs @@ -114,12 +114,14 @@ parameter_types! { pub AssetHub: Location = Parachain(ASSET_HUB_ID).into_location(); pub Collectives: Location = Parachain(COLLECTIVES_ID).into_location(); pub BridgeHub: Location = Parachain(BRIDGE_HUB_ID).into_location(); + pub Encointer: Location = Parachain(ENCOINTER_ID).into_location(); pub People: Location = Parachain(PEOPLE_ID).into_location(); pub Broker: Location = Parachain(BROKER_ID).into_location(); pub Wnd: AssetFilter = Wild(AllOf { fun: WildFungible, id: AssetId(TokenLocation::get()) }); pub WndForAssetHub: (AssetFilter, Location) = (Wnd::get(), AssetHub::get()); pub WndForCollectives: (AssetFilter, Location) = (Wnd::get(), Collectives::get()); pub WndForBridgeHub: (AssetFilter, Location) = (Wnd::get(), BridgeHub::get()); + pub WndForEncointer: (AssetFilter, Location) = (Wnd::get(), Encointer::get()); pub WndForPeople: (AssetFilter, Location) = (Wnd::get(), People::get()); pub WndForBroker: (AssetFilter, Location) = (Wnd::get(), Broker::get()); pub MaxInstructions: u32 = 100; @@ -130,6 +132,7 @@ pub type TrustedTeleporters = ( xcm_builder::Case, xcm_builder::Case, xcm_builder::Case, + xcm_builder::Case, xcm_builder::Case, xcm_builder::Case, ); diff --git a/prdoc/pr_3411.prdoc b/prdoc/pr_3411.prdoc new file mode 100644 index 00000000000..d847d6756ac --- /dev/null +++ b/prdoc/pr_3411.prdoc @@ -0,0 +1,11 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: add Encointer as trusted teleporter for Westend + +doc: + - audience: Runtime Dev + description: | + add Encointer as trusted teleporter for Westend with ParaId 1003 + +crates: [ ] -- GitLab From 0893ca1584a41ea632214dbcaf8ae134b990f9e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 26 Feb 2024 11:32:35 +0100 Subject: [PATCH 128/133] frame-support: Improve error reporting when having too many pallets (#3478) Instead of only generating the error, we now generate the actual code and the error. This generates in total less errors and helps the user to identify the actual problem and not being confronted with tons of errors. --- .../procedural/src/construct_runtime/mod.rs | 43 ++++--- ...umber_of_pallets_exceeds_tuple_size.stderr | 106 ++++-------------- 2 files changed, 49 insertions(+), 100 deletions(-) diff --git a/substrate/frame/support/procedural/src/construct_runtime/mod.rs b/substrate/frame/support/procedural/src/construct_runtime/mod.rs index 54ed15f7b1d..6e95fdf116a 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/mod.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/mod.rs @@ -233,25 +233,38 @@ pub fn construct_runtime(input: TokenStream) -> TokenStream { let input_copy = input.clone(); let definition = syn::parse_macro_input!(input as RuntimeDeclaration); - let res = match definition { - RuntimeDeclaration::Implicit(implicit_def) => - check_pallet_number(input_copy.clone().into(), implicit_def.pallets.len()).and_then( - |_| construct_runtime_implicit_to_explicit(input_copy.into(), implicit_def), - ), - RuntimeDeclaration::Explicit(explicit_decl) => check_pallet_number( - input_copy.clone().into(), - explicit_decl.pallets.len(), - ) - .and_then(|_| { - construct_runtime_explicit_to_explicit_expanded(input_copy.into(), explicit_decl) - }), - RuntimeDeclaration::ExplicitExpanded(explicit_decl) => - check_pallet_number(input_copy.into(), explicit_decl.pallets.len()) - .and_then(|_| construct_runtime_final_expansion(explicit_decl)), + let (check_pallet_number_res, res) = match definition { + RuntimeDeclaration::Implicit(implicit_def) => ( + check_pallet_number(input_copy.clone().into(), implicit_def.pallets.len()), + construct_runtime_implicit_to_explicit(input_copy.into(), implicit_def), + ), + RuntimeDeclaration::Explicit(explicit_decl) => ( + check_pallet_number(input_copy.clone().into(), explicit_decl.pallets.len()), + construct_runtime_explicit_to_explicit_expanded(input_copy.into(), explicit_decl), + ), + RuntimeDeclaration::ExplicitExpanded(explicit_decl) => ( + check_pallet_number(input_copy.into(), explicit_decl.pallets.len()), + construct_runtime_final_expansion(explicit_decl), + ), }; let res = res.unwrap_or_else(|e| e.to_compile_error()); + // We want to provide better error messages to the user and thus, handle the error here + // separately. If there is an error, we print the error and still generate all of the code to + // get in overall less errors for the user. + let res = if let Err(error) = check_pallet_number_res { + let error = error.to_compile_error(); + + quote! { + #error + + #res + } + } else { + res + }; + let res = expander::Expander::new("construct_runtime") .dry(std::env::var("EXPAND_MACROS").is_err()) .verbose(true) diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.stderr index 3b6329c650f..6160f8234a3 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.stderr @@ -4,88 +4,24 @@ error: The number of pallets exceeds the maximum number of tuple elements. To in 67 | pub struct Runtime | ^^^ -error[E0412]: cannot find type `RuntimeCall` in this scope - --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:35:64 - | -35 | pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; - | ^^^^^^^^^^^ not found in this scope - | -help: you might be missing a type parameter - | -35 | pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; - | +++++++++++++ - -error[E0412]: cannot find type `Runtime` in this scope - --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:37:25 - | -37 | impl pallet::Config for Runtime {} - | ^^^^^^^ not found in this scope - -error[E0412]: cannot find type `Runtime` in this scope - --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:40:31 - | -40 | impl frame_system::Config for Runtime { - | ^^^^^^^ not found in this scope - -error[E0412]: cannot find type `RuntimeOrigin` in this scope - --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:42:23 - | -42 | type RuntimeOrigin = RuntimeOrigin; - | ^^^^^^^^^^^^^ - | -help: you might have meant to use the associated type - | -42 | type RuntimeOrigin = Self::RuntimeOrigin; - | ++++++ - -error[E0412]: cannot find type `RuntimeCall` in this scope - --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:44:21 - | -44 | type RuntimeCall = RuntimeCall; - | ^^^^^^^^^^^ - | -help: you might have meant to use the associated type - | -44 | type RuntimeCall = Self::RuntimeCall; - | ++++++ - -error[E0412]: cannot find type `RuntimeEvent` in this scope - --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:50:22 - | -50 | type RuntimeEvent = RuntimeEvent; - | ^^^^^^^^^^^^ - | -help: you might have meant to use the associated type - | -50 | type RuntimeEvent = Self::RuntimeEvent; - | ++++++ - -error[E0412]: cannot find type `PalletInfo` in this scope - --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:56:20 - | -56 | type PalletInfo = PalletInfo; - | ^^^^^^^^^^ - | -help: you might have meant to use the associated type - | -56 | type PalletInfo = Self::PalletInfo; - | ++++++ -help: consider importing one of these items - | -18 + use frame_benchmarking::__private::traits::PalletInfo; - | -18 + use frame_support::traits::PalletInfo; - | - -error[E0412]: cannot find type `RuntimeTask` in this scope - --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:39:1 - | -39 | #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this error originates in the macro `frame_system::config_preludes::TestDefaultConfig` which comes from the expansion of the macro `frame_support::macro_magic::forward_tokens_verbatim` (in Nightly builds, run with -Z macro-backtrace for more info) -help: you might have meant to use the associated type - --> $WORKSPACE/substrate/frame/system/src/lib.rs - | - | type Self::RuntimeTask = (); - | ++++++ +error: recursion limit reached while expanding `frame_support::__private::tt_return!` + --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:22:1 + | +22 | / #[frame_support::pallet] +23 | | mod pallet { +24 | | #[pallet::config] +25 | | pub trait Config: frame_system::Config {} +... | +66 | |/ construct_runtime! { +67 | || pub struct Runtime +68 | || { +69 | || System: frame_system::{Pallet, Call, Storage, Config, Event}, +... || +180 | || } +181 | || } + | ||_^ + | |_| + | in this macro invocation + | + = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`$CRATE`) + = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -- GitLab From 6c5a42a690f96d59dbdf94640188f5d5b5cc47e2 Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Mon, 26 Feb 2024 12:45:30 +0100 Subject: [PATCH 129/133] Introduce Notification block pinning limit (#2935) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While investigating some pruning issues I found some room for improvement in the notification pin handling. **Problem:** It was not possible to define an upper limit on notification pins. The block pinning cache has a limit, but only handles bodies and justifications. After this PR, bookkeeping for notifications is managed in the pinning worker. A limit can be defined in the worker. If that limit is crossed, blocks that were pinned for that notification are unpinned, which now affects the state as well as bodies and justifications. The pinned blocks cache still has a limit, but should never be hit. closes #19 --------- Co-authored-by: Bastian Köcher Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> --- Cargo.lock | 1 + substrate/client/api/src/client.rs | 27 +- substrate/client/db/src/lib.rs | 2 +- .../client/db/src/pinned_blocks_cache.rs | 2 +- substrate/client/service/Cargo.toml | 1 + substrate/client/service/src/client/client.rs | 53 +-- substrate/client/service/src/client/mod.rs | 1 + .../src/client/notification_pinning.rs | 353 ++++++++++++++++++ 8 files changed, 406 insertions(+), 34 deletions(-) create mode 100644 substrate/client/service/src/client/notification_pinning.rs diff --git a/Cargo.lock b/Cargo.lock index fae9eacdab9..410b45d0018 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16729,6 +16729,7 @@ dependencies = [ "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", + "schnellru", "serde", "serde_json", "sp-api", diff --git a/substrate/client/api/src/client.rs b/substrate/client/api/src/client.rs index 46232c74539..2de09840e4d 100644 --- a/substrate/client/api/src/client.rs +++ b/substrate/client/api/src/client.rs @@ -278,7 +278,7 @@ impl fmt::Display for UsageInfo { pub struct UnpinHandleInner { /// Hash of the block pinned by this handle hash: Block::Hash, - unpin_worker_sender: TracingUnboundedSender, + unpin_worker_sender: TracingUnboundedSender>, } impl Debug for UnpinHandleInner { @@ -291,7 +291,7 @@ impl UnpinHandleInner { /// Create a new [`UnpinHandleInner`] pub fn new( hash: Block::Hash, - unpin_worker_sender: TracingUnboundedSender, + unpin_worker_sender: TracingUnboundedSender>, ) -> Self { Self { hash, unpin_worker_sender } } @@ -299,12 +299,25 @@ impl UnpinHandleInner { impl Drop for UnpinHandleInner { fn drop(&mut self) { - if let Err(err) = self.unpin_worker_sender.unbounded_send(self.hash) { + if let Err(err) = + self.unpin_worker_sender.unbounded_send(UnpinWorkerMessage::Unpin(self.hash)) + { log::debug!(target: "db", "Unable to unpin block with hash: {}, error: {:?}", self.hash, err); }; } } +/// Message that signals notification-based pinning actions to the pinning-worker. +/// +/// When the notification is dropped, an `Unpin` message should be sent to the worker. +#[derive(Debug)] +pub enum UnpinWorkerMessage { + /// Should be sent when a import or finality notification is created. + AnnouncePin(Block::Hash), + /// Should be sent when a import or finality notification is dropped. + Unpin(Block::Hash), +} + /// Keeps a specific block pinned while the handle is alive. /// Once the last handle instance for a given block is dropped, the /// block is unpinned in the [`Backend`](crate::backend::Backend::unpin_block). @@ -315,7 +328,7 @@ impl UnpinHandle { /// Create a new [`UnpinHandle`] pub fn new( hash: Block::Hash, - unpin_worker_sender: TracingUnboundedSender, + unpin_worker_sender: TracingUnboundedSender>, ) -> UnpinHandle { UnpinHandle(Arc::new(UnpinHandleInner::new(hash, unpin_worker_sender))) } @@ -353,7 +366,7 @@ impl BlockImportNotification { header: Block::Header, is_new_best: bool, tree_route: Option>>, - unpin_worker_sender: TracingUnboundedSender, + unpin_worker_sender: TracingUnboundedSender>, ) -> Self { Self { hash, @@ -412,7 +425,7 @@ impl FinalityNotification { /// Create finality notification from finality summary. pub fn from_summary( mut summary: FinalizeSummary, - unpin_worker_sender: TracingUnboundedSender, + unpin_worker_sender: TracingUnboundedSender>, ) -> FinalityNotification { let hash = summary.finalized.pop().unwrap_or_default(); FinalityNotification { @@ -436,7 +449,7 @@ impl BlockImportNotification { /// Create finality notification from finality summary. pub fn from_summary( summary: ImportSummary, - unpin_worker_sender: TracingUnboundedSender, + unpin_worker_sender: TracingUnboundedSender>, ) -> BlockImportNotification { let hash = summary.hash; BlockImportNotification { diff --git a/substrate/client/db/src/lib.rs b/substrate/client/db/src/lib.rs index 870b4150b9d..0faa90dfc4f 100644 --- a/substrate/client/db/src/lib.rs +++ b/substrate/client/db/src/lib.rs @@ -2531,7 +2531,7 @@ impl sc_client_api::backend::Backend for Backend { self.storage.state_db.pin(&hash, number.saturated_into::(), hint).map_err( |_| { sp_blockchain::Error::UnknownBlock(format!( - "State already discarded for `{:?}`", + "Unable to pin: state already discarded for `{:?}`", hash )) }, diff --git a/substrate/client/db/src/pinned_blocks_cache.rs b/substrate/client/db/src/pinned_blocks_cache.rs index 46c9287fb19..ac4aad07765 100644 --- a/substrate/client/db/src/pinned_blocks_cache.rs +++ b/substrate/client/db/src/pinned_blocks_cache.rs @@ -20,7 +20,7 @@ use schnellru::{Limiter, LruMap}; use sp_runtime::{traits::Block as BlockT, Justifications}; const LOG_TARGET: &str = "db::pin"; -const PINNING_CACHE_SIZE: usize = 1024; +const PINNING_CACHE_SIZE: usize = 2048; /// Entry for pinned blocks cache. struct PinnedBlockCacheEntry { diff --git a/substrate/client/service/Cargo.toml b/substrate/client/service/Cargo.toml index 73edceb2ef3..bbf67d1fbd0 100644 --- a/substrate/client/service/Cargo.toml +++ b/substrate/client/service/Cargo.toml @@ -84,6 +84,7 @@ tokio = { version = "1.22.0", features = ["parking_lot", "rt-multi-thread", "tim tempfile = "3.1.0" directories = "5.0.1" static_init = "1.0.3" +schnellru = "0.2.1" [dev-dependencies] substrate-test-runtime-client = { path = "../../test-utils/runtime/client" } diff --git a/substrate/client/service/src/client/client.rs b/substrate/client/service/src/client/client.rs index 108c258595a..35e8b53a09c 100644 --- a/substrate/client/service/src/client/client.rs +++ b/substrate/client/service/src/client/client.rs @@ -19,8 +19,8 @@ //! Substrate Client use super::block_rules::{BlockRules, LookupResult as BlockLookupResult}; -use futures::{FutureExt, StreamExt}; -use log::{error, info, trace, warn}; +use crate::client::notification_pinning::NotificationPinningWorker; +use log::{debug, info, trace, warn}; use parking_lot::{Mutex, RwLock}; use prometheus_endpoint::Registry; use rand::Rng; @@ -38,7 +38,7 @@ use sc_client_api::{ execution_extensions::ExecutionExtensions, notifications::{StorageEventStream, StorageNotifications}, CallExecutor, ExecutorProvider, KeysIter, OnFinalityAction, OnImportAction, PairsIter, - ProofProvider, UsageProvider, + ProofProvider, UnpinWorkerMessage, UsageProvider, }; use sc_consensus::{ BlockCheckParams, BlockImportParams, ForkChoiceStrategy, ImportResult, StateAction, @@ -114,7 +114,7 @@ where block_rules: BlockRules, config: ClientConfig, telemetry: Option, - unpin_worker_sender: TracingUnboundedSender, + unpin_worker_sender: TracingUnboundedSender>, _phantom: PhantomData, } @@ -326,19 +326,35 @@ where // dropped, the block will be unpinned automatically. if let Some(ref notification) = finality_notification { if let Err(err) = self.backend.pin_block(notification.hash) { - error!( + debug!( "Unable to pin block for finality notification. hash: {}, Error: {}", notification.hash, err ); - }; + } else { + let _ = self + .unpin_worker_sender + .unbounded_send(UnpinWorkerMessage::AnnouncePin(notification.hash)) + .map_err(|e| { + log::error!( + "Unable to send AnnouncePin worker message for finality: {e}" + ) + }); + } } if let Some(ref notification) = import_notification { if let Err(err) = self.backend.pin_block(notification.hash) { - error!( + debug!( "Unable to pin block for import notification. hash: {}, Error: {}", notification.hash, err ); + } else { + let _ = self + .unpin_worker_sender + .unbounded_send(UnpinWorkerMessage::AnnouncePin(notification.hash)) + .map_err(|e| { + log::error!("Unable to send AnnouncePin worker message for import: {e}") + }); }; } @@ -416,25 +432,12 @@ where backend.commit_operation(op)?; } - let (unpin_worker_sender, mut rx) = - tracing_unbounded::("unpin-worker-channel", 10_000); - let task_backend = Arc::downgrade(&backend); - spawn_handle.spawn( - "unpin-worker", - None, - async move { - while let Some(message) = rx.next().await { - if let Some(backend) = task_backend.upgrade() { - backend.unpin_block(message); - } else { - log::debug!("Terminating unpin-worker, backend reference was dropped."); - return - } - } - log::debug!("Terminating unpin-worker, stream terminated.") - } - .boxed(), + let (unpin_worker_sender, rx) = tracing_unbounded::>( + "notification-pinning-worker-channel", + 10_000, ); + let unpin_worker = NotificationPinningWorker::new(rx, backend.clone()); + spawn_handle.spawn("notification-pinning-worker", None, Box::pin(unpin_worker.run())); Ok(Client { backend, diff --git a/substrate/client/service/src/client/mod.rs b/substrate/client/service/src/client/mod.rs index a13fd4317e1..0703cc2b47d 100644 --- a/substrate/client/service/src/client/mod.rs +++ b/substrate/client/service/src/client/mod.rs @@ -47,6 +47,7 @@ mod block_rules; mod call_executor; mod client; +mod notification_pinning; mod wasm_override; mod wasm_substitutes; diff --git a/substrate/client/service/src/client/notification_pinning.rs b/substrate/client/service/src/client/notification_pinning.rs new file mode 100644 index 00000000000..80de91c02f1 --- /dev/null +++ b/substrate/client/service/src/client/notification_pinning.rs @@ -0,0 +1,353 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Notification pinning related logic. +//! +//! This file contains a worker that should be started when a new client instance is created. +//! The goal is to avoid pruning of blocks that have active notifications in the node. Every +//! recipient of notifications should receive the chance to act upon them. In addition, notification +//! listeners can hold onto a [`sc_client_api::UnpinHandle`] to keep a block pinned. Once the handle +//! is dropped, a message is sent and the worker unpins the respective block. +use std::{ + marker::PhantomData, + sync::{Arc, Weak}, +}; + +use futures::StreamExt; +use sc_client_api::{Backend, UnpinWorkerMessage}; + +use sc_utils::mpsc::TracingUnboundedReceiver; +use schnellru::Limiter; +use sp_runtime::traits::Block as BlockT; + +const LOG_TARGET: &str = "db::notification_pinning"; +const NOTIFICATION_PINNING_LIMIT: usize = 1024; + +/// A limiter which automatically unpins blocks that leave the data structure. +#[derive(Clone, Debug)] +struct UnpinningByLengthLimiter> { + max_length: usize, + backend: Weak, + _phantom: PhantomData, +} + +impl> UnpinningByLengthLimiter { + /// Creates a new length limiter with a given `max_length`. + pub fn new(max_length: usize, backend: Weak) -> UnpinningByLengthLimiter { + UnpinningByLengthLimiter { max_length, backend, _phantom: PhantomData::::default() } + } +} + +impl> Limiter + for UnpinningByLengthLimiter +{ + type KeyToInsert<'a> = Block::Hash; + type LinkType = usize; + + fn is_over_the_limit(&self, length: usize) -> bool { + length > self.max_length + } + + fn on_insert( + &mut self, + _length: usize, + key: Self::KeyToInsert<'_>, + value: u32, + ) -> Option<(Block::Hash, u32)> { + log::debug!(target: LOG_TARGET, "Pinning block based on notification. hash = {key}"); + if self.max_length > 0 { + Some((key, value)) + } else { + None + } + } + + fn on_replace( + &mut self, + _length: usize, + _old_key: &mut Block::Hash, + _new_key: Block::Hash, + _old_value: &mut u32, + _new_value: &mut u32, + ) -> bool { + true + } + + fn on_removed(&mut self, key: &mut Block::Hash, references: &mut u32) { + // If reference count was larger than 0 on removal, + // the item was removed due to capacity limitations. + // Since the cache should be large enough for pinned items, + // we want to know about these evictions. + if *references > 0 { + log::warn!( + target: LOG_TARGET, + "Notification block pinning limit reached. Unpinning block with hash = {key:?}" + ); + if let Some(backend) = self.backend.upgrade() { + (0..*references).for_each(|_| backend.unpin_block(*key)); + } + } else { + log::trace!( + target: LOG_TARGET, + "Unpinned block. hash = {key:?}", + ) + } + } + + fn on_cleared(&mut self) {} + + fn on_grow(&mut self, _new_memory_usage: usize) -> bool { + true + } +} + +/// Worker for the handling of notification pinning. +/// +/// It receives messages from a receiver and pins/unpins based on the incoming messages. +/// All notification related unpinning should go through this worker. If the maximum number of +/// notification pins is reached, the block from the oldest notification is unpinned. +pub struct NotificationPinningWorker> { + unpin_message_rx: TracingUnboundedReceiver>, + task_backend: Weak, + pinned_blocks: schnellru::LruMap>, +} + +impl> NotificationPinningWorker { + /// Creates a new `NotificationPinningWorker`. + pub fn new( + unpin_message_rx: TracingUnboundedReceiver>, + task_backend: Arc, + ) -> Self { + let pinned_blocks = + schnellru::LruMap::>::new( + UnpinningByLengthLimiter::new( + NOTIFICATION_PINNING_LIMIT, + Arc::downgrade(&task_backend), + ), + ); + Self { unpin_message_rx, task_backend: Arc::downgrade(&task_backend), pinned_blocks } + } + + fn handle_announce_message(&mut self, hash: Block::Hash) { + if let Some(entry) = self.pinned_blocks.get_or_insert(hash, Default::default) { + *entry = *entry + 1; + } + } + + fn handle_unpin_message(&mut self, hash: Block::Hash) -> Result<(), ()> { + if let Some(refcount) = self.pinned_blocks.peek_mut(&hash) { + *refcount = *refcount - 1; + if *refcount == 0 { + self.pinned_blocks.remove(&hash); + } + if let Some(backend) = self.task_backend.upgrade() { + log::debug!(target: LOG_TARGET, "Reducing pinning refcount for block hash = {hash:?}"); + backend.unpin_block(hash); + } else { + log::debug!(target: LOG_TARGET, "Terminating unpin-worker, backend reference was dropped."); + return Err(()) + } + } else { + log::debug!(target: LOG_TARGET, "Received unpin message for already unpinned block. hash = {hash:?}"); + } + Ok(()) + } + + /// Start working on the received messages. + /// + /// The worker maintains a map which keeps track of the pinned blocks and their reference count. + /// Depending upon the received message, it acts to pin/unpin the block. + pub async fn run(mut self) { + while let Some(message) = self.unpin_message_rx.next().await { + match message { + UnpinWorkerMessage::AnnouncePin(hash) => self.handle_announce_message(hash), + UnpinWorkerMessage::Unpin(hash) => + if self.handle_unpin_message(hash).is_err() { + return + }, + } + } + log::debug!(target: LOG_TARGET, "Terminating unpin-worker, stream terminated.") + } +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use sc_client_api::{Backend, UnpinWorkerMessage}; + use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver}; + use sp_core::H256; + use sp_runtime::traits::Block as BlockT; + + type Block = substrate_test_runtime_client::runtime::Block; + + use super::{NotificationPinningWorker, UnpinningByLengthLimiter}; + + impl> NotificationPinningWorker { + fn new_with_limit( + unpin_message_rx: TracingUnboundedReceiver>, + task_backend: Arc, + limit: usize, + ) -> Self { + let pinned_blocks = + schnellru::LruMap::>::new( + UnpinningByLengthLimiter::new(limit, Arc::downgrade(&task_backend)), + ); + Self { unpin_message_rx, task_backend: Arc::downgrade(&task_backend), pinned_blocks } + } + + fn lru( + &self, + ) -> &schnellru::LruMap> { + &self.pinned_blocks + } + } + + #[test] + fn pinning_worker_handles_base_case() { + let (_tx, rx) = tracing_unbounded("testing", 1000); + + let backend = Arc::new(sc_client_api::in_mem::Backend::::new()); + + let hash = H256::random(); + + let mut worker = NotificationPinningWorker::new(rx, backend.clone()); + + // Block got pinned and unpin message should unpin in the backend. + let _ = backend.pin_block(hash); + assert_eq!(backend.pin_refs(&hash), Some(1)); + + worker.handle_announce_message(hash); + assert_eq!(worker.lru().len(), 1); + + let _ = worker.handle_unpin_message(hash); + + assert_eq!(backend.pin_refs(&hash), Some(0)); + assert!(worker.lru().is_empty()); + } + + #[test] + fn pinning_worker_handles_multiple_pins() { + let (_tx, rx) = tracing_unbounded("testing", 1000); + + let backend = Arc::new(sc_client_api::in_mem::Backend::::new()); + + let hash = H256::random(); + + let mut worker = NotificationPinningWorker::new(rx, backend.clone()); + // Block got pinned multiple times. + let _ = backend.pin_block(hash); + let _ = backend.pin_block(hash); + let _ = backend.pin_block(hash); + assert_eq!(backend.pin_refs(&hash), Some(3)); + + worker.handle_announce_message(hash); + worker.handle_announce_message(hash); + worker.handle_announce_message(hash); + assert_eq!(worker.lru().len(), 1); + + let _ = worker.handle_unpin_message(hash); + assert_eq!(backend.pin_refs(&hash), Some(2)); + let _ = worker.handle_unpin_message(hash); + assert_eq!(backend.pin_refs(&hash), Some(1)); + let _ = worker.handle_unpin_message(hash); + assert_eq!(backend.pin_refs(&hash), Some(0)); + assert!(worker.lru().is_empty()); + + let _ = worker.handle_unpin_message(hash); + assert_eq!(backend.pin_refs(&hash), Some(0)); + } + + #[test] + fn pinning_worker_handles_too_many_unpins() { + let (_tx, rx) = tracing_unbounded("testing", 1000); + + let backend = Arc::new(sc_client_api::in_mem::Backend::::new()); + + let hash = H256::random(); + let hash2 = H256::random(); + + let mut worker = NotificationPinningWorker::new(rx, backend.clone()); + // Block was announced once but unpinned multiple times. The worker should ignore the + // additional unpins. + let _ = backend.pin_block(hash); + let _ = backend.pin_block(hash); + let _ = backend.pin_block(hash); + assert_eq!(backend.pin_refs(&hash), Some(3)); + + worker.handle_announce_message(hash); + assert_eq!(worker.lru().len(), 1); + + let _ = worker.handle_unpin_message(hash); + assert_eq!(backend.pin_refs(&hash), Some(2)); + let _ = worker.handle_unpin_message(hash); + assert_eq!(backend.pin_refs(&hash), Some(2)); + assert!(worker.lru().is_empty()); + + let _ = worker.handle_unpin_message(hash2); + assert!(worker.lru().is_empty()); + assert_eq!(backend.pin_refs(&hash2), None); + } + + #[test] + fn pinning_worker_should_evict_when_limit_reached() { + let (_tx, rx) = tracing_unbounded("testing", 1000); + + let backend = Arc::new(sc_client_api::in_mem::Backend::::new()); + + let hash1 = H256::random(); + let hash2 = H256::random(); + let hash3 = H256::random(); + let hash4 = H256::random(); + + // Only two items fit into the cache. + let mut worker = NotificationPinningWorker::new_with_limit(rx, backend.clone(), 2); + + // Multiple blocks are announced but the cache size is too small. We expect that blocks + // are evicted by the cache and unpinned in the backend. + let _ = backend.pin_block(hash1); + let _ = backend.pin_block(hash2); + let _ = backend.pin_block(hash3); + assert_eq!(backend.pin_refs(&hash1), Some(1)); + assert_eq!(backend.pin_refs(&hash2), Some(1)); + assert_eq!(backend.pin_refs(&hash3), Some(1)); + + worker.handle_announce_message(hash1); + assert!(worker.lru().peek(&hash1).is_some()); + worker.handle_announce_message(hash2); + assert!(worker.lru().peek(&hash2).is_some()); + worker.handle_announce_message(hash3); + assert!(worker.lru().peek(&hash3).is_some()); + assert!(worker.lru().peek(&hash2).is_some()); + assert_eq!(worker.lru().len(), 2); + + // Hash 1 should have gotten unpinned, since its oldest. + assert_eq!(backend.pin_refs(&hash1), Some(0)); + assert_eq!(backend.pin_refs(&hash2), Some(1)); + assert_eq!(backend.pin_refs(&hash3), Some(1)); + + // Hash 2 is getting bumped. + worker.handle_announce_message(hash2); + assert_eq!(worker.lru().peek(&hash2), Some(&2)); + + // Since hash 2 was accessed, evict hash 3. + worker.handle_announce_message(hash4); + assert_eq!(worker.lru().peek(&hash3), None); + } +} -- GitLab From d05f8c57fb29b3c156e542c5473cbb09fae359bb Mon Sep 17 00:00:00 2001 From: Vladimir Istyufeev Date: Mon, 26 Feb 2024 21:40:02 +0400 Subject: [PATCH 130/133] Limit max execution time for `test-linux-stable` CI jobs (#3483) Tests run as part of the `test-linux-stable` jobs could hang as shown [here](https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/jobs/5341393). This PR adds a failsafe for such cases. --- .gitlab/pipeline/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab/pipeline/test.yml b/.gitlab/pipeline/test.yml index b5e26d19489..9f774aab271 100644 --- a/.gitlab/pipeline/test.yml +++ b/.gitlab/pipeline/test.yml @@ -48,6 +48,7 @@ test-linux-stable: - target/nextest/default/junit.xml reports: junit: target/nextest/default/junit.xml + timeout: 90m test-linux-oldkernel-stable: extends: test-linux-stable -- GitLab From 4080632ee05fc3d7734c856b3af3a024d63197ef Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 26 Feb 2024 18:23:37 +0100 Subject: [PATCH 131/133] [prdoc] Validate crate names (#3467) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes: - Add CI script to check that the `crate` names that are mentioned in prdocs are valid. We can extend it lateron to also validate the correct SemVer bumps as introduced in https://github.com/paritytech/polkadot-sdk/pull/3441. Example output: ```pre $ python3 .github/scripts/check-prdoc.py Cargo.toml prdoc/*.prdoc 🔎 Reading workspace polkadot-sdk/Cargo.toml. 📦 Checking 36 prdocs against 494 crates. ✅ All prdocs are valid. ``` Note that not all old prdocs pass the check since crates have been renamed: ```pre $ python3 .github/scripts/check-prdoc.py Cargo.toml prdoc/**/*.prdoc 🔎 Reading workspace polkadot-sdk/Cargo.toml. 📦 Checking 186 prdocs against 494 crates. ❌ Some prdocs are invalid. 💥 prdoc/1.4.0/pr_1926.prdoc lists invalid crate: node-cli 💥 prdoc/1.4.0/pr_2086.prdoc lists invalid crate: xcm-executor 💥 prdoc/1.4.0/pr_2107.prdoc lists invalid crate: xcm 💥 prdoc/1.6.0/pr_2684.prdoc lists invalid crate: xcm-builder ``` --------- Signed-off-by: Oliver Tale-Yazdi --- .github/scripts/check-prdoc.py | 71 +++++++++++++++++++++++++++++++ .github/workflows/check-prdoc.yml | 8 ++++ prdoc/1.4.0/pr_1246.prdoc | 2 +- prdoc/1.6.0/pr_2689.prdoc | 2 +- prdoc/1.6.0/pr_2771.prdoc | 2 +- prdoc/pr_3412.prdoc | 2 +- 6 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 .github/scripts/check-prdoc.py diff --git a/.github/scripts/check-prdoc.py b/.github/scripts/check-prdoc.py new file mode 100644 index 00000000000..42b063f2885 --- /dev/null +++ b/.github/scripts/check-prdoc.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 + +''' +Ensure that the prdoc files are valid. + +# Example + +```sh +python3 -m pip install cargo-workspace +python3 .github/scripts/check-prdoc.py Cargo.toml prdoc/*.prdoc +``` + +Produces example output: +```pre +🔎 Reading workspace polkadot-sdk/Cargo.toml +📦 Checking 32 prdocs against 493 crates. +✅ All prdocs are valid +``` +''' + +import os +import yaml +import argparse +import cargo_workspace + +def check_prdoc_crate_names(root, paths): + ''' + Check that all crates of the `crates` section of each prdoc is present in the workspace. + ''' + + print(f'🔎 Reading workspace {root}.') + workspace = cargo_workspace.Workspace.from_path(root) + crate_names = [crate.name for crate in workspace.crates] + + print(f'📦 Checking {len(paths)} prdocs against {len(crate_names)} crates.') + faulty = {} + + for path in paths: + with open(path, 'r') as f: + prdoc = yaml.safe_load(f) + + for crate in prdoc.get('crates', []): + crate = crate['name'] + if crate in crate_names: + continue + + faulty.setdefault(path, []).append(crate) + + if len(faulty) == 0: + print('✅ All prdocs are valid.') + else: + print('❌ Some prdocs are invalid.') + for path, crates in faulty.items(): + print(f'💥 {path} lists invalid crate: {", ".join(crates)}') + exit(1) + +def parse_args(): + parser = argparse.ArgumentParser(description='Check prdoc files') + parser.add_argument('root', help='The cargo workspace manifest', metavar='root', type=str, nargs=1) + parser.add_argument('prdoc', help='The prdoc files', metavar='prdoc', type=str, nargs='*') + args = parser.parse_args() + + if len(args.prdoc) == 0: + print('❌ Need at least one prdoc file as argument.') + exit(1) + + return { 'root': os.path.abspath(args.root[0]), 'prdocs': args.prdoc } + +if __name__ == '__main__': + args = parse_args() + check_prdoc_crate_names(args['root'], args['prdocs']) diff --git a/.github/workflows/check-prdoc.yml b/.github/workflows/check-prdoc.yml index 91701ca036e..c31dee06ec5 100644 --- a/.github/workflows/check-prdoc.yml +++ b/.github/workflows/check-prdoc.yml @@ -57,3 +57,11 @@ jobs: echo "Checking for PR#${GITHUB_PR}" echo "You can find more information about PRDoc at $PRDOC_DOC" $ENGINE run --rm -v $PWD:/repo -e RUST_LOG=info $IMAGE check -n ${GITHUB_PR} + + - name: Validate prdoc for PR#${{ github.event.pull_request.number }} + if: ${{ !contains(steps.get-labels.outputs.labels, 'R0') }} + run: | + echo "Validating PR#${GITHUB_PR}" + python3 --version + python3 -m pip install cargo-workspace==1.2.1 + python3 .github/scripts/check-prdoc.py Cargo.toml prdoc/pr_${GITHUB_PR}.prdoc diff --git a/prdoc/1.4.0/pr_1246.prdoc b/prdoc/1.4.0/pr_1246.prdoc index a4d270c45cb..3b5c2017f22 100644 --- a/prdoc/1.4.0/pr_1246.prdoc +++ b/prdoc/1.4.0/pr_1246.prdoc @@ -11,7 +11,7 @@ migrations: description: "Messages from the DMP dispatch queue will be moved over to the MQ pallet via `on_initialize`. This happens over multiple blocks and emits a `Completed` event at the end. The pallet can be un-deployed and deleted afterwards. Note that the migration reverses the order of messages, which should be acceptable as a one-off." crates: - - name: cumulus_pallet_xcmp_queue + - name: cumulus-pallet-xcmp-queue note: Pallet config must be altered according to the MR description. host_functions: [] diff --git a/prdoc/1.6.0/pr_2689.prdoc b/prdoc/1.6.0/pr_2689.prdoc index 847c3e8026c..5d3081e3a4c 100644 --- a/prdoc/1.6.0/pr_2689.prdoc +++ b/prdoc/1.6.0/pr_2689.prdoc @@ -1,7 +1,7 @@ # Schema: Parity PR Documentation Schema (prdoc) # See doc at https://github.com/paritytech/prdoc -title: BEEFY: Support compatibility with Warp Sync - Allow Warp Sync for Validators +title: "BEEFY: Support compatibility with Warp Sync - Allow Warp Sync for Validators" doc: - audience: Node Operator diff --git a/prdoc/1.6.0/pr_2771.prdoc b/prdoc/1.6.0/pr_2771.prdoc index 1b49162e439..50fb99556ec 100644 --- a/prdoc/1.6.0/pr_2771.prdoc +++ b/prdoc/1.6.0/pr_2771.prdoc @@ -6,4 +6,4 @@ doc: Enable better req-response protocol versioning, by allowing for fallback requests on different protocols. crates: - - name: sc_network + - name: sc-network diff --git a/prdoc/pr_3412.prdoc b/prdoc/pr_3412.prdoc index d68f05e45dc..1ee6edfeb83 100644 --- a/prdoc/pr_3412.prdoc +++ b/prdoc/pr_3412.prdoc @@ -13,5 +13,5 @@ doc: crates: - name: pallet-babe - - name: pallet-aura-ext + - name: cumulus-pallet-aura-ext - name: pallet-session -- GitLab From 81d447a8feca14222fef57ffa721302a8b808562 Mon Sep 17 00:00:00 2001 From: philoniare Date: Tue, 27 Feb 2024 04:40:13 +0800 Subject: [PATCH 132/133] Cleanup String::from_utf8 (#3446) # Description *Refactors `String::from_utf8` usage in the pallet benchmarking Fixes #389 --------- Co-authored-by: command-bot <> --- .../benchmarking-cli/src/pallet/command.rs | 50 ++++++++++--------- .../benchmarking-cli/src/pallet/writer.rs | 46 +++++++---------- 2 files changed, 44 insertions(+), 52 deletions(-) diff --git a/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs b/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs index d5dc6226ab9..c6ba2824016 100644 --- a/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs +++ b/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs @@ -298,16 +298,23 @@ impl PalletCmd { // Convert `Vec` to `String` for better readability. let benchmarks_to_run: Vec<_> = benchmarks_to_run .into_iter() - .map(|b| { + .map(|(pallet, extrinsic, components, pov_modes)| { + let pallet_name = + String::from_utf8(pallet.clone()).expect("Encoded from String; qed"); + let extrinsic_name = + String::from_utf8(extrinsic.clone()).expect("Encoded from String; qed"); ( - b.0, - b.1, - b.2, - b.3.into_iter() + pallet, + extrinsic, + components, + pov_modes + .into_iter() .map(|(p, s)| { (String::from_utf8(p).unwrap(), String::from_utf8(s).unwrap()) }) .collect(), + pallet_name, + extrinsic_name, ) }) .collect(); @@ -329,12 +336,12 @@ impl PalletCmd { let mut component_ranges = HashMap::<(Vec, Vec), Vec>::new(); let pov_modes = Self::parse_pov_modes(&benchmarks_to_run)?; - for (pallet, extrinsic, components, _) in benchmarks_to_run.clone() { + for (pallet, extrinsic, components, _, pallet_name, extrinsic_name) in + benchmarks_to_run.clone() + { log::info!( target: LOG_TARGET, - "Starting benchmark: {}::{}", - String::from_utf8(pallet.clone()).expect("Encoded from String; qed"), - String::from_utf8(extrinsic.clone()).expect("Encoded from String; qed"), + "Starting benchmark: {pallet_name}::{extrinsic_name}" ); let all_components = if components.is_empty() { vec![Default::default()] @@ -415,12 +422,7 @@ impl PalletCmd { ) .map_err(|e| format!("Failed to decode benchmark results: {:?}", e))? .map_err(|e| { - format!( - "Benchmark {}::{} failed: {}", - String::from_utf8_lossy(&pallet), - String::from_utf8_lossy(&extrinsic), - e - ) + format!("Benchmark {pallet_name}::{extrinsic_name} failed: {e}",) })?; } // Do one loop of DB tracking. @@ -494,11 +496,7 @@ impl PalletCmd { log::info!( target: LOG_TARGET, - "Running benchmark: {}.{}({} args) {}/{} {}/{}", - String::from_utf8(pallet.clone()) - .expect("Encoded from String; qed"), - String::from_utf8(extrinsic.clone()) - .expect("Encoded from String; qed"), + "Running benchmark: {pallet_name}::{extrinsic_name}({} args) {}/{} {}/{}", components.len(), s + 1, // s starts at 0. all_components.len(), @@ -707,12 +705,14 @@ impl PalletCmd { Vec, Vec<(BenchmarkParameter, u32, u32)>, Vec<(String, String)>, + String, + String, )>, ) -> Result { use std::collections::hash_map::Entry; let mut parsed = PovModesMap::new(); - for (pallet, call, _components, pov_modes) in benchmarks { + for (pallet, call, _components, pov_modes, _, _) in benchmarks { for (pallet_storage, mode) in pov_modes { let mode = PovEstimationMode::from_str(&mode)?; let splits = pallet_storage.split("::").collect::>(); @@ -766,6 +766,8 @@ fn list_benchmark( Vec, Vec<(BenchmarkParameter, u32, u32)>, Vec<(String, String)>, + String, + String, )>, list_output: ListOutput, no_csv_header: bool, @@ -773,11 +775,11 @@ fn list_benchmark( let mut benchmarks = BTreeMap::new(); // Sort and de-dub by pallet and function name. - benchmarks_to_run.iter().for_each(|(pallet, extrinsic, _, _)| { + benchmarks_to_run.iter().for_each(|(_, _, _, _, pallet_name, extrinsic_name)| { benchmarks - .entry(String::from_utf8_lossy(pallet).to_string()) + .entry(pallet_name) .or_insert_with(BTreeSet::new) - .insert(String::from_utf8_lossy(extrinsic).to_string()); + .insert(extrinsic_name); }); match list_output { diff --git a/substrate/utils/frame/benchmarking-cli/src/pallet/writer.rs b/substrate/utils/frame/benchmarking-cli/src/pallet/writer.rs index 9493a693bbe..bd4b65d8a2e 100644 --- a/substrate/utils/frame/benchmarking-cli/src/pallet/writer.rs +++ b/substrate/utils/frame/benchmarking-cli/src/pallet/writer.rs @@ -153,8 +153,8 @@ fn map_results( continue } - let pallet_string = String::from_utf8(batch.pallet.clone()).unwrap(); - let instance_string = String::from_utf8(batch.instance.clone()).unwrap(); + let pallet_name = String::from_utf8(batch.pallet.clone()).unwrap(); + let instance_name = String::from_utf8(batch.instance.clone()).unwrap(); let benchmark_data = get_benchmark_data( batch, storage_info, @@ -166,7 +166,7 @@ fn map_results( worst_case_map_values, additional_trie_layers, ); - let pallet_benchmarks = all_benchmarks.entry((pallet_string, instance_string)).or_default(); + let pallet_benchmarks = all_benchmarks.entry((pallet_name, instance_name)).or_default(); pallet_benchmarks.push(benchmark_data); } Ok(all_benchmarks) @@ -571,19 +571,22 @@ pub(crate) fn process_storage_results( let mut prefix_result = result.clone(); let key_info = storage_info_map.get(&prefix); + let pallet_name = match key_info { + Some(k) => String::from_utf8(k.pallet_name.clone()).expect("encoded from string"), + None => "".to_string(), + }; + let storage_name = match key_info { + Some(k) => String::from_utf8(k.storage_name.clone()).expect("encoded from string"), + None => "".to_string(), + }; let max_size = key_info.and_then(|k| k.max_size); let override_pov_mode = match key_info { - Some(StorageInfo { pallet_name, storage_name, .. }) => { - let pallet_name = - String::from_utf8(pallet_name.clone()).expect("encoded from string"); - let storage_name = - String::from_utf8(storage_name.clone()).expect("encoded from string"); - + Some(_) => { // Is there an override for the storage key? - pov_modes.get(&(pallet_name.clone(), storage_name)).or( + pov_modes.get(&(pallet_name.clone(), storage_name.clone())).or( // .. or for the storage prefix? - pov_modes.get(&(pallet_name, "ALL".to_string())).or( + pov_modes.get(&(pallet_name.clone(), "ALL".to_string())).or( // .. or for the benchmark? pov_modes.get(&("ALL".to_string(), "ALL".to_string())), ), @@ -662,15 +665,10 @@ pub(crate) fn process_storage_results( // writes. if !is_prefix_identified { match key_info { - Some(key_info) => { + Some(_) => { let comment = format!( "Storage: `{}::{}` (r:{} w:{})", - String::from_utf8(key_info.pallet_name.clone()) - .expect("encoded from string"), - String::from_utf8(key_info.storage_name.clone()) - .expect("encoded from string"), - reads, - writes, + pallet_name, storage_name, reads, writes, ); comments.push(comment) }, @@ -698,11 +696,7 @@ pub(crate) fn process_storage_results( ) { Some(new_pov) => { let comment = format!( - "Proof: `{}::{}` (`max_values`: {:?}, `max_size`: {:?}, added: {}, mode: `{:?}`)", - String::from_utf8(key_info.pallet_name.clone()) - .expect("encoded from string"), - String::from_utf8(key_info.storage_name.clone()) - .expect("encoded from string"), + "Proof: `{pallet_name}::{storage_name}` (`max_values`: {:?}, `max_size`: {:?}, added: {}, mode: `{:?}`)", key_info.max_values, key_info.max_size, new_pov, @@ -711,13 +705,9 @@ pub(crate) fn process_storage_results( comments.push(comment) }, None => { - let pallet = String::from_utf8(key_info.pallet_name.clone()) - .expect("encoded from string"); - let item = String::from_utf8(key_info.storage_name.clone()) - .expect("encoded from string"); let comment = format!( "Proof: `{}::{}` (`max_values`: {:?}, `max_size`: {:?}, mode: `{:?}`)", - pallet, item, key_info.max_values, key_info.max_size, + pallet_name, storage_name, key_info.max_values, key_info.max_size, used_pov_mode, ); comments.push(comment); -- GitLab From f5de090aa56d0801f043c2b13892191b3d0c9f50 Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Tue, 27 Feb 2024 08:24:07 -0300 Subject: [PATCH 133/133] fix(zombienet): increase timeout in download artifacts (#3376) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix timeouts downloading the artifacts (e.g https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/jobs/5246040) Thx! --------- Co-authored-by: Bastian Köcher --- polkadot/zombienet_tests/misc/0002-upgrade-node.zndsl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/polkadot/zombienet_tests/misc/0002-upgrade-node.zndsl b/polkadot/zombienet_tests/misc/0002-upgrade-node.zndsl index db0a60ac1df..5fe1b2ad2f1 100644 --- a/polkadot/zombienet_tests/misc/0002-upgrade-node.zndsl +++ b/polkadot/zombienet_tests/misc/0002-upgrade-node.zndsl @@ -10,9 +10,8 @@ dave: parachain 2001 block height is at least 10 within 200 seconds # POLKADOT_PR_ARTIFACTS_URL=https://gitlab.parity.io/parity/mirrors/polkadot/-/jobs/1842869/artifacts/raw/artifacts # with the version of polkadot you want to download. -# avg 30s in our infra -alice: run ./0002-download-polkadot-from-pr.sh with "{{POLKADOT_PR_ARTIFACTS_URL}}" within 60 seconds -bob: run ./0002-download-polkadot-from-pr.sh with "{{POLKADOT_PR_ARTIFACTS_URL}}" within 60 seconds +alice: run ./0002-download-polkadot-from-pr.sh with "{{POLKADOT_PR_ARTIFACTS_URL}}" within 240 seconds +bob: run ./0002-download-polkadot-from-pr.sh with "{{POLKADOT_PR_ARTIFACTS_URL}}" within 240 seconds # update the cmd to add the flag '--insecure-validator-i-know-what-i-do' # once the base image include the version with this flag we can remove this logic. alice: run ./0002-update-cmd.sh within 60 seconds -- GitLab
Changelog

Sourced from once_cell's changelog.

1.19.0

  • Use portable-atomic instead of atomic-polyfill, #251.